011.SpringBoot集成Hazelcast

1,241 阅读35分钟

Hazelcast简介

Hazelcast 是一个分布式计算和缓存平台。Hazelcast 采用 Java 语言实现,拥有 Java、C++、.NET、REST、Python、Go 和 Node.js 客户端。Hazelcast 还支持 Memcached 和 REST 协议。它最常使用的场景:在服务器A上缓存的数据,可以在服务器B上直接使用,感觉就像用本地缓存一样。

其主要功能有:

  • 提供了 Map、Queue、MultiMap、Set、List、Semaphore、Atomic 等接口的分布式实现
  • 提供了基于Topic 实现的消息队列或订阅\发布模式;
  • 提供了分布式id生成器(IdGenerator)
  • 提供了分布式事件驱动(Distributed Events)
  • 提供了分布式计算(Distributed Computing)
  • 提供了分布式查询(Distributed Query)

官网文档地址:

docs.hazelcast.com/hazelcast/l…

课外知识:

Hazelcast缓存框架背后的公司也是叫Hazelcast。

Hazelcast公司的名称来源于两个单词:Hazel和cast。Hazel是一种植物(榛树),而cast则表示将数据从一个地方传输到另一个地方。因此,Hazelcast的名称意味着将数据从一个地方传输到另一个地方,就像植物的花粉一样。

重要说明:

image-20230714172922161

Hazelcast 不再支持 JDK 8 作为 Hazelcast 5.3.0 及更高版本的运行时。需要JDK 11+。

JDK8所能支持的最高版本为Hazelcast5.2.X版本。所以本文以最新版本Hzelcast5.2.4版本为例进行讲述

虽然,我在JDK8中使用最新版本Hazelcast5.3.1没有遇到问题,但是避免不必要的麻烦,如果用JDK8,那么最高就不要超过5.2版本

Hazelcast版本区别

Hazelcast 分为开源版和商用版,开源版本遵循 Apache License 2.0 开源协议可以免费使用,商用版本需要获取特定的License。 两者之间最大的区别在于:商用版本提供了数据的高密度存储。

我们知道在JVM中,有自己特定的GC机制,无论数据是在堆中还是栈中,只要发现无效引用的数据块,就有可能被回收。而Hazelcast的分布式数据都存放在JVM的内存中,频繁的读写数据会导致大量的GC开销。使用商业版的Hazelcast会拥有高密度存储的特性,大大降低JVM的内存开销,从而降低GC开销。

商用版本初提供高密度存储外,还提供了更多的数据结构、更好的性能、更好的可扩展性、更好的安全性等。此外,商用版本还提供了更好的支持和服务,例如,技术支持、培训、咨询等。

针对springboot项目,开源版和商用版不同的引入方式:

  • 开源版

    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>hazelcast</artifactId>
        <version>5.2.4</version>
    </dependency>
    
  • 商用版

    <dependency>
        <groupId>com.hazelcast</groupId>
        <artifactId>hazelcast-enterprise</artifactId>
        <version>5.2.4</version>
    </dependency>
    

hazelcast集群方式

1.嵌入式方式

image-20230707083726692

这种嵌入式方式集群在springboot项目中非常的简单,由于hazelcast本身就是java编写的,我们在pom文件中引入hazelcast,几乎不需要什么代码,就能把带有集群特性的hazelcast跑起来。当一台机器上改写缓存记录时,hazelcast负责将改动分发到集群中的其他机器中。而加入集群的发现机制,如果我们在不配置的情况下,会采用多播方式查找同一网络中的其他成员。

该方式的优点:

  • 设置集群容易

    上面用描述过这种方式非常的简单,只需要几行代码或者几句配置就能创建集群。

  • 数据访问非常快

    访问数据的时候,由于访问的是本地缓存,没有网络开销,所以访问非常快

该方式的缺点:

  • 复制和同步代价很大

    缓存中添加或更新记录时,该记录都会与集群的其他成员同步,这会导致大量的网络通信。

  • 消耗大量的内存

    当集群中的节点过多时,由于集群中的每个成员机,都会有其他成员的部分或者全部备份。那么会消耗大量的内存。

  • 仅能用于java

    springboot项目中引入一个jar包,就能实现集群,这种方式仅仅用于java,其他语言无法引入jar包。

hazelcast关键配置:

Hazelcast.newHazelcastInstance();

2.客户端-服务器方式

image-20230707140148041

这里member1到member4是Hazelcast集群的成员,他们构成了缓存集群。Hazelcast是通过外部访问的形式,和该集群进行通讯。相当于现在springboot程序,是一个客户端来访问Hazelcast集群。Hazelcast 使用 TCP socket通信。这时,就不仅仅可以使用java来访问这个缓存集群了。

该方式的优点:

  • 缓存具有更好的扩展性

    这里的缓存集群是独立的,避免了上面说的缓存浪费的情况,而且便于扩展集群数量。

  • 更容易找到问题

    由于缓存和程序是独立的,如果发生异常时,便于程序定位问题。

  • 可以针对不同语言的客户端

    客户端可以不仅仅是java语言,还能支持REST、Python、Go 和 Node.js 客户端。

该方式的缺点:

  • 通讯时间更长

    由于客户端和缓存集群有网络通讯的开销,所以可能会比嵌入式花费更长的时间。

  • 版本兼容性问题

    必须注意缓存集群和客户端之间的Hazelcast版本兼容性问题。

hazelcast关键配置:

HazelcastClient.newHazelcastClient();

3.二级缓存方式

image-20230707145837892

其实就是把上面两种方式进行结合。在取缓存值的时候,会首先从本地缓存读取,如果本地缓存没有找到数据,再从远端的缓存集群中请求数据并且将其添加到本地缓存中。当应用程序想要再次读取该数据时,可以在本地缓存中找到它。这样可以减少网络流量。但是凡事有利有弊,用二级缓存是我们必须接受可能的数据不一致。这是由于本地缓存有自己的缓存配置,它会根据这个配置失效数据。如果缓存集群中的数据被更新或删除,而我们在本地缓存中仍然可能有过时的数据。要跟进实际业务具体情况具体分析。不过一般情况是用不到二级缓存的。

hazelcast关键配置:

