百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

SpringBoot集成Hazelcast实现集群与分布式内存缓存

toyiye 2024-08-25 15:41 4 浏览 0 评论

Hazelcast是Hazelcast公司开源的一款分布式内存数据库产品,提供弹性可扩展、高性能的分布式内存计算。并通过提供诸如Map,Queue,ExecutorService,Lock和JCache等Java的许多开发人员友好的分布式实现。

了解Hazelcast

Hazelcast特性

  • 简单易用

Hazelcast是用Java编写的,没有其他依赖关系。只需简单的把jar包引入项目的classpath即可创建集群。

  • 无主从模式

与许多NoSQL解决方案不同,Hazelcast节点是点对点的。没有主从关系; 所有成员都存储相同数量的数据,并进行相等的处理,避免了单点故障。

  • 弹性可扩展

Hazelcast旨在扩展成千上万的成员。新成员启动,将自动发现群集,并线性增加存储和处理能力。成员之间通过TCP保持连接和通讯。

  • 读写快速高效

Hazelcast所有数据都存储在内存中,提供基于内存快速高效的读写能力。

Hazelcast部署拓扑

在Hazelcast官方提供两种方式部署集群(图片均来自官方文档):

如需聚焦异步或高性能大批量任务的缓存服务,嵌入式方式是相对有优势的,最明显嵌入式方式访问数据延迟性低。

独立创建Hazelcast集群,统一管理,所有的应用程序如果需要访问缓存,可通过Hazelcast客户端(有java .NET C++的实现)或Memcache客户端或简单的REST客户端访问。后续demo示例以嵌入式为例。

Hazelcast数据分区

在Hazelcast分布式环境中,默认情况下,Hazelcast有271个分区。

当启动第一个成员的时候,成员1在集群中的分区如下图:

当在集群中新添加一个节点2时,分区图如下:

在图示中,黑色分区是主分区,蓝色分区是副本分区(备份)。第一个成员具有135个主分区(黑色),并且每个分区都备份在第二个成员(蓝色)中。同时,第一个成员还具有第二个成员的主分区的副本分区。

随着成员的增多,Hazelcast将一些主要和副本分区逐个移动到新成员,使所有成员相等和冗余。只有最小量的分区将被移动到扩展Hazelcast。以下是具有四个成员的Hazelcast集群中的分区图示如下:

Hazelcast在群集成员之间平均分配分区。Hazelcast创建分区的备份,并将其分配给成员之间进行冗余。

上述插图中的分区是为了方便描述。通常,Hazelcast分区不会按照顺序分配(如这些图所示),而是随机分布。Hazelcast在成员间平均分配了分区和备份。

Hazelcast优势

  • Hazelcast提供开源版本。
  • Hazelcast无需安装,只是个极小jar包。
  • Hazelcast提供开箱即用的分布式数据结构,如Map,Queue,MultiMap,Topic,Lock和Executor。
  • Hazelcast集群非传统主从关系,避免了单点故障;集群中所有成员共同分担集群功能。
  • Hazelcast集群提供弹性扩展,新成员在内存不足或负载过高时能动态加入集群。
  • Hazelcast集群中成员分担数据缓存的同时互相冗余备份其他成员数据,防止某成员离线后数据丢失。
  • Hazelcast提供SPI接口支持用户自定义分布式数据结构。

Hazelcast适用场景

  • 频繁读写数据
  • 需要高可用分布式缓存
  • 内存行NoSql存储
  • 分布式环境中弹性扩展

下面我们来使用Spring Boot集成Hazelcast实现分布式集群服务看看

Spring Boot集成Hazelcast实现分布式集群服务

首先新建一个Spring Boot的gradle项目,引入Hazelcast相关jar包:

dependencies {
 compile 'com.hazelcast:hazelcast'
 compile 'org.springframework.boot:spring-boot-starter-web'
}

当Hazelcast包在classpath上,Spring Boot将通过下面两种方式之一为我们创建一个HazelcastInstance实例:

方式一,通过配置属性指定的Hazelcast.xml文件创建:

spring.hazelcast.config = classpath:hazelcast.xml

该方式需要编写一个hazelcast.xml文件,通过xml文件描述Hazelcast集群

方式二,通过提供一个com.hazelcast.config.Config javabean到Spring容器中(下面所有demo是基于java config方式)

