Redis Cluster 详解

先看一张图,大致触摸下Redis Cluster

Redis Cluster要求至少需要3个master才能组成一个集群,同时每个master至少需要有一个slave节点。各个节点之间保持TCP通信。当master发生了宕机, Redis Cluster自动会将对应的slave节点提拔为master,来重新对外提供服务。

Redis Cluster 功能 : 负载均衡,故障切换 主从复制

负载均衡

先说下槽,集群中每个redis实例都负责接管一部分槽,总槽数为:16384(2^14),如果有3台master,那么每台负责5461个槽(16384/3)。

当redis客户端设置值时,会拿key进行CRC16算法,然后 跟16384取模,得到的就是落在哪个槽位,根据上面表格就得出在哪台节点上。槽公式如下:

slot = CRC16(key) & 16383

Redis集群中,每个节点都会有其余节点ip,负责的槽 等 信息。

JedisCluster如何寻址集群的?

JedisCluster配置只用指定集群中某一个节点的IP,端口信息就可以了。JedisCluster初始化时,会找配置的节点获取整个集群的信息(cluster nodes命令)。

解析集群信息,得到集群中所有master信息,然后遍历每台master,通过ip,端口构建jedis实例,然后put到一个全局nodes变量里面(Map类型) , key为ip,端口,值为Jedis实例,nodes值如下:

nodes={172.19.93.120:6380=redis.clients.jedis.JedisPool@74ad1f1f,.....}

在上面遍历master过程中,还做一件事,遍历此台master负责的槽索引,然后又put到一个全局map slots里面。值为上面的Jedis实例, slots值如下:

slots={0=redis.clients.jedis.JedisPool@74ad1f1f,
1=redis.clients.jedis.JedisPool@74ad1f1f,
2=redis.clients.jedis.JedisPool@74ad1f1f,
....
5461 = redis.clients.jedis.JedisPool@65aa1f2f,    ####另外的master机器
....
16383=redis.clients.jedis.JedisPool@756d1afd}

有了上面的slots变量,当有值set 时, 会先算出slot = getCRC16(key)&(16383-1),假如是12182 , 然后调用slots.get(12182) 得到jedis实例,然后去操作redis。

如果发现MovedDataException,说明初始化得到的槽位与节点的对应关系有问题,(节点新增或者宕机)就会重置slots。

集群机器之间的通信

集群机器等数据信息通常有两种方式,一种是集中式,比如springcloud服务集群信息保存在配置中心 。另一种就是redis的方式,gossip。

集中式 :好处在于,元数据的更新和读取,时效性非常好,一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候立即就可以感知到; 不好在于,所有的元数据的跟新压力全部集中在一个地方,可能会导致元数据的存储有压力。

gossip :好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力; 缺点,元数据更新有延时,可能导致集群的一些操作会有一些滞后。

通信的端口就是本身redis监听端口+10000 ,比如 监听端口6379,通信端口就是16379 。

Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息,常用的Gossip消息可分为: ping 消息、 pong 消息、 meet 消息、 fail 消息等。

  • meet消息 :用于通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。
  • ping消息 :集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。ping消息发送封装了自身节点和部分其他节点的状态数据。
  • pong消息 :当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。
  • fail消息 :当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。

举例当新增一个节点,也就是Meet消息过程

  • 节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面。
  • 节点A根据CLUSTER MEET命令给定的IP地址和端口号,向节点B发送一条MEET消息。
  • 节点B接收到节点A发送的MEET消息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面。
  • 节点B向节点A返回一条PONG消息。
  • 节点A将受到节点B返回的PONG消息,通过这条PONG消息节点A可以知道节点B已经成功的接收了自己发送的MEET消息。
  • 之后,节点A将向节点B返回一条PING消息。
  • 节点B将接收到的节点A返回的PING消息,通过这条PING消息节点B可以知道节点A已经成功的接收到了自己返回的PONG消息,握手完成。
  • 之后,节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点,让其他节点也与节点B进行握手,最终,经过一段时间后,节点B会被集群中的所有节点认识。

举例当一个节点故障,怎么判断下线

集群中的每个节点都会定期向其他节点发送ping命令,如果接受ping消息的节点在指定时间内没有回复pong,则发送ping的节点就把接受ping的节点标记为 主观下线