HazelcastClient.newHazelcastClient(createClientConfig());
private ClientConfig createClientConfig() {
 ClientConfig clientConfig = new ClientConfig();
 clientConfig.addNearCacheConfig(createNearCacheConfig());
 return clientConfig;
}
private NearCacheConfig createNearCacheConfig() {
 NearCacheConfig nearCacheConfig = new NearCacheConfig();
 nearCacheConfig.setName("mymap");
 nearCacheConfig.setTimeToLiveSeconds(360);
 nearCacheConfig.setMaxIdleSeconds(60);
 return nearCacheConfig;
}

Hazelcast存储数据的实现过程

1.Hazelcast分区

由于Hazelcast 服务之间是端对端的,没有主从之分,集群中所有的节点都存储等量的数据以及进行等量的计算。

Hazelcast 默认情况下把数据存储在 271 个区上,这个值可以通过系统属性 hazelcast.partition.count来配置。

2.Hazelcast分区存储原理

对于一个给定的键,在经过序列化、哈希并对分区总数取模之后能得到此键对应的分区号,所有的分区等量的分布与集群中所有的节点中,每个分区对应的备份也同样分布在集群中。

也就是说 Hazelcast 会使用哈希算法对数据进行分区,比如对于一个给定的map中的键,或者topic和list中的对象名称,分区存储的过程如下:

  • 先序列化此键或对象名称,得到一个byte数组;
  • 然后对上面得到的byte数组进行哈希运算;
  • 再进行取模后的值即为分区号;
  • 最后每个节点维护一个分区表,存储着分区号与节点之间的对应关系,这样每个节点都知道如何获取数据。

3.Hazelcast集群实现原理

Hazelcast通过分片来存储和管理所有进入集群的数据,采用分片的方案目标是保证数据可以快速被读写、通过冗余保证数据不会因节点退出而丢失、节点可线性扩展存储能力。下面将从理论上说明Hazelcast是如何进行分片管理的。Hazelcast的每个数据分片(shards)被称为一个分区(Partitions)。分区是一些内存段,根据系统内存容量的不同,每个这样的内存段都包含了几百到几千项数据条目,默认情况下,Hazelcast会把数据划分为271个分区,并且每个分区都有一个备份副本。当启动一个集群成员时,这271个分区将会一起被启动。

  • 下图展示了集群只有一个节点时的分区情况。

    image-20230707153318668

    从一个节点的分区情况可以看出,当只启动一个节点时,所有的271个分区都存放在一个节点中

  • 启动第二个节点,会出现下面这样的集群分区方式

    image-20230707153653286

    其中黑色的字体表示分区,蓝色的字体表示备份。节点1存储了标号为1到135的分区,这些分区会同时备份到节点2中。而节点2则存储了136到271的分区,并备份到了节点1中。

  • 再添加2个新的节点到集群中

    image-20230707153842958

    Hazelcast会一个一个的移动分区和备份到新的节点中,使得集群数据分布平衡。实际中分区并不是有序的分布,而是随机分布,上面的示例只是为了方便理解,重要的是理解 Hazelcast 的平均分布分区以及备份。

这个备份数量是可以设置的:不管是xml,yaml或者java代码,配置都是差不多的,参考java代码如下

config.getMapConfig("my-map").setBackupCount(2);

Hazelcast 默认备份的数量是1个,如果备份数量超过1时,每个节点会存放自己的数据以及其它节点上的备份。

嵌入式方式

1.目标

假设有登录用户的信息需要缓存,为了演示过期方便,设置30秒的有效期。看30秒后Hazelcast是不是自动删除了。这里采用map缓存所有用户信息。map的key是userId,而value就是User对象。

2.编码方式

2.1加入依赖

<!-- Hazelcast -->
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast</artifactId>
    <version>5.2.4</version>
</dependency>
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast-spring</artifactId>
    <version>5.2.4</version>
    <exclusions>
        <exclusion>
            <artifactId>hazelcast</artifactId>
            <groupId>com.hazelcast</groupId>
        </exclusion>
    </exclusions>
</dependency>

说明:

  1. 其实在引入hazelcast-spring时,它本身就依赖了hazelcast。这里为什么都引入一遍呢?理由是springboot本身维护了hazelcast的版本。就拿现在做例子用的springboot2.7.10版本来说,它依赖的是hazelcast的5.1.15版本。而我们想使用比较新的hazelcast,所以这里从新指定了两个依赖的版本。
  2. hazelcast 和 hazelcast-spring 的区别在于,hazelcast-spring 是为了结合 Spring 使用,例如在 xml 配置中使用 <hz:hazelcast id="instance"> 这样的命名空间。而 hazelcast 的主要依赖只有一个,即 hazelcast-5.3.1.jar,引入这一个 jar 理论上就能使用 Hazelcast 了。
  3. 如果想使用springboot的cache和Hazelcast结合的话,那么引入hazelcast-spring还是不错的。

2.2配置类

@EnableCaching
@Configuration
public class HazelcastConfig {

    @Bean
    public Config config() {
        Config config = new Config();
        config.setInstanceName("hazelcast-instance");
        config.setClusterName("dev");

        // 设置驱逐策略
        EvictionConfig evictionConfig = new EvictionConfig();
        evictionConfig.setEvictionPolicy(EvictionPolicy.LFU);
        evictionConfig.setMaxSizePolicy(MaxSizePolicy.PER_NODE);
        evictionConfig.setSize(542);

        // 设置map配置
        MapConfig mapUserConfig = new MapConfig();
        mapUserConfig.setName(Constants.CACHE_NAME_SESSION_USERS)
              .setBackupCount(2)
              .setTimeToLiveSeconds(30)
              .setMaxIdleSeconds(30)
              .setEvictionConfig(evictionConfig);
        config.addMapConfig(mapUserConfig);
        return config;
    }

}

驱逐策略:就是当映射的大小超过限制时,缓存的数据会根据策略进行清除。程序代码中设置的是每个节点如果数量达到542个【默认是10000个】,那么按照LFU【最不经常使用】的策略删除缓存中的数据。

Map配置:这里设置备份的数量是2份【setBackupCount(2)】,设置了生命周期30秒【setTimeToLiveSeconds(30)】,设置了最大空闲时间30秒【setMaxIdleSeconds(30)】同时还设置了驱逐策略。过期策略和驱逐策略是可以同时设置的,满足其中的任何一个策略,数据都会被清除。

说明:

1.驱逐策略后面会再单独说明,这里先做个了解即可

