Spring Boot集成Hazelcast实现集群与分布式内存缓存
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/
——————————————————分割线——————————————————
我是黑少,直男一枚,微服务硬核玩家,喜欢分享、爱交友人、崇尚“实践出真知”的理念,以折腾鼓捣代码为乐
我的微信:weiweiweiblack (备注:开源中国)
微信公号:黑少微服务,专注微服务技术分享,非技术不八卦!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
移动端Appium自动化测试框架的优势
众所周知,现在市面上的移动端操作系统已被Android和IOS占领,其中Android的份额更是在80%以上。那么面对市面上林林总总的自动化测试框架和工具,为什么说Appium在自动化测试框架的统治级优势呢,下面先看一下各大主流框架的对比及优势。 一、主流框架对比 下面对比了市面上主流的几大框架: 二、各大框架优缺点说明 1、Monkey是Android SDK自带的测试工具,在测试过程中会向系统发送伪随机的用户事件流,如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试,也有日志输出。实际上该工具只能做程序做一些压力测试,由于测试事件和数据都是随机的,不能自定义,所以有很大的局限性。 2、MonkeyRunner也是Android SDK提供的测试工具。严格意义上来说MonkeyRunner其实是一个Api工具包,比Monkey强大,可以编写测试脚本来自定义数据、事件。缺点是脚本用Python来写,对测试人员来说要求较高,有比较大的学习成本。 3、Espresso是Google的开源自动化测试框架。相对于Robotium和UIAutomator,它的特点是规模...
- 下一篇
Apache Flink 漫谈系列 - 持续查询(Continuous Queries)
摘要:实际问题 我们知道在流计算场景中,数据是源源不断的流入的,数据流永远不会结束,那么计算就永远不会结束,如果计算永远不会结束的话,那么计算结果何时输出呢?本篇将介绍Apache Flink利用持续查询来对流计算结果进行持续输出的实现原理。 实际问题 我们知道在流计算场景中,数据是源源不断的流入的,数据流永远不会结束,那么计算就永远不会结束,如果计算永远不会结束的话,那么计算结果何时输出呢?本篇将介绍Apache Flink利用持续查询来对流计算结果进行持续输出的实现原理。 数据管理 在介绍持续查询之前,我们先看看Apache Flink对数据的管理和传统数据库对数据管理的区别,以MySQL为例,如下图: 如上图所示传统数据库是数据存储和查询计算于一体的架构管理方式,这个很明显,oracle数据库不可能管理MySQL数据库数据,反之亦然,每种数据库厂商都有自己的数据库管理和存储的方式,各自有特有的实现。在这点上Apache Flink海纳百川(也有corner case),将data store 进行抽象,分为source(读) 和 sink(写)两种类型接口,然后结合不同存储的特点...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- Red5直播服务器,属于Java语言的直播服务器
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Hadoop3单机部署,实现最简伪集群
- CentOS8编译安装MySQL8.0.19