在看部门基础设施架构时, OSG模块服务Eureka用于服务发现, 从Apollo启动读取配置, 从MySQL定时加载网关配置, 并集成了缓存Hazelcast, Hazelcast是什么? 怎么用? 为什么用它? 带着问题, 找到挺不错的一篇文章, 以下为转载正文!
本系列文章为学习
Hazelcast
的笔记,内容大部分都为官方文档翻译,如果对Hazelcast感兴趣,可移步
Hazelcast
查看官方原版文档。
公司项目服务化改造后,缺少一种在微服务多个实例间广播、多播的机制,为了解决这个问题决定引入Vert.x Event Bus(cluster)来实现,在学习使用Event Bus的过程中了解到Event Bus的默认ClusterManager基于Hazelcast项目,简单看了一下感觉还不错,决定整体学习一遍扩展视野。
1.1 Hazelcast是什么
Hazelcast是基于内存的数据网格开源项目,同时也是该公司的名称。Hazelcast提供弹性可扩展的分布式内存计算,Hazelcast被公认是提高应用程序性能和扩展性最好的方案。Hazelcast通过开放源码的方式提供以上服务。更重要的是,Hazelcast通过提供对开发者友好的Map、Queue、ExecutorService、Lock和JCache接口使分布式计算变得更加简单。例如,Map接口提供了内存中的键值存储,这在开发人员友好性和开发人员生产力方面提供了NoSQL的许多优点。
除了在内存中存储数据外,Hazelcast还提供了一组方便的api来访问集群中的cpu,以获得最大的处理速度。轻量化和简单易用是Hazelcast的设计目标。Hazelcast以Jar包的方式发布,因此除Java语言外Hazelcast没有任何依赖。Hazelcast可以轻松地内嵌已有的项目或应用中,并提供分布式数据结构和分布式计算工具。
Hazelcast 具有高可扩展性和高可用性(100%可用,从不失败)。分布式应用程序可以使用Hazelcast进行分布式缓存、同步、集群、处理、发布/订阅消息等。Hazelcast基于Java实现,并提供C/C++,.NET,REST,Python、Go和Node.js客户端。Hazelcast遵守内存缓存协议,可以内嵌到Hibernate框架,并且可以和任何现有的数据库系统一起使用。
Hazelcast的整体架构如下:
Hazelcast整体架构
Hazelcast的架构不对开发者暴露
Hazelcast基于Java语言编写,没有任何其他依赖。Hazelcast基于熟悉的Java util包对外暴露相同的API和接口。只要将Hazelcast的jar包添加到classpath中,便可以快速使用JVM集群,并开始构建可扩展的应用程序。
和大多数NoSQL解决方案不同,Hazelcast集群中的节点是对等的,集群中没有主备角色之分,因此Hazelcast无单点故障问题。集群内所有节点存储和计算同量数据。可以把Hazelcast内嵌到已有的应用程序中或使用客户端服务器模式(应用程序作为Hazelcast集群中一个节点的客户端)。
Hazelcast被设计为可以扩展到成百上千个节点,简单的增加节点,新加入的节点可以自动发现集群,集群的内存存储能力和计算能力可以维持线性增加。集群内每两个节点之间都有一条TCP连接,所有的交互都通过该TCP连接。
快、快、快
所有数据都存储在内存中,Hazelcast支持快速写和更新操作。
1.5 为什么选择Hazelcast
1.5.1传统的数据一致性方案
数据是软件系统的核心,在传统的架构中,通常使用关系型数据库存储并提供数据访问服务。应用程序直接和数据交互,数据库在另外的机器上通常存在一个备份。为了提高性能,需要对数据库调优或购买更高性能的服务器,这需要大量的投资和努力。
架构通常的做法是在更加靠近数据库的地方保存一份数据的备份,通常采用外部K-V存储技术或二级缓存来降低数据库访问压力。然而,当数据的性能达到极限或应用程序更多的请求是写请求时,这种方案对降低数据库的访问压力无能为力,因为不管是K-V存储还是二级多级缓存方案都只能降低数据库读压力。即便应用程序大多数是读请求,上述方案也有很多问题:当数据变化后,对缓存的影响是什么,缓存如何处理数据变化(目前公司的项目也在考虑方案解决整个问题),在这种条件下缓存存活时间(TTL)和直写缓存(Write-through )的概念诞生了。
考虑TTL的情况,如果访问的频率比TTL更低(TTL=30s,每次请求间隔35S),则每次访问的数据都不在缓存中,都需要从数据库读取数据,缓存每次都被穿透。另一方面,考虑直写缓存场景,如果集群中缓存的数据有多份,同样会面临数据一致性问题。数据一致问题可以通过节点间的互相通信解决,当一个数据不可用时,该消息可以在集群内的节点之间传播。
基于TTL和直写缓存可以设计一个理想的缓存模型,在该领域已经有缓存服务器和内存数据库等。然而,这些解决方案都是具有由其他技术提供的分布式机制的独立的单机实例(本质不是分布式集群,只是通过其他技术附加了集群特性)。回到问题的起点,如果产品是单节点,或者发行版没有提供一致性,总有一天会遇到容量问题。
1.5.2Hazelcast的一致性解决方案
围绕分布式思想设计的Hazelcast提供一种全新访问处理数据的方法。为了提高灵活性和性能,Hazelcast在集群周围共享数据。Hazelcast基于内存的数据网格为分布式数据提供集群和高可扩展性。
集群无主节点是Hazelcast的一个主要特性,从功能上来讲,集群内每个节点都被配置为对等。第一个加入集群的节点负责管理集群内其他所有节点,例如数据自动平衡、分区表更新广播。如果第一个节点下线,第二个加入集群的节点负责管理集群其他节点。第一个节点故障切换方式如下图所示:
数据完全基于内存存储、访问速度快是Hazelcast的另外一个特点。由于Hazelcast将数据的副本分布到集群中的其他节点上,所以在故障条件下(节点宕机)也不会有数据丢失。Hazelcast提供很多分布式数据结构和分布式计算工具,增强分布式集群内存的访问并通过CPU最大化分布式计算的速度。
1.5.3Hazelcast的优势
基于Jar发布,无需安装软件。
不对用户暴露Hazelcast的架构。
提供开箱即用的分布式数据结构。
无单点故障。
支持动态弹性扩展。
数据备份,节点故障数据无丢失。
集群内节点彼此感知。
使用SPI可以构建自己的分布式数据结构。
拥有一个活跃的开源社区。
Hazelcast将分区均匀的分布到集群的各个节点,Hazelcast自动创建分区的副本,并将副本分布到各个节点来提供可靠性。以上图片展示的Hazelcast分区仅仅是为了方便和清晰的描述Hazelcast分区机制。通常来说分区的分布不是有序的,Hazelcast使用一种随机的方式分布各个分区。这里重点说明Hazelcast均匀的分布分区和分区副本。Hazelcast 3.6版本引入轻量化节点(存储轻量化节点),轻量化节点是一种新的节点类型,轻量化节点不持有任何分区数据。轻量化节点主要用于计算型任务和监听器注册,尽管轻量化节点不保存分区数据,但是轻量化节点可以访问集群中其他节点所持有的分区数据。
1.6.1 数据如何被分区
Hazelcast使用一种哈希算法将数据分布到特定分区。给定一个key,分区计算过程如下:
将key序列化为byte数组。
计算byte数组的哈希值。
哈希值与分区数求余,得到分区ID(注意不是节点数)。
1.6.2 分区表
启动集群内的一个节点时,该节点会自动创建一个分区表。分区表存储分区ID和集群内其他的节点信息。分区表的主要目的是让集群内的所有节点(包括轻量节点)了解分区信息,确保每个节点都知道数据存储在哪个分区,哪个节点上。最早加入集群的节点周期性的向集群内其他节点发送分区表。通过这种方式,当分区关系发生变化时,集群内的所有节点都可以感知该变化。当一个节点加入或离开集群时,分区关系就会发生变化。
当集群内最早加入的节点故障时,剩余节点中最早加入集群的节点负责周期性的向其他节点发送分区表
发送分区表的周期默认是15S,如果想修改周期,可以通过设置环境变量hazelcast.partition.table.send.interval来进行修改。
1.6.3 重分区
重分区是Hazelcast重新分配分区关系的过程,以下两种情况会触发Hazelcast执行重分区动作。
一个节点加入集群.
一个节点离开集群.
在这种情况下,最早加入集群的节点会更新分区关系表,并将更新后的分区表发送给集群内其他节点。轻量节点加入集群不会触发重分区动作,因为轻量节点主要执行计算任务,本身不保存任何分区数据。
以把它看做是内存数据库,不过它与 Redis 等内存数据库又有些不同。项目地址:
http://hazelcast.org/
import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import java.util.concurrent.ConcurrentMap;
public class DistributedMap {
public static void main(String[] args) {
Config config = new Config();
HazelcastInstance h = Hazelcast.newHazelcastInstance(config);
ConcurrentMap<String, String> map = h.getMap("my-distributed-map");
map.put("key", "value");
map.get("key");
//Concurrent Map methods
map.putIfAbsent("somekey", "somevalue");
map.replace("key", "value", "newvalue");
https://www.jianshu.com/p/99d98acb3195
https://www.cnblogs.com/seasonsluo/p/hazelcast-intro.html
作者:习惯沉淀
如果文中有误或对本文有不同的见解,欢迎在评论区留言。
如果觉得文章对你有帮助,请点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
扫码关注一线码农的学习见闻与思考。
回复"大数据","微服务","架构师","面试总结",获取更多学习资源!