2.TimeToLiveSeconds和MaxIdleSeconds的区别这里说一下:

  • TimeToLiveSeconds[TTL]:该元素,如果没有写入操作,那么到了这个时间就会被删除
  • MaxIdleSeconds[最大空闲时间]:该元素,在设置的时间段内,如果没有get()、put()、EntryProcessor.process()、containsKey()这些访问该元素的操作,那么久回被删除。

2.3controller测试类

@RestController
public class UserCacheTestController {

    @Resource
    private HazelcastInstance hazelcastInstance;

    @PostMapping(value = "/writeuser")
    public String writeDataToHazelcast(String userId, String userName) {
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(userId);
        userInfo.setUserName(userName);
        Map<String, Object> hazelcastMap = hazelcastInstance.getMap(Constants.CACHE_NAME_SESSION_USERS);
        hazelcastMap.put(userId, userInfo);
        return "Map数据写入完成" + JSONUtil.toJsonStr(hazelcastMap.get(userId));
    }

    @GetMapping(value = "/readoneuser")
    //@Cacheable(cacheNames = "session:users", key = "#userId", condition = "#userId != null")
    public UserInfo readDataFromHazelcast(String userId) {
        Map<String, Object> hazelcastMap = hazelcastInstance.getMap(Constants.CACHE_NAME_SESSION_USERS);
        return (UserInfo) hazelcastMap.get(userId);
    }

    @GetMapping(value = "/readalluser")
    public Map<String, Object> readAllDataFromHazelcast() {
        return hazelcastInstance.getMap(Constants.CACHE_NAME_SESSION_USERS);
    }

}

这个测试controller取我们定义好的map,然后向里面写入值,进行Hazelcast基本的写入和读取操作。

我们可以用springboot的cache,来缓存数据@Cacheable(cacheNames = "session:users", key = "#userId", condition = "#userId != null")这时需要注意两点:

  • pom文件中引入了hazelcast-spring的依赖
  • HazelcastConfig类或者springboot启动类上加入注解:@EnableCaching

2.4测试

  • 为了能够模拟集群,我们可以设置idea的端口来启动多个springboot程序.

    image-20230713155129370

  • 当启动两个程序的时候,能在控台中看到如下信息:

    image-20230713155322524

  • 测试一下TTL

    可以发现写入的值在30秒后被自动清除了。

2.5.程序代码

gitee.com/mayuanfei/S…下的springboot12

3.配置文件方式

配置文件方式和java配置类设置的属性名称几乎可以一一对应。在用配置文件实现上述功能前,先了解下Hazelcast配置的优先级。

3.1配置优先级

不管是嵌入方式集群还是客户端-服务器方式集群,都会按照如下顺序由高到低查找配置:

优先级嵌入式客户端
1编码方式配置(上例HazelcastConfig类)编码方式配置(上例中HazelcastConfig类)
2系统属性配置的hazelcast.config指定的文件系统属性配置的hazelcast.client.config
3工作目录中的hazelcast.xml工作目录中的hazelcast-client.xml
4类路径上的hazelcast.xml类路径上的hazelcast-client.xml
5工作目录中的hazelcast.yaml工作目录中的hazelcast-client.yaml
6类路径上的hazelcast.yaml类路径上的hazelcast-client.yaml

解释说明:

  • 优先级2的解释

    -Dhazelcast.config=`*`<hazelcast.xml或者hazelcast.yaml的路径>
    

    该路径可以是常规路径,也可以是带有前缀的类路径比如:classpath:

  • 优先级3的解释

    工作目录,拿springboot项目打出来的jar包为例,就是和jar包放在一块的那个目录。

    image-20230714131708309

  • 优先级4的解释

    类路径针对springboot项目来说,src.main.javasrc.main.resources路径以及第三方jar包的根路径

3.2配置文件

  • Hazelcast默认配置文件

    github.com/hazelcast/h…

    在hazelcast默认配置文件中,对Map的默认设置总结如下:

    • 1个同步备份
    • 内存对象为二进制格式
    • 所有其他功能均被禁用
  • 我们定义的xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <hazelcast xmlns="http://www.hazelcast.com/schema/config"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.hazelcast.com/schema/config
               http://www.hazelcast.com/schema/config/hazelcast-config-5.3.xsd">
    
        <instance-name>hazelcast-instance</instance-name>
        <cluster-name>dev</cluster-name>
    
        <!-- 设置session中user的map配置 -->
        <map name="session:users">
            <backup-count>2</backup-count>
            <max-idle-seconds>30</max-idle-seconds>
            <time-to-live-seconds>30</time-to-live-seconds>
            <eviction eviction-policy="LFU" max-size-policy="PER_NODE" size="542"/>
        </map>
    </hazelcast>
    
  • 也可以用yaml配置文件

    hazelcast:
      instance-name: hazelcast-instance
      cluster-name: dev
      map:
        session:users:
          backup-count: 2
          max-idle-seconds: 30
          time-to-live-seconds: 30
          eviction:
            eviction-policy: LFU
            max-size-policy: PER_NODE
            size: 542
    

3.3程序代码

除了上面核心配置的内容外,其他内容与编码方式的代码一致。

gitee.com/mayuanfei/S…下的springboot13

客户端-服务器方式

1.目标

和上面嵌入式方式的目标一致,也是缓存用户信息,只不过这里集群的方式修改为客户端-服务器方式。这种方式很像使用redis或者说是数据库。我们程序中所有缓存的数据都放在了Hazelcast的一个服务器缓存集群中。我们的程序就是个客户端,通过远程获取服务器中的缓存的数据。

2.Hazelcast集群发现机制

Hazelcast集群是由一推的Hazelcast实例构成的网络。集群成员靠发现机制自动加入集群中,集群一旦形成,他们之间的通信始终通过 TCP/IP 进行,就与发现机制无关了。

2.1 发现机制

就是集群成员之间彼此知道对方存在的一种方式。Hazelcast支持的发现机制很多,主要包括以下几种:

  • 自动检测

    默认发现机制是自动检测,当它被启用时,会便利所有可用的发现机制。比如下面列出的TCP、多播等。并且会检测当前的运行时环境,比如你在AWS实例【亚马云的云服务器】上运行,则会自动使用 hazelcast-aws 插件,再比如Kubernetes环境中,也是可以自动检测环境并且发现集群成员的。这个默认发现机制官方是不推荐在生产环境中使用的。估计是效率问题吧。

    image-20230718132430787

  • TCP

  • Multicast【多播】

  • Eureka

  • Zookeeper

  • Kubernetes

  • Tanzu VMware

