大家在平常写代码用缓存的时候是不是要么是内存缓存,要么是redis的缓存?像内存缓存那种,最大的好处是不用去搭建什么缓存中间件,引入个jar包就能直接用了,缺点也很明显,分布式环境下内存只是单机的不能多个实例共享。redis那种好处就是解决了内存的不共享的问题,缺点也很明显,配置复杂还需要服务器啥的比较复杂。
有没有一个中间件能既可以用于分布式缓存,又不用搭建服务器就可以使用的呢?当然有,最近小编接触到了一个分布式缓存工具,结合了上面说的两大特性,感觉很牛逼,接下来给大家介绍一下:
Redis和Hazelcast进行了效率的对比,红色是Redis,蓝色是Hazelcast,根据结果。Redis在低数据负载的时候响应比 Hazelcast 表现更好,而在数据负载和并发请求增加时则表现相反。在不常见的大环境(如我们在脚本4)我们可以看到 Redis的平均响应时间剧烈的增长。Hazelcast 响应时间虽然也随着线程数增加而增长,但是这种增长要稳定得多,而且不像 Redis 表现的那样是指数级。
1. Hazelcast原理
Hazelcast 提供了 Map、Queue、MultiMap、Set、List、Semaphore、Atomic 等常用接口的分布式实现。
2. Hazelcast存储数据的实现过程
2.1 Hazelcast分区
由于Hazelcast 服务之间是端对端的,没有主从之分,集群中所有的节点都存储等量的数据以及进行等量的计算。
Hazelcast 默认情况下把数据存储在 271 个区上,这个值可以通过系统属性 hazelcast.partition.count来配置。
2.2 Hazelcast分区存储原理
对于一个给定的键,在经过序列化、哈希并对分区总数取模之后能得到此键对应的分区号,所有的分区等量的分布与集群中所有的节点中,每个分区对应的备份也同样分布在集群中。
也就是说 Hazelcast 会使用哈希算法对数据进行分区,比如对于一个给定的map中的键,或者topic和list中的对象名称,分区存储的过程如下:
- 先序列化此键或对象名称,得到一个byte数组;
- 然后对上面得到的byte数组进行哈希运算;
- 再进行取模后的值即为分区号;
- 最后每个节点维护一个分区表,存储着分区号与节点之间的对应关系,这样每个节点都知道如何获取数据。
2.3 Hazelcast集群实现原理
Hazelcast通过分片来存储和管理所有进入集群的数据,采用分片的方案目标是保证数据可以快速被读写、通过冗余保证数据不会因节点退出而丢失、节点可线性扩展存储能力。下面将从理论上说明Hazelcast是如何进行分片管理的。
2.3.1 分片
Hazelcast的每个数据分片(shards)被称为一个分区(Partitions)。分区是一些内存段,根据系统内存容量的不同,每个这样的内存段都包含了几百到几千项数据条目,默认情况下,Hazelcast会把数据划分为271个分区,并且每个分区都有一个备份副本。当启动一个集群成员时,这271个分区将会一起被启动。下图展示了集群只有一个节点时的分区情况。
从一个节点的分区情况可以看出,当只启动一个节点时,所有的271个分区都存放在一个节点中。然后我们启动第二个节点,会出现下面这样的集群分区方式。
其中黑色的字体表示分区,蓝色的字体表示备份。节点1存储了标号为1到135的分区,这些分区会同时备份到节点2中。而节点2则存储了136到271的分区,并备份到了节点1中。
此时如果再添加2个新的节点到集群中,Hazelcast会一个一个的移动分区和备份到新的节点中,使得集群数据分布平衡。
注意:
实际中分区并不是有序的分布,而是随机分布,上面的示例只是为了方便理解,重要的是理解 Hazelcast 的平均分布分区以及备份。
2.4 重分区
集群中最老的节点(或者说最先启动)负责定时发送分区表到其他节点,这样如果有其他节点加入或者离开集群,所有的节点也能更新分区表。
这个定时任务时间间隔可以通过系统属性 hazelcast.partition.table.send.interval来配置,缺省值为15秒。重分区会发生在如下时间:
- 节点加入集群时;
- 节点离开集群时。
此时最老节点会更新分区表,然后分发,再接着集群开始移动分区,或者从备份恢复分区。
注意:
如果最老的节点挂了,次老节点会接手这个任务。
3. Hazelcast的使用方式
有两种方式,嵌入式和客户端服务器。
- 嵌入式: Hazelcast 服务器的 jar 包被导入到宿主应用程序中,服务器启动后缓存数据会被存在于各个宿主应用中,优点是可以更低延迟的数据访问。
- 客户端服务器: Hazelcast 客户端的 jar 包被导入宿主应用程序中,服务器 jar 包独立运行于 JVM 中。优点是更容易调试以及有更可靠的性能,最重要的是有更好的扩展性。
使用:
第一步:引入jar包
<dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-all</artifactId> <version>4.2.7</version> </dependency>
第二步:创建一个工具类
package com.example.team6.util; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.map.IMap; import java.util.concurrent.TimeUnit; public class HazelCastUtils { private final HazelcastInstance hazelcastInstance; private static final String MAP_NAME = "test:cache"; public HazelCastUtils(HazelcastInstance hazelcastInstance) { this.hazelcastInstance = hazelcastInstance; } public Object get(String key) { IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME); return map.get(key); } public void set(String key, String value, Integer ttl, TimeUnit timeUnit) { IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME); map.put(key, value, ttl, timeUnit); } public void del(String key) { IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME); map.remove(key); } public void delAllCache() { IMap<Object, Object> map = hazelcastInstance.getMap(MAP_NAME); map.clear(); } }
第三步:配置一个配置类
package com.example.team6.config; import com.example.team6.util.HazelCastUtils; import com.hazelcast.config.*; import com.hazelcast.instance.impl.HazelcastInstanceFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @Configuration public class HazelcastConfiguration { @Bean @Primary public Config config() { EvictionConfig evictionConfig = new EvictionConfig(); //数据释放策略[NONE|LRU|LFU]。这是Map作为缓存的一个参数,用于指定数据的回收算法,默认为NONE。 // //NONE:当设置为NONE时,不会发生数据回收,同时max-size会失效。但是任然可以使用time-to-live-seconds和max-idle-seconds参数来控制数据留存时间。 // //LRU:“最近最少使用“策略。 // //LFU:“最不常用的使用”策略。 evictionConfig.setEvictionPolicy(EvictionPolicy.LRU); evictionConfig.setMaxSizePolicy(MaxSizePolicy.FREE_HEAP_SIZE); Config config = new Config(); config.setInstanceName("testInstanceName") .setClusterName("testClusterName") .addMapConfig(new MapConfig() .setName("testName") .setEvictionConfig(evictionConfig) //数据留存时间[0~Integer.MAX_VALUE]。缓存相关参数,单位秒, // 默认为0。这个参数决定了一条数据在map中的停留时间。 // 当数据在Map中留存超过这个时间并且没有被更新时,它会根据指定的回收策略从Map中移除。值为0时,意味着无求大。 .setTimeToLiveSeconds(10)); return config; } @Bean public HazelCastUtils hazelCastUtils(Config config) { return new HazelCastUtils(HazelcastInstanceFactory.getOrCreateHazelcastInstance(config)); } }
第四步:创建一个controller来测试(只测试类String其他类型没有测)
package com.example.team6.controller; import com.alibaba.fastjson.JSONObject; import com.example.team6.util.HazelCastUtils; import com.example.team6.util.UserDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.concurrent.TimeUnit; @Slf4j @RestController @RequestMapping("/hazelcast") public class HazelcastController { @Autowired private HazelCastUtils hazelCastUtils; @PostMapping(value = "/save") public String saveMapData(@RequestParam String key, @RequestParam String value) { UserDTO userDTO=UserDTO.builder().userId(1).userName(value).build(); hazelCastUtils.set(key, JSONObject.toJSONString(userDTO), 60, TimeUnit.SECONDS); return "success"; } @GetMapping(value = "/get") public Object getMapData(@RequestParam String key) { return hazelCastUtils.get(key); } @GetMapping(value = "/del") public String del(@RequestParam String key) { hazelCastUtils.del(key); return "success"; } @GetMapping(value = "/clear") public String clear() { hazelCastUtils.delAllCache(); return "success"; } }
最后,大家想获取更多知识的,可以继续关注公众号,不定时推送。分享了这么牛逼的知识,还不请小编喝个水吗,哈哈哈,欢迎土豪直接赏赞,谢谢,您的支持就是小编最大的动力。