SpringBoot 整合 hazelcast 分布式缓存技术
🙏废话不多说系列,直接开整🙏
一、Hazelcast 认识
(1)简介
Hazelcast 是由 Hazelcast 公司开发的一款开源的分布式内存级别的缓存数据库,可以为基于JVM环境运行的各种应用提供分布式集群和分布式缓存服务。
利用 Hazelcast 可以满足“分布式”、“集群服务”、“网格式内存数据”、“分布式缓存“、“弹性可伸缩服务”等的要求。
(2)应用
- Hazelcast 提供了对很多 Java 接口的分布式实现,如Map, Queue, ExecutorService, Lock以及 JCache。它以一个 JAR 包的形式提供服务,并且提供了 Java, C/C++, .NET 以及 REST 客户端。
- 在应用时,可以将Hazelcast的jar包直接嵌入到任何使用Java、C++、.NET 开发的产品中,我们只需要在应用中引入一个jar包,进行简单的配置和编码就可以实现。
目前 Hazelcast 已经更新到 4.X 版本。
(3)版本区别
-
Hazelcast 分为开源版和商用版,开源版本遵循 Apache License 2.0 开源协议可以免费使用,商用版本需要获取特定的License。两者之间最大的区别在于:商用版本提供了数据的高密度存储。
-
我们知道在JVM中,有自己特定的GC机制,无论数据是在堆中还是栈中,只要发现无效引用的数据块,就有可能被回收。而Hazelcast的分布式数据都存放在JVM的内存中,频繁的读写数据会导致大量的GC开销。使用商业版的Hazelcast会拥有高密度存储的特性,大大降低JVM的内存开销,从而降低GC开销。
(4)特性
-
自治集群(无中心化)
-
数据按应用分布式存储;
-
抗单点故障;
-
简单易用;
-
其他特性:① 服务器/客户端模型;② 支持脚本管理;③能够和Docker快速整合等;
(5)功能
- 提供了分布式id生成器(IdGenerator);
- 提供了分布式事件驱动(Distributed Events);
- 提供了分布式计算(Distributed Computing);
- 提供了分布式查询(Distributed Query)。
- 提供java.util.{Queue, Set, List, Map}分布式实现。
- 提供java.util.concurrency.locks.Lock分布式实现。
- 提供java.util.concurrent.ExecutorService分布式实现。
- 提供用于一对多关系的分布式MultiMap。
- 提供用于发布/订阅的分布式Topic(主题)。
- 通过JCA与J2EE容器集成和事务支持。
- 提供用于安全集群的Socket层加密。
- 支持同步和异步持久化。
- 为Hibernate提供二级缓存Provider 。
- 通过JMX监控和管理集群。
- 支持动态HTTP Session集群。
- 利用备份实现动态分割。
- 支持动态故障恢复。
总的来说,在独立JVM中经常使用的数据结果或模型,Hazelcast 都提供了分布式集群的实现。
二、Hazelcast 原理
Hazelcast 提供了 Map、Queue、MultiMap、Set、List、Semaphore、Atomic 等常用接口的分布式实现。
以Map接口为例,当我们通过Hazelcast创建一个Map实例后,我们在节点A调用 Map::put("A","A_DATA") 方法添加数据,然后可以在节点B使用 Map::get("A") 获取到值为"A_DATA" 的数据。
三、Hazelcast 存储数据的实现过程
(1)Hazelcast 分区
-
由于Hazelcast 服务之间是端对端的,没有主从之分,集群中所有的节点都存储等量的数据以及进行等量的计算。
-
Hazelcast 默认情况下把数据存储在 271 个区上,这个值可以通过系统属性 hazelcast.partition.count来配置。
(2)Hazelcast 分区存储原理
对于一个给定的键,在经过序列化、哈希并对分区总数取模之后能得到此键对应的分区号,所有的分区等量的分布与集群中所有的节点中,每个分区对应的备份也同样分布在集群中。
也就是说 Hazelcast 会使用哈希算法对数据进行分区,比如对于一个给定的map中的键,或者topic和list中的对象名称,分区存储的过程如下:
-
先序列化此键或对象名称,得到一个byte数组;
-
然后对上面得到的byte数组进行哈希运算;
-
再进行取模后的值即为分区号;
-
最后每个节点维护一个分区表,存储着分区号与节点之间的对应关系,这样每个节点都知道如何获取数据。
(3)Hazelcast 集群实现原理
Hazelcast通过分片来存储和管理所有进入集群的数据,采用分片的方案目标是保证数据可以快速被读写、通过冗余保证数据不会因节点退出而丢失、节点可线性扩展存储能力。下面将从理论上说明Hazelcast是如何进行分片管理的。
【1】分片
Hazelcast的每个数据分片(shards)被称为一个分区(Partitions)。分区是一些内存段,根据系统内存容量的不同,每个这样的内存段都包含了几百到几千项数据条目,默认情况下,Hazelcast会把数据划分为271个分区,并且每个分区都有一个备份副本。当启动一个集群成员时,这271个分区将会一起被启动。
① 集群只有一个节点时的分区情况:
(4)重分区
集群中最老的节点(或者说最先启动)负责定时发送分区表到其他节点,这样如果有其他节点加入或者离开集群,所有的节点也能更新分区表。
这个定时任务时间间隔可以通过系统属性 hazelcast.partition.table.send.interval来配置,缺省值为15秒。
- 重分区发生在:① 节点加入集群;② 节点离开集群;(此时最老节点会更新分区表,然后分发,再接着集群开始移动分区,或者从备份恢复分区。)
- 如果 老节点挂了,此老节点会接手这个任务。
四、Hazelcast 的使用方式
有两种方式:① 嵌入式;② 客户端/服务器模式;
(1)嵌入式
Hazelcast 服务器的 jar 包被导入到宿主应用程序中,服务器启动后缓存数据会被存在于各个宿主应用中,优点是可以更低延迟的数据访问。
(2)客户端 / 服务器
Hazelcast 客户端的 jar 包被导入宿主应用程序中,服务器 jar 包独立运行于 JVM 中。优点是更容易调试以及有更可靠的性能,最重要的是有更好的扩展性。
五、SpringBoot 整合 Hazelcast
- JDK 8+
- SpringBoot 2.4+
(1)引入依赖
<!--hazelcast 核心依赖包-->
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-spring</artifactId>
</dependency>
(2)核心代码
① 配置application.properties
# 应用名称
spring.application.name=springboot-hazelcast
# 应用服务 WEB 访问端口
server.port=8080
② 添加 hazelcast 配置类
@Configuration
public class HazelcastConfiguration {
@Bean
public Config hazelCastConfig() {
Config config = new Config();
// 解决同网段下,不同库项目
GroupConfig gc = new GroupConfig("hazelGroup");
config.setInstanceName("hazelcast-instance")
.addMapConfig(new MapConfig()
.setName("configuration")
// Map 中存储条目的最大值。[0 ~ Integer.MAX_VALUE] 默认值为0
.setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
// 数据释放策[NONE/LRU/LFU] 这是 Map作为缓存的一个参数,用于指定数据的回收算法。默认为 NONE,LRU:最近最少使用策略。
.setEvictionPolicy(EvictionPolicy.LRU)
// 数据留存时间(0~Integer.MAX_VALUE,缓存相关参数,单位秒,默认为0)
.setTimeToLiveSeconds(-1)).setGroupConfig(gc);
return config;
}
}
③ 控制层测试
package edu.study.module.springboothazelcast.controller;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.core.IList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
import java.util.Queue;
/**
* 分布式缓存技术控制层
*
* @author drew
* @date 2021/2/5 15:06
**/
@Slf4j
@RestController
@RequestMapping(path = "/hazelcast")
public class HazelcastController {
@Autowired
@Qualifier("hazelcastInstance")
private HazelcastInstance hazelcastInstance;
@PostMapping(value = "/save")
public String saveMapData(@RequestParam String key, @RequestParam String value) {
Map<String, String> hazelcastMap = hazelcastInstance.getMap("hazelcastMap");
hazelcastMap.put(key, value);
return "success";
}
@GetMapping(path = "/get")
public String getMapData(@RequestParam String key) {
Map<String, String> hazelcastMap = hazelcastInstance.getMap("hazelcastMap");
return hazelcastMap.get(key);
}
@GetMapping(path = "/all")
public Map<String, String> readAllDataFromHazelcast() {
return hazelcastInstance.getMap("hazelcastMap");
}
@GetMapping(path = "/list")
public String saveList(@RequestParam(required = false) String value) {
// 创建集群 list
IList<Object> clusterList = hazelcastInstance.getList("myList");
clusterList.add(value);
return "success";
}
@DeleteMapping(value = "/showList")
public IList<Object> showList() {
return hazelcastInstance.getList("myList");
}
@GetMapping(value = "/clearList")
public String clearList() {
IList<Object> clusterList = hazelcastInstance.getList("myList");
clusterList.clear();
return "success";
}
@GetMapping(value = "/queue")
public String saveQueue(@RequestParam String value) {
// 创建集群Queue
Queue<String> clusterQueue = hazelcastInstance.getQueue("myQueue");
clusterQueue.offer(value);
return "success";
}
@GetMapping(value = "/showQueue")
public Queue<String> showQueue() {
Queue<String> clusterQueue = hazelcastInstance.getQueue("myQueue");
for (String obj : clusterQueue) {
log.warn("value=" + obj);
}
return clusterQueue;
}
@DeleteMapping(value = "/clearQueue")
public String clearQueue() {
Queue<String> clusterQueue = hazelcastInstance.getQueue("myQueue");
clusterQueue.clear();
return "success";
}
}
(3)测试接口汇总
-
map 类型的数据结构
-- 存储元素到指定的 map(hazelcastMap)中 请求方式:POST 请求地址:http://localhost:8080/hazelcast/save 数据: key:phone value:13798765425 -- 查询指定元素 请求方式:GET 请求地址:http://localhost:8080/hazelcast/get?key=name -- 查询所有元素 请求方式:GET 请求地址:http://localhost:8080/hazelcast/all
-
list 类型的数据结构
-- 添加元素到指定集合 请求方式:GET 请求地址:http://localhost:8080/hazelcast/list?value=Fish -- 查询所有 list 元素 请求方式:GET 请求地址:http://localhost:8080/hazelcast/showList -- 清空指定集合 请求方式:DELETE 请求地址:http://localhost:8080/hazelcast/clearList
-
queue 类型的数据结构
- 添加元素;
- 查询元素;
- 删除元素;
(4)hazelcast 延申内容
- hazelcast管理终端:[Hazelcast IMDG](hazelcast.org/download/ar…)
- 注意 hazelcast IMDG 需要跟引入的hazelcast版本保持一致。
- hazelcast 特性:
- 无主从模式;(与许多Nosql 解决方案不同,hazelcast 节点是点对点的。没有主从关系;所以成员都存储相同数据的数据,并进行了相等的处理,避免了单点故障。)
- 弹性可扩展;(hazelcast 旨在扩展成千上万的成员。新成员启动,将自动发现集群,并线性增加存储和处理能力。成员之间通过 TCP 保持连接和通讯。)
- 读写快速高效:(hazelcast 所有数据都存储在内存中,提供基于内存快速高效的读写能力。)
- hazelcast 优势:
- 提供开源版本;
- 无需安装,只是个极小的 jar 包。
- 提供开箱即用的分布式数据结构,如 List, Map, Queue,MultiMap,Topic ,Lock 和 Executor。
- 集群非传统主从关系,避免了单点故障;集群中所有成员共同分担集群功能;
- 集群提供弹性扩展,新成员在内存不足或者负载过高时能动态加入集群。
- 集群中成员分担数据缓存的同时【相互冗余备份】其他成员数据,防止成员离线后数据丢失。
- 提供 SPI 接口支持支持用户自定义分布式数据结构。
- hazelcast 使用场景
- 频繁读写数据;
- 需要高可用分布式缓存;
- 内存行 nosql 存储;
- 分部署环境中弹性扩展;
附录
- Hazelcast 官网地址:Hazelcast | The Real-Time Intelligent Applications Platform【hazelcast.com/】
- Hazelcast 开源地址:GitHub - hazelcast/hazelcast: Open-source distributed computation and storage platform
转载自:https://juejin.cn/post/7362842553845350437