这里可以看到Hazelcast支持的发现方式还是蛮多的。但是居然连Eureka都支持,却没有对nacos的支持,不免有点感叹国产软件在世界范围内的应用还是不算广泛啊。言归正传,由于其他方式都需要搭建相应的环境,一般采用TCP和多播这两种应用的场景比较多,而多播采用UDP协议,会向集群内所有侦听的成员广播消息。由于局域网内可能限制广播的发送,所以使用前要谨慎确认,避免由于局域网的限制导致功能异常。下面介绍前三种常用方式。

2.2通过自动检测发现集群成员

如果用户没有指定或提供任何配置文件,Hazelcast默认会使用jar包中自带的配置文件——"hazelcast-default.xml"来配置Hazelcast的运行环境。打开这个默认的xml文件可以看到它配置的自动检测配置:

<hazelcast>
    <port auto-increment="true" port-count="100">5701</port>
    ...
    <network>
        <join>
            <auto-detection enabled="true" />
            <multicast enabled="false">
            ...
        </join>
    </network>
    ...
</hazelcast>

2.3通过TCP发现集群成员

使用TCP发现机制,需要配置一个完整的TPC/IP集群(发现和通信都使用TCP/IP协议)。使用TCP/P配Hazelcas集群成员发现时,需要列出全部或部分成员的主机名或IP地址。无需列出所有集群成员,但是当新成员加入时,至少有一个列出的成员必须在集群中处于活跃状态。配置如下:

<hazelcast>
    ...
    <network>
        <join>
            <tcp-ip enabled="true">
                <member-list>
                    <member>machine1</member>
                    <member>machine2</member>
                    <member>machine3:5799</member>
                    <member>192.168.1.0-7</member>
                    <member>192.168.1.21</member>
                </member-list>
            </tcp-ip>
        </join>
    </network>
    ...
</hazelcast>

member元素接受的值类型可以是ip或者主机名,还能指定端口。甚至还可以指定 IP 范围,例如192.168.1.0-7。如果我们都是以ip来地址来定义的话,还能简化为:

<members>192.168.1.0-7,192.168.1.21</members>

在没有指定端口号时,默认是从5701开始到5801结束。就是默认配置里的这段配置:

<port auto-increment="true" port-count="100">5701</port>

2.4通过Multicast[多播]发现集群成员

通过多播自动发现机制,Hazelcast允许集群成员使用多播通信找到彼此。集群成员不需要知道其他成员的具体地址,因为它们只是多播给所有其他成员进行监听。是否可以或允许多播取决于运行时的环境。配置如下:

<hazelcast>
    ...
    <network>
        <join>
            <multicast enabled="true">
                <multicast-group>224.2.2.3</multicast-group>
                <multicast-port>54327</multicast-port>
                <multicast-time-to-live>32</multicast-time-to-live>
                <multicast-timeout-seconds>2</multicast-timeout-seconds>
                <trusted-interfaces>
                    <interface>192.168.1.102</interface>
                </trusted-interfaces>
            </multicast>
        </join>
    </network>
    ...
</hazelcast>
  • multicast-group

    多播的组地址。默认为:224.2.2.3

  • multicast-port

    指定Hazelcast成员侦听或者发送消息的socket端口。默认为:54327端口

  • multicast-time-to-live

    多播数据包的生存时间。

  • multicast-timeout-seconds

    该参数表明了一个成员等待合法的多播响应的时间,单位为s,如果在设定的时间内没有收到合法的响应,该成员就会选举自己成为leader并创建自己的集群。该参数只适用于集群无leader而且新的成员刚启动的场景。如果参数的值设置的太大,比如60s,这意味着在选举出leader之前需要等待60s之后才能进行下一次的尝试。值设置过大和过小都需要谨慎处理,如果值设置的太小,可能导致成员过早的放弃而开始下一轮尝试。

3.搭建一个Hazelcast缓存集群

3.1搭建方式

了解了上面的集群发现机制,搭建集群就是具体实践的过程了。这个搭建也分好几种:

  • 下载压缩包单独在每台服务器上运行构成集群

    下载地址:hazelcast.com/open-source…

    这个压缩包里包含Hazelcast和管理中心。可以采用每台服务器,独立运行的方式构成集群。

  • docker方式

    采用拉取的方式把Hazelcast和管理中心分别拉取,以容器方式启动。比较省时省力。

  • 云部署

  • k8s