如果集群半数以上的主节点都将主节点A标记为主观下线,则节点A将被标记为客观下线(通过节点的广播)即下线。

故障切换

当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移,以下是故障转移执行的步骤:

  • 从节点会执行SLAVEOF no one命令,成为新的主节点;
  • 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己;
  • 新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。
  • 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。

主从复制


主从复制简单步骤

  • 从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。
  • slave内部有个定时任务,每1s检查是否有新的master要连接和复制,若发现,就跟master建立socket网络连接。
  • 口令认证 - 若master设置了requirepass,那么salve必须同时发送masterauth的口令认证
  • master 第一次执行全量复制 ,将所有数据发给slave 。(run id不同就做全量复制)
  • master后续持续将写命令,异步复制给slave。

全量复制过程

  • 主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令。
  • 主节点的bgsave执行完成后,将RDB文件发送给从节点; 从节点首先清除自己的旧数据,然后载入接收的RDB文件 ,将数据库状态更新至主节点执行bgsave时的数据库状态。
  • 主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态。
  • 如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态。

服务器运行ID(runid)

每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。通过info Server命令,可以查看节点的runid:

https://pic1.zhimg.com/v2-394ddaa7d44245c412f4b0c5a3ee85c0_b.png

主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行全量复制:

如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,进行全量复制。


腾讯的一个面试题

Redis 集群模式的工作原理说一下?在集群模式下,key是如何寻址的?寻址都有哪些算法?了解一致性hash吗?

https://cloud.tencent.com/product/crs?from=10680

寻址都有哪些算法

hash 算法

根据key的hash值然后取模节点数 , hash(key)%节点数。

缺点:当节点宕机或者新增,会导致节点数变换,所有数据都要重新计算。

redis cluster 的 hash slot 算法

上面已讲

一致性 hash 算法

一致性hash算法通过一个叫作一致性hash环的数据结构实现,环的整数分布范围是( 0 , 1 , 2 , 3 … 2^32-1 ) ,如下图:

假设现在我们有4个对象,分别为o1,o2,o3,o4,使用hash函数计算这4个对象的hash值(范围为0 ~ 2^32-1):

hash(o1) = m1
hash(o2) = m2
hash(o3) = m3
hash(o4) = m4

将m1,m2,m3,m4落在hash环上:

假设我们集群中有 c1,c2,c3三台机器,分别用其ip地址取hash:

hash(c1的ip) = t1
hash(c2的ip) = t2
hash(c3的ip) = t3

将t1,t2,t3落在hash环上:

在hash环上顺时针查找距离这个对象的hash值最近的机器,即是这个对象所属的机器。如上图所示:

  • o1[m1] 对象落在 t3[c3]机器上
  • o2[m2] 对象落在 t1[c1]机器上
  • o3[m3] 对象落在 t2[c2]机器上
  • o4[m4] 对象落在 t2[c2]机器上

新增机器情况

如上图,我们新增了c4机器,计算出落在了hash环上的t4位置,现在只需重组o4对象重新落在c4机器上就ok了,其他对象仍在原有机器上不动。

宕机情况

如上图,我们c1宕机了,则o2需要重组到c3机器上,其他对象仍在原有机器上。

Hash环的数据倾斜问题

一致性Hash算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,例如系统中只有两台服务器,其环分布如下:

此时必然造成大量数据集中到Node A上,而只有极少量会定位到Node B上。为了解决这种数据倾斜问题,一致性Hash算法引入了虚拟节点机制。

虚拟节点

就是对真实的机器映射出多个虚拟节点,那么在hash环上就仿佛有很多个机器节点。具体做法可以在服务器IP或主机名的后面增加编号来实现。

例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六个虚拟节点:

同时数据定位算法不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。


插个题外话

小弟不才搭了个java技术传播的网站,帮助更多的程序员,由于服务器开支不小,您觉得文章有用,帮助您了,麻烦在下面我搭建的架构师网站,点击赞赏!谢谢, 网站开销不小,需要您的支持 ~~~ 【网站也是传播编程知识,都是我手写的技术文章】

维持网站生计


强烈推荐一个 进阶 JAVA架构师 的博客

Java架构师修炼

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者