@Bean
 public Config hazelCastConfig() {
 //如果有集群管理中心,可以配置
 ManagementCenterConfig centerConfig = new ManagementCenterConfig();
 centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
 centerConfig.setEnabled(true);
 return new Config()
 .setInstanceName("hazelcast-instance")
 .setManagementCenterConfig(centerConfig)
 .addMapConfig(
 new MapConfig()
 .setName("instruments")
 .setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
 .setEvictionPolicy(EvictionPolicy.LRU)
 .setTimeToLiveSeconds(20000));
 }

上面代码通过提供Config的bean时候,主要做了如下几个事:

  • 创建一个默认名为hazelcast-instance的HazelcastInstance实例;
  • 使用默认的组播发现模式,组播传播地址默认为:224.2.2.3,如果想修改信息或修改为TCP模式可通过setNetworkConfig()接口设置相关信息;
  • 创建一个名为dev,访问密码为dev-pass的group保障节点加入,如果想修改组,可通过setGroupConfig()接口设置相关信息;
  • 创建了一个名为instruments的分布式map数据结构,并设置了该map的最大容量200/逐出策略LRU/有效期20000ms等信息,当集群启动后,我们可以在任一成员节点上通过HazelcastInstance读写该map。

完整代码:

@SpringBootApplication
public class StartUp {
 private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);
 public static void main(String[] args) {
 SpringApplication.run(StartUp.class, args);
 }
 @Bean
 public Config hazelCastConfig() {
 //如果有集群管理中心,可以配置
 ManagementCenterConfig centerConfig = new ManagementCenterConfig();
 centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
 centerConfig.setEnabled(true);
 return new Config()
 .setInstanceName("hazelcast-instance")
 .setManagementCenterConfig(centerConfig)
 .addMapConfig(
 new MapConfig()
 .setName("instruments")
 .setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
 .setEvictionPolicy(EvictionPolicy.LRU)
 .setTimeToLiveSeconds(20000));
 }
}

下面我们通过修改server.port分别启动端口为8080和8081的成员服务

当启动完8080成员的时候,可以在8080控制台看到如下日志:

Members [1] {
 Member [172.17.42.1]:5701 - 0d39dd66-d4fb-4af4-8ddb-e9f4c7bbe5a1 this
}

因我们使用的是组播传播模式,5701为节点在组播网络中分配的端口

当启动完8081成员的时候,可以在8081控制台看到如下日志:

Members [2] {
 Member [172.17.42.1]:5701 - 0d39dd66-d4fb-4af4-8ddb-e9f4c7bbe5a1
 Member [172.17.42.1]:5702 - a46ceeb4-e079-43a5-9c9d-c74265211bf7 this
}

回到8080控制台,发现多了一行日志:

Members [2] {
 Member [172.17.42.1]:5701 - 0d39dd66-d4fb-4af4-8ddb-e9f4c7bbe5a1 this
 Member [172.17.42.1]:5702 - a46ceeb4-e079-43a5-9c9d-c74265211bf7
}

发现8081成员也加入进来了。两个控制台都能看到成员列表。集群就已经搭建成功。

为了验证结果,上面我们在集群中已经创建了一个名为instruments的分布式map数据结构,现在我们通过写个接口证明:

@GetMapping("/greet")
 public Object greet() {
 Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
 if (Objects.isNull(value)) {
 Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");
 } LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
 return value;
 }

首先通过访问8080服务的/greet,第一次访问instruments中是没有key为hello的键值对,会往里面塞入{"helo":"world!"},然后访问8081服务的/greet,这个时候应该是能取得改键值对的。

完整代码:

@RestController
@SpringBootApplication
public class StartUp {
 private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);
 public static void main(String[] args) {
 SpringApplication.run(StartUp.class, args);
 }
 @Bean
 public Config hazelCastConfig() {
 //如果有集群管理中心,可以配置
 ManagementCenterConfig centerConfig = new ManagementCenterConfig();
 centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
 centerConfig.setEnabled(true);
 return new Config()
 .setInstanceName("hazelcast-instance")
 .setManagementCenterConfig(centerConfig)
 .addMapConfig(
 new MapConfig()
 .setName("instruments")
 .setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
 .setEvictionPolicy(EvictionPolicy.LRU)
 .setTimeToLiveSeconds(20000));
 }
 @GetMapping("/greet")
 public Object greet() {
 Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
 if (Objects.isNull(value)) {
 Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");
 } LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
 return value;
 }
}