3.2采用docker方式部署集群

  • 拉取Hazelcast镜像

    docker pull hazelcast/hazelcast:5.2.4
    
  • 创建一个hazelcast-docker.xml文件

    Docker 容器中的默认配置文件是/opt/hazelcast/config/hazelcast-docker.xml.我们为了便于修改配置在docker启动的时候做一个数据卷。hazelcast-docker.xml内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <hazelcast xmlns="http://www.hazelcast.com/schema/config"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="http://www.hazelcast.com/schema/config
               http://www.hazelcast.com/schema/config/hazelcast-config-5.2.xsd">
    
        <!-- 集群的名称 -->
        <cluster-name>lmcache</cluster-name>
    
        <network>
            <port auto-increment="true" port-count="5">5701</port>
           
            <join>
                <!-- 关闭自动检测发现 -->
                <auto-detection enabled="false"/>
                <!-- 采用tcp/ip机制发现集群成员 -->
                <tcp-ip enabled="true">
                    <members>member1,member2,member3</members>
                </tcp-ip>
                
            </join>
        </network>
        
    </hazelcast>
    

    这里要特别注意设置的内容。里面都是主机名称。也就是docker容器的名字。

  • docker中创建本地网络

    docker network create hazelcast-network
    

    所有指向该网络的容器,均可通过使用容器的名称进行互通。

  • 运行Hazelcast容器

    # 启动第1个集群成员
    docker run --rm -d \
    -v /Users/mayuanfei/docker-vol/hazelcast/hazelcast-docker.xml:/opt/hazelcast/config/hazelcast-docker.xml \
    -e JAVA_OPTS="-Dhazelcast.local.publicAddress=172.17.236.59:5701 -Xms256M -Xmx256M" \
    --network hazelcast-network \
    --name member1 \
    -p 5701:5701 hazelcast/hazelcast:5.2.4
    # 启动第2个集群成员
    docker run --rm -d \
    -v /Users/mayuanfei/docker-vol/hazelcast/hazelcast-docker.xml:/opt/hazelcast/config/hazelcast-docker.xml \
    -e JAVA_OPTS="-Dhazelcast.local.publicAddress=172.17.236.59:5701 -Xms256M -Xmx256M" \
    --network hazelcast-network \
    --name member2 \
    -p 5702:5701 hazelcast/hazelcast:5.2.4
    # 启动第3个集群成员
    docker run --rm -d \
    -v /Users/mayuanfei/docker-vol/hazelcast/hazelcast-docker.xml:/opt/hazelcast/config/hazelcast-docker.xml \
    -e JAVA_OPTS="-Dhazelcast.local.publicAddress=172.17.236.59:5701 -Xms256M -Xmx256M" \
    --network hazelcast-network \
    --name member3 \
    -p 5703:5701 hazelcast/hazelcast:5.2.4
    

    docker命令参数解释:

    命令参数解释
    --rm告诉 Docker 在退出后将容器从本地缓存中删除
    -d采用后台方式启动容器
    -v给容器挂载存储卷,挂载到容器的某个目录。这里用自己定义的配置文件替换默认
    -e指定环境变量,容器中可以使用该环境变量。这里设置JVM占用的最小、最大内存
    --network指定本地网络。处于该网络的容器,均可以通过容器名进行访问
    --name指定容器名。
    -p端口映射。“宿主机端口:容器内端口”
  • 查看容器的运行情况

    docker ps
    

    image-20230720164059795

  • 查看容器启动日志

    docker logs --tail 200 member1
    

    image-20230720145654259

4.客户端方式使用Hazelcast集群

4.1编码方式

还是和上面的例子实现的功能一样,这里仅仅看配置类的内容:

@EnableCaching
@Configuration
public class HazelcastConfig {

    @Bean
    public ClientConfig config() {
        ClientConfig clientConfig = new ClientConfig();
        //设置Hazelcast实例名称
        clientConfig.setInstanceName("hazelcast-client-instance");
        // 设置集群名称.这个参数很重要,一定要和搭建集群时名称一致.默认是dev
        clientConfig.setClusterName("lmcache");
        // 设置集群服务器的地址
        clientConfig.getNetworkConfig()
                .addAddress("172.17.236.59");
        return clientConfig;
    }

    /**
     * 这里默认就会调用HazelcastClient.newHazelcastClient方法.
     * 为了便于理解,加入此方法.
     */
    @Bean
    public HazelcastInstance hazelcastInstance(ClientConfig config) {
        return HazelcastClient.newHazelcastClient(config);
    }

}

4.2配置文件方式

resouces目录下创建hazelcast-client.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<hazelcast-client xmlns="http://www.hazelcast.com/schema/client-config"
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                  xsi:schemaLocation="http://www.hazelcast.com/schema/client-config
                  http://www.hazelcast.com/schema/client-config/hazelcast-client-config-5.2.xsd">

    <cluster-name>lmcache</cluster-name>
    <instance-name>hazelcast-client-instance</instance-name>
    <network>
        <cluster-members>
            <!--
            以下是客户端尝试连接的地址列表。 Hazelcast 集群的所有成员都接受客户端连接。
            使用格式 <hostname>:<port> 如果未指定端口号,则将尝试端口范围 5701-5703。
            -->
            <address>172.17.236.59</address>
        </cluster-members>
        <!--
        关闭智能路由功能.仅用上面地址列表中的集群成员
        -->
        <smart-routing>false</smart-routing>
    </network>
    <connection-strategy>
        <connection-retry>
            <!--
            客户端连接服务器集群超时时间.
            -->
            <cluster-connect-timeout-millis>1000</cluster-connect-timeout-millis>
        </connection-retry>
    </connection-strategy>
</hazelcast-client>

5.程序代码

gitee.com/mayuanfei/S…下的springboot14

Hazelcast管理中心

1.管理中心作用

管理中心是用于管理和监控 Hazelcast 集群的工具。它具有的一些特性如下:

  • 通过 UI、JMX 界面和 Prometheus 监控集群的性能。
  • 查看有关会员、客户和数据结构的统计信息。
  • 在集群上执行 SQL 查询。
  • 执行管理任务,例如识别和诊断集群中的问题。
  • 使用 REST API 端点返回管理中心中显示的信息。

官网地址:docs.hazelcast.com/management-…

2.本地启动管理中心容器

  • 启动管理中心

    # 找到下载的Hazelcast目录,进入management-center目录
    cd management-center/bin
    ./hz-mc start
    
  • 访问管理中心

    http://localhost:8080

    image-20230719160451181

    点击enable蓝色button。进入集群链接界面:

    image-20230720150416639

增加用户的命令:

./hz-mc conf user create --username=laoma --role=admin --password=8位以上的复杂密码

3.添加集群

  • 点击add按钮

    image-20230720150657217

  • 添加集群需要填写的内容

    image-20230720151158034

  • 添加结果

    image-20230720151354512

4.查看集群

点击VIEW CLUSTER按钮后:

image-20230720152014573

Hazelcast序列化

在客户端-服务器这种模式下,java对象存储在集群服务器缓存中时是序列化的。可以理解成一个对象以字节的形式保存上面,存的时候是一个序列化的过程,取的时候是一个反序列化过程。Hazelcast提供了很多序列化方式,其优缺点如下:

序列化接口优点缺点
Serializable1.标准java接口
2.无需额外实现其他方法
3.兼容数据类型很好
1. 费时并且CUP占用率高
2. 占用空间大
Externalizable1.标准java接口
2.比Serializable效率更高
1.必须实现序列化接口
DataSerializable1.比Serializable效率更高1. Hazelcast提供的接口,具有局限性
IdentifiedDataSerializable1. 比Serializable效率更高
2.反序列化期间不使用反射
1.局限于Hazelcast
2.必须实现序列化接口
3.必须实现工厂和配置
Portable1.比Serializable效率更高
2.反序列化期间不使用反射
3.支持版本控制
4.查询期间支持部分反序列化
1.局限于Hazelcast
2.必须实现序列化接口
3.必须实现工厂和配置
4.类定义也与数据一起发送
Compact Serialization1.比Portable内存使用率高
2.无须实现任何接口
3.无须配置
4.查询期间支持部分反序列化
1.局限于Hazelcast
HazelcastJsonValue1.Hazelcast实现,无须编码1.局限于Hazelcast
2.需要在服务器成员上存储额外的元数据。
Custom Serialization1.无须实现任何接口
2.方便灵活
3.基于StreamSerializer、ByteArraySerializer实现
1.必须实现接口
2.需要配置

这里挑常用的几个做实验。

1.Serializable【java的序列化接口】

一个类如果实现了java的Serializable接口,那么这个类对象在存储时,Hazelcast会采用java序列化和反序列的方式进行对象的存储和转换。示例代码:

@Data
public class UserInfo1 implements Serializable {

    /**
     * 用户id
     */
    private String userId;
    /**
     * 用户名称
     */
    private String userName;

    /**
     * 用户年龄
     */
    private Integer age;

    /**
     * 用户出生日期,java.util.Date类型
     */
    private Date birthday;

}

通过java Serializable存入集群中的对象,无法通过管理中心查看具体的值。报错如下:

image-20230721172930330

因为我们客户端的UserInfo1在集群服务器中是没有这个类的。所以在反序列化显示时就报错了。但是程序中是不会有问题的。

2.Compact Serialization【紧凑序列化】

它可以说是Hazelcast中最强序列化,具有以下主要特点:

  • 将结构和数据分开,并按类型存储,而不是按对象存储,这将会减少内存和带宽的使用
  • 无须继承任何类或者实现任何接口
  • 独立于平台和语言
  • 支持查询或索引期间字段的部分反序列化
  • 支持模式演化,允许添加或删除字段,或更改字段类型

紧凑序列化在5.2版本中已经是稳定版。官方建议使用。并且如果你一个Bean什么接口也不实现并且也没有配置序列化的话,默认的序列化方式就是这个紧凑系列化。

2.1支持类型[仅列出java语言]

更多语言查看:docs.hazelcast.com/hazelcast/5…

类型Java描述
BOOLEANboolean真或假由单个位表示。真为 1; 假为 0。
ARRAY_OF_BOOLEANboolean[]布尔数组或 null。
NULLABLE_BOOLEANBoolean一个可以为null的布尔值
ARRAY_OF_NULLABLE_BOOLEANBoolean[]一个可以为null的布尔值数组或null
INT8byte8位二进制补码有符号整数
ARRAY_OF_INT8byte[]一个可以为null的int8数组
NULLABLE_INT8Byte一个可以为null的int8
ARRAY_OF_NULLABLE_INT8Byte[]一个可以为null的int8数组或null
INT16short16位二进制补码有符号整数
ARRAY_OF_INT16short[]一个可以为null的int16数组
NULLABLE_INT16Short一个可以为null的int16
ARRAY_OF_NULLABLE_INT16Short[]一个可以为null的int16数组或null
INT32int32位二进制补码有符号整数
ARRAY_OF_INT32int[]一个可以为null的int32数组
NULLABLE_INT32Integer一个可以为null的int32
ARRAY_OF_NULLABLE_INT32Integer[]一个可以为null的int32数组或null
INT64long64位二进制补码有符号整数
ARRAY_OF_INT64long[]一个可以为null的int64数组
NULLABLE_INT64Long一个可以为null的int64
ARRAY_OF_NULLABLE_INT64Long[]一个可以为null的int64数组或null
FLOAT32float32位IEEE 754浮点数
ARRAY_OF_FLOAT32float[]一个可以为null的float32数组
NULLABLE_FLOAT32Float一个可以为null的float32
ARRAY_OF_NULLABLE_FLOAT32Float[]一个可以为null的float32数组或null
FLOAT64double64位IEEE 754浮点数
ARRAY_OF_FLOAT64double[]一个可以为null的float64数组
NULLABLE_FLOAT64Double一个可以为null的float64
ARRAY_OF_NULLABLE_FLOAT64Double[]一个可以为null的float64数组或null
STRINGString一个可以为null的UTF-8编码字符串
ARRAY_OF_STRINGString[]一个可以为null的字符串数组
DECIMALBigDecimal一个可以为null的任意精度和比例浮点数
ARRAY_OF_DECIMALBigDecimal[]一个可以为null的DECIMAL数组
TIMELocalTime一个可以为null的由小时、分钟、秒和纳秒组成的时间
ARRAY_OF_TIMELocalTime[]一个可以为null的TIME数组
DATELocalDate一个可以为null的由年、月和日组成的日期
ARRAY_OF_DATELocalDate[]一个可以为null的DATE数组
TIMESTAMPLocalDateTime由年、月、日、小时、分钟、秒和纳秒或null组成的时间戳
ARRAY_OF_TIMESTAMPLocalDateTime[]一个可以为null的TIMESTAMP数组
TIMESTAMP_WITH_TIMEZONEOffsetDateTime由年、月、日、小时、分钟、秒和纳秒或null组成的带时区的时间戳
ARRAY_OF_TIMESTAMP_WITH_TIMEZONEOffsetDateTime[]一个可以为null的OffsetDateTime数组
COMPACT任何用户类型用户定义的嵌套紧凑可序列化对象或null
ARRAY_OF_COMPACT任何用户类型数组用户定义的紧凑可序列化对象或null的数组

注意:

从上表中可以看到,Hazelcast的紧凑序列化器,不支持java.util.Date类型。如果使用会报错。可替换为LocalDate、LocalTime、LocalDateTime等支持的格式。

2.2使用很便利

POJO类不用继承特别的类或者实现接口了。如下代码所示:

@Data
public class UserInfo2 {

    /**
     * 用户id
     */
    private String userId;
    /**
     * 用户名称
     */
    private String userName;

    /**
     * 用户年龄
     */
    private Integer age;

    /**
     * 用户出生日期
     */
    private LocalDate birthday;
    //private Date birthday;
    
}

这里如果是java.util.Date。则报错:

com.hazelcast.nio.serialization.HazelcastSerializationException: The 'class java.util.Date' cannot be serialized with zero configuration Compact serialization because this type is not supported yet....

2.3管理中心可以查看

image-20230724131245886

2.4增加字段也很方便

比如在UserInfo2的类中增加一个地址字段:

	/**
     * 新增一个地址
     */
    private String address;

那么无须进行什么配置或者其他什么改动。直接就可以进行序列化和反序列化了。只不过之前的反序列地址字段时一个null值而已。

2.5补充说明

这里Hazelcast为什么不支持Date,而仅仅支持LocalDate、LocalTime、localDateTime呢?也就是说Date和LocalDate、LocalTime、localDateTime的区别是什么。列出几个最显著的区别:

  • 所在的包不同。Date在java.util包下;而另外几个在 java.time包下
  • Date是JDK1.0就已经存在的一个日期时间类。而LocalDate、LocalTime、LocalDateTime是JDK8才有的
  • Date中不光有时间信息还有时区的信息,而JDK8的LocalDate只表示日期,没有时区等信息;LocalTime仅表示时间;localDateTime表示日期时间和Date很像,但是他能精确到纳秒,而Date只能到毫秒。
  • Date非线程安全的;而JDK新增的这几个日期时间类是线程安全的
  • Date的API设计的不太直观,而且部分方法已过时。JDK新增的日期时间类易于使用,提供了更方便的方法来处理日期和时间。

总之,如果是在JDK8以上的JVM环境中推荐使用JDK8新增的这些日期时间类。如果是更旧版本的JDK程序,序列化时最好就选java的Serializable接口。

3.序列化为 JSON

如果想查询存储在Hazelcast中的JSON字符串,可以使用HazelcastJsonValue。HazelcastJsonValue是一个轻量级包装器,告诉Hazelcast集群将给定字符串视为JSON。使用此信息,集群中的成员可以创建元数据来优化对字符串中数据的查询。因此,HazelcastJsonValue当你希望能够查询存储在Hazelcast集群中的JSON值时,最好使用它。

@PostMapping(value = "/writeJsonValue")
public UserInfo2 writeDataToHazelcast(String userId, String userName, Integer age) {
    UserInfo2 userInfo = new UserInfo2();
    userInfo.setUserId(userId);
    userInfo.setUserName(userName);
    userInfo.setAge(age);
    userInfo.setBirthday(LocalDate.now());
    userInfo.setAddress("北京");
    IMap<String, Object> map = hazelcastInstance.getMap(Constants.CACHE_NAME_SESSION_USERS);
    // 这里把对象转换为json,并且告诉Hazelcast这是个HazelcastJsonValue
    map.put(userId, new HazelcastJsonValue(JSONUtil.toJsonStr(userInfo)));
    return userInfo;
}

通过管理中心可以查看这个json字符串:

image-20230724141114639

4.自定义序列化器

要想实现自己的序列化器,可以通过StreamSerializer和ByteArraySerializer接口来实现。

4.1实现 StreamSerializer

public class UserInfo2Serializer implements StreamSerializer<UserInfo2> {

    @Override
    public int getTypeId() {
        return 100;
    }

    @Override
    public void write(ObjectDataOutput out, UserInfo2 object) throws IOException {
        ObjectMapper mapper = SpringUtil.getBean(ObjectMapper.class);
        out.write(mapper.writeValueAsBytes(object));
    }

    @Override
    public UserInfo2 read(ObjectDataInput in) throws IOException {
        InputStream inputStream = (InputStream) in;
        ObjectMapper mapper = SpringUtil.getBean(ObjectMapper.class);
        return mapper.readValue(inputStream, UserInfo2.class);
    }
}

注意:

typeId 必须是唯一的,并且>=1,它被用来在Hazelcast 反序列化对象时确认使用哪个序列化程序用的。

4.2配置 StreamSerializer

//配置序列化器
SerializerConfig sc = new SerializerConfig()
        .setImplementation(new UserInfo2Serializer())
        .setTypeClass(UserInfo2.class);
clientConfig.getSerializationConfig().addSerializerConfig(sc);

或者使用xml配置

<hazelcast>
    <serialization>
        <serializers>
            <serializer type-class="com.mayuanfei.springboot15.pojo.UserInfo2" 
               class-name="com.mayuanfei.springboot15.serializer.UserInfo2Serializer" />
        </serializers>
    </serialization>
    ...
</hazelcast>

5.全局序列化器

从实现的角度上来说,全局序列化器和自定义序列化器基本一致。只是配置的地方和作用域的范围不一样。全局序列化器被设计为一个备用序列化候选方案。就是当一个对象找不到序列化器处理时,用这个全局序列化器进行处理。默认情况下,全局序列化器是不会处理java.io.Serializable和java.io.Externalizable实例。但是可以通过配置来指定全局序列化来处理这些实例。

5.1实现全局序列化器

public class GlobalStreamSerializer implements StreamSerializer<Object> {

    private ObjectMapper mapper = SpringUtil.getBean(ObjectMapper.class);

    @Override
    public int getTypeId() {
        return 10000;
    }
    @Override
    public void write(ObjectDataOutput out, Object object) throws IOException {
        out.write(mapper.writeValueAsBytes(object));
    }

    @Override
    public Object read(ObjectDataInput in) throws IOException {
        InputStream inputStream = (InputStream) in;
        return mapper.readValue(inputStream, Object.class);
    }
}

5.2配置全局序列化器

// 配置全局序列化器
GlobalSerializerConfig gc = new GlobalSerializerConfig()
        .setImplementation(new GlobalStreamSerializer())
        .setOverrideJavaSerialization(true);
clientConfig.getSerializationConfig().setGlobalSerializerConfig(gc);

或者使用xml配置

<hazelcast>
    ...
    <serialization>
        <serializers>
            <global-serializer override-java-serialization="true">GlobalStreamSerializer</global-serializer>
        </serializers>
    </serialization>
    ...
</hazelcast>

6.序列化器的优先级

当Hazelcast序列化一个对象时:

  • 首先检查对象是否为null。
  • 如果上述检查失败,则Hazelcast会查找用户指定的CompactSerializer。
  • 如果上述检查失败,则Hazelcast会检查它是否是com.hazelcast.nio.serialization.DataSerializable或com.hazelcast.nio.serialization.IdentifiedDataSerializable的实例。
  • 如果上述检查失败,则Hazelcast会检查它是否是com.hazelcast.nio.serialization.Portable的实例。
  • 如果上述检查失败,则Hazelcast会检查它是否是默认类型之一的实例。
  • 如果上述检查失败,则Hazelcast会查找用户指定的Custom Serializer,即ByteArraySerializer或StreamSerializer的实现。使用输入对象的Class及其父类(直到Object)搜索自定义序列化器。如果父类搜索失败,则还将检查类实现的所有接口(不包括java.io.Serializable和java.io.Externalizable)。
  • 如果上述检查失败,则Hazelcast会检查它是否是java.io.Serializable或java.io.Externalizable的实例,并且未使用Java Serialization Override功能注册全局序列化器。
  • 如果上述检查失败,并且已注册全局序列化器,则Hazelcast使用已注册的全局序列化器。
  • 如果上述检查失败,并且启用了紧凑序列化,则Hazelcast尝试自动从对象的类中提取模式。

7.程序代码

gitee.com/mayuanfei/S…下的springboot15

再谈Map中的驱逐策略

这个驱逐策略和过期策略要区分开,过期策略是设置一个条目在内存中的生命周期,主要通过:time-to-live-secondsmax-idle-seconds来设置;而驱逐策略限制了Map的大小,当超过限制时,采用什么策略删除条目以减小其大小。通常是配置 sizemax-size-policyeviction-policy属性。

前面示例中配置过的驱逐策略:

<hazelcast>
    ...
    <!-- 设置session中user的map配置 -->
    <map name="session:users">
        <backup-count>2</backup-count>
        <max-idle-seconds>30</max-idle-seconds>
        <time-to-live-seconds>30</time-to-live-seconds>
        <eviction eviction-policy="LFU" max-size-policy="PER_NODE" size="542"/>
    </map>
    ...
</hazelcast>

1.size属性

此属性定义Map的最大值。当达到最大值时,将根据驱逐策略eviction-policy设置的方式删除Map中的条目。

  • 默认值 :0(无限制)
  • 接受的值: 0到Integer.MaxValue之间的整数。如果设置了>0的整数值,则驱逐策略eviction-policy设置的值不能为NONE(也就是不驱逐,下文会列出驱逐策略有哪些值)。

2.max-size-policy属性

如果说上面的size属性是设置最大值的话,这里就是这个最大值的统计角度或者说统计的范围、方式。包括的取值有:

  • PER_NODE

    每个集群成员中Map条目的最大数量。这个是默认值。

  • PER_PARTITION

    每个分区内地图条目的最大数量。存储大小取决于群集成员中的分区计数。一般不应该设置此属性。譬如避免在小型群集中使用此属性。如果群集很小,则它会托管比较大的群分区,因此也会托管比较多的Map条目。因此,对于小型群集,按分区删除条目的开销可能会影响整体性能。

  • USED_HEAP_SIZE

    每个Hazelcast实例中的Map最大可用堆内存大小(单位:MB)。注意,当内存中格式设置为OBJECT时,此策略不起作用,因为在将数据放置为OBJECT时无法确定内存占用情况(默认内存中的对象都是以二进制存储的)。

  • USED_HEAP_PERCENTAGE

    每个Hazelcast实例中的Map最大可用堆内存大小的百分比。如:JVM配置1000MB,而这里设置的是10,则在使用堆内存超过100MB时将开始删除Map中的条目。当内存中格式设置为OBJECT时,此策略不起作用。

  • FREE_HEAP_SIZE

    JVM中最小空闲堆大小(以MB为单位)。

  • FREE_HEAP_PERCENTAGE

    JVM中最小空闲堆大小百分比。例如,如果JVM配置1000 MB,并且该值为10,则在空闲堆大小低于100 MB时将清除Map中的条目。


  • USED_NATIVE_MEMORY_SIZE

    企业版具有的功能。使用堆外内存。每个Hazelcast实例中的Map最大已用本机内存大小(单位:MB)。

  • USED_NATIVE_MEMORY_PERCENTAGE

    企业版具有的功能。每个Hazelcast实例中的Map最大使用本机内存大小的百分比。

  • FREE_NATIVE_MEMORY_SIZE

    企业版具有的功能。每个Hazelcast实例的本机最小空闲内存大小(以MB为单位)。

  • FREE_NATIVE_MEMORY_PERCENTAGE

    企业版具有的功能。每个Hazelcast实例的本机最小空闲内存大小百分比

3.理解示例中设置的size="542"

Hazelcast通过分区来计算地图的大小。例如,使用max-size的PER_NODE属性,则Hazelcast会为每个分区的每个群集成员计算地图条目的最大数量。Hazelcast使用以下方程式来计算分区的最大大小:

partition-maximum-size = max-size * member-count / partition-count 

翻译过来就是:

分区存的最大条目数 = 我们设置的size数 * 集群成员的数量 / 分区数(前文说了这个数是271)

分区最大条目数 = 542/271 * 1 = 2 当一个节点时每个分区中存了2条数据。

在实际测试中,发现PER_NODE和PER_PARTITION在单机上是一样的效果。设置的size最好是271的倍数

4.eviction-policy属性

定义当Map的大小增长超过阈值时要删除哪些映射条目。可以设置如下值:

  • NONE

    默认策略。如果设置,则不会驱逐任何项目,并忽略size设置的大小。

  • LRU

    删除最近最少使用的条目

  • LFU

    删除最不经常使用的映射条目

记忆印记

  • Hazelcast是一个分布式计算和缓存平台。这里我们主要还是用到它的缓存功能。
  • Hazelcast被springboot框架默认集成。说明这框架还是很稳定高效的。
  • Hazelcast可以采用嵌入式集成(也就是引入jar包方式),也可以采用类似redis的集群方式
  • 通过配置类的方式优先级最高。通过xml配置的方式最直观。如果是嵌入式hazelcast.xml;是客户端hazelcast-client.xml【这里再次体现了springboot的约定大于配置的思想】
  • Hazelcast管理中心是一个单独web项目,可以监控集群成员和客户端的各种内存使用情况,还能查看集群成员中存储的值。集监控和查看与一体。
  • 针对客户端-服务器这种模式,Hazelcast的序列化如果一个Bean实现了java.io.Serializable那么会安装java序列化的方式进行存储;而一个Bean什么接口也不实现会按照紧凑序列化方式存储。这个紧凑序列化方式也是官方推荐的。只不过要注意它支持的类型。java.util.Date用JDK8新包中的类代替
  • Hazelcast占用的是JVM的内存,也就是说会收到JVM垃圾回收的影响。