重启8080和8081服务

通过浏览器请求http://localhost:8080/greet

查看8080控制台日志:

2017-10-23 13:52:27.865 INFO 13848 --- [nio-8080-exec-1] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=nul

通过浏览器请求http://localhost:8081/greet

查看8081控制台日志:

2017-10-23 13:52:40.116 INFO 13860 --- [nio-8081-exec-2] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=world

Spring Boot为Hazelcast提供了明确的缓存支持。如果启用缓存, HazelcastInstance则会自动包含在CacheManager实现中。所以完全可以支持Spring Cache。

以往我们用Spring Cache都是基于Redis做存储后端,现在我们使用Hazelcast来尝试一下 首先在启动类上开启缓存

@EnableCaching

建立个service类,demo为了方便,写在一起

完整代码:

@EnableCaching
@RestController
@SpringBootApplication
public class StartUp {
 private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);
 public static void main(String[] args) {
 SpringApplication.run(StartUp.class, args);
 }
 @Bean
 public Config hazelCastConfig() {
 //如果有集群管理中心,可以配置
 ManagementCenterConfig centerConfig = new ManagementCenterConfig();
 centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
 centerConfig.setEnabled(true);
 return new Config()
 .setInstanceName("hazelcast-instance")
 .setManagementCenterConfig(centerConfig)
 .addMapConfig(
 new MapConfig()
 .setName("instruments")
 .setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
 .setEvictionPolicy(EvictionPolicy.LRU)
 .setTimeToLiveSeconds(20000));
 }
 @GetMapping("/greet")
 public Object greet() {
 Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
 if (Objects.isNull(value)) {
 Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");
 } LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
 return value;
 }
 @Autowired
 private DemoService demoService;
 @GetMapping("/cache")
 public Object cache() {
 String value = demoService.greet("hello"); LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
 return value;
 }
}
@Service
@CacheConfig(cacheNames = "instruments")
class DemoService {
 private Logger LOGGER = LoggerFactory.getLogger(DemoService.class);
 @Cacheable(key = "#key")
 public String greet(String key) { LOGGER.info("缓存内没有取到key={}", key);
 return "world!";
 }
}

连续访问两次8080服务的/cache接口 第一次控制台输出日志:

2017-10-23 14:10:02.201 INFO 13069 --- [nio-8081-exec-1] com.hazelcast.DemoService: 缓存内没有取到key=hello
2017-10-23 14:10:02.202 INFO 13069 --- [nio-8081-exec-1] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=world!

第二次控制台输出日志:

2017-10-23 14:11:51.966 INFO 13069 --- [nio-8081-exec-3] com.hazelcast.StartUp: 从分布式缓存获取到 key=hello,value=world!

第二次比第一次相比少了执行service方法体内容,证明第二次是通过了缓存获取。

  • 在Hazelcast官网上,有使用Hazelcast集群和Redis集群做缓存的对比
  • 单只性能上来说,写入速度Hazelcast比Redis快44%,读取速度Hazelcast比Redis快56%
  • 详情移步底下参考资料中链接
  • 下面,我们再来一个尝试,既然有分布式缓存了,我们可以把我们的8080和8081服务做成一个web集群,web服务集群主要标志是前端负载均衡和session共享,我们来实现8080和8081的session共享。

Spring Session已经支持使用Hazelcast作为会话缓存后端,首先引入Spring Session jar包

dependencies {
 compile 'com.hazelcast:hazelcast'
 compile 'org.springframework.boot:spring-boot-starter-web'
 compile 'org.springframework.session:spring-session'
}

要启用Hazelcast作为集群会话缓存后端,有两种方式

第一种Spring Boot配置文件里面配置spring.session.*属性:

spring.session.store-type=hazelcast

第二种使用java注解开启:

@EnableHazelcastHttpSession

这里选择第二种方式,要证明集群会话共享,我们定一个简单接口打印一下sessionId,通过同一浏览器访问8080和8081服务的该接口,看看不同服务请求的时候sessionId是否一致,完整代码如下:

@EnableCaching
@RestController
@EnableHazelcastHttpSession
@SpringBootApplication
public class StartUp {
 private Logger LOGGER = LoggerFactory.getLogger(StartUp.class);
 public static void main(String[] args) {
 SpringApplication.run(StartUp.class, args);
 }
 @Bean
 public Config hazelCastConfig() {
 //如果有集群管理中心,可以配置
 ManagementCenterConfig centerConfig = new ManagementCenterConfig();
 centerConfig.setUrl("http://127.0.0.1:8200/mancenter");
 centerConfig.setEnabled(true);
 return new Config()
 .setInstanceName("hazelcast-instance")
 .setManagementCenterConfig(centerConfig)
 .addMapConfig(
 new MapConfig()
 .setName("instruments")
 .setMaxSizeConfig(new MaxSizeConfig(200, MaxSizeConfig.MaxSizePolicy.FREE_HEAP_SIZE))
 .setEvictionPolicy(EvictionPolicy.LRU)
 .setTimeToLiveSeconds(20000));
 }
 @GetMapping("/greet")
 public Object greet() {
 Object value = Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").get("hello");
 if (Objects.isNull(value)) {
 Hazelcast.getHazelcastInstanceByName("hazelcast-instance").getMap("instruments").put("hello", "world!");
 } LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
 return value;
 }
 @Autowired
 private DemoService demoService;
 @GetMapping("/cache")
 public Object cache() {
 String value = demoService.greet("hello"); LOGGER.info("从分布式缓存获取到 key=hello,value={}", value);
 return value;
 }
 @GetMapping("/session")
 public Object session(HttpSession session) {
 String sessionId = session.getId(); LOGGER.info("当前请求的sessionId={}", sessionId);
 return sessionId;
 }
}
@Service
@CacheConfig(cacheNames = "instruments")
class DemoService {
 private Logger LOGGER = LoggerFactory.getLogger(DemoService.class);
 @Cacheable(key = "#key")
 public String greet(String key) { LOGGER.info("缓存内没有取到key={}", key);
 return "world!";
 }
}

访问8080服务/session接口,控制台日志如下:

2017-10-23 14:28:41.991 INFO 14140 --- [nio-8080-exec-2] com.hazelcast.StartUp: 当前请求的sessionId=e75ffc53-90bc-41cd-8de9-e9ddb9c2a5ee

访问8081服务/session接口,控制台日志如下:

2017-10-23 14:28:45.615 INFO 14152 --- [nio-8081-exec-1] com.hazelcast.StartUp: 当前请求的sessionId=e75ffc53-90bc-41cd-8de9-e9ddb9c2a5ee

集群会话共享生效。

集群管理界面

在上面的demo中,在创建Config的时候,设置了一个ManagementCenterConfig配置,该配置是指向一个Hazelcast集群管理平台,比如demo中表示在本地启动了一个管理平台服务。该功能也是相对其他NoSql服务的一个优势。

要部署ManagementCenter管理平台有多种方式

比如通过https://download.hazelcast.com/management-center/management-center-3.8.3.zip地址下载,解压后启动;

sh ./startManCenter.sh 8200 /mancenter

如果有docker环境,直接可以docker部署:

docker run -ti -p 8200:8080 hazelcast/management-center:latest

部署成功后,访问http://ip:8200/mancenter,首次访问会让你配置个用户名密码,进入后 :

在左侧菜单栏,能看到现有支持的分布式数据格式,比如Maps下面名为instruments的是我们前面demo自己创建的,名为spring:session:sessions是我们用了Hazelcast做集群会话同步的时候Spring为我们创建的。

中间区域能看到所有节点成员的系统相关实时使用率,随便点击一个节点进去,能看到当前节点的系统实时使用率:

红圈里面的即是上面提到的节点数据分区数,通过左侧菜单栏的数据结构进去,能看到当前对应的数据结构的详细信息和实时吞吐量:

更多内容请参考下方参考资料。

示例代码可以通过https://github.com/zggg/hazelcast-in-spring-boot下载。

参考资料

  • 为什么选Hazelcast:https://hazelcast.com/why-hazelcast/imdg/
  • Hazelcast官方文档:http://docs.hazelcast.org/docs/3.8.6/manual/html-single/index.html
  • Redis对比:https://hazelcast.com/use-cases/nosql/redis-replacement/
  • Redis 3.2.8 vs Hazelcast 3.8 集群基准测试对比:https://hazelcast.com/resources/benchmark-redis-vs-hazelcast/

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码