原创

Redis Cluster详细分析

集群节点

分布式集群一定是由多个节点组成,redis中也不例外,既然是集群那么集群中的每个节点一定要互相感知到对方的存在并且能够及时进行通讯,这一切可以选择第三方实现,例如Hadoop中借助zookeeper来实现集群。不过也可以选择自己来实现,redis就是这样做的。在redis刚启动时每一个节点都是单独的集群,我们需要使用CLUSTER MEET命令来帮助他们握手,当他们握手成功时,他们将会把对方的节点信息保存起来。这样他们就变成了一个整体。

启动节点

前文中所说的集群节点,其实就是我们眼中一个普通的redis服务器,不过在配置中有一点不同,需要在配置中开启cluster-enabled,不过通过源码可以看到,集群模式还是继续使用单节点模式下的redis组件。

  • 节点使用的还是文件事件处理器处理命令请求和返回命令回复
  • 继续使用时间时间处理器来执行serverCron函数,在函数中会特别执行特有的clusterCron函数,负责集群的后台操作,如检查节点状态,发送监测信息,进行故障转移等。
  • 继续使用数据库进行key-value的保存,同时会保留各种不同类型的对象
  • 持久化模块也会继续使用,RBD、AOF两种方式都可以使用
  • 继续使用复制模块来实现复制功能
  • 继续使用发布于订阅模块实现发布于订阅功能
  • 会继续使用lua环境来执行分配到该节点的lua脚本

节点相关数据结构

  • clusterNode:保存了节点的相关状态,创建时间、名字、配置的纪元、ip、端口号等等。在集群中节点会为所有节点包括自己创建一个clusterNode,进行信息的保存。
  • clusteLink:归属于clusterNode,包含了节点的连接信息,套接字、输出输入缓冲区。该信息只是用来节点的湖相连接,于clientLink不同它是提供客户端的。
  • clusterState:每个节点独有一个,记录当前节点视角下,集群所有的状态,在线或下线、集群包含了多少个节点、配置了多少个纪元。

关于CLUSTER MEET的实现

  1. 节点A接收到客户端发送来的cluster meet命令,其中包含了节点B的ip 和port
  2. 节点A根据信息为B创建一个clusterNode,并且添加到clusterState.Nodes里。
  3. 节点A向节点B发送meet消息。
  4. 不出意外节点B将会收到A发过来的消息,并且为A创建一个clusterNode,并且添加到clusterState.Nodes里。
  5. 节点B向A返回PONG消息。
  6. 不出意外节点A收到PONG消息,那么他知道B已经成功的接受到了meet消息,
  7. 节点A将会向B再次返回PING,通知他已经接受到了PONG消息。
  8. 此时节点A和节点B都已经实现了连接,也可以叫做握手完成。
  9. 在这之后节点A会将节点B的信息通过协议传播给A所在的集群的其他节点,一段时间后节点B将会完整的添加进来。

20190526231011152.png

槽指派

Redis集群通过分片的方式来保存键值对数据,记忆中使用分片方式的还有ElasticSearch。在redis中整个集群被分成16384个slot,集群中任意一个数据都属于这些槽中的一个,每个节点可以处理其中0个最多16384个。

只有当集群中的所有slot都有被分配到处理节点时,集群才可以被认为是上线状态。相反则是下线状态。

关于槽信息

在每个节点中使用clusterNode进行槽分配信息的保存,在clusterNode中有一个二进制数组slots长度为2048个字节,共包含16384个二进制位(以我的理解就是相当于一个bitmap)。

  • 如果slots数组中i的值为1,那么说明该节点负责处理i这个slot。相反则不由当前节点进行处理这个slot。

这样使用是为了降低操作的时间复杂度,将查找和新增slot的处理复杂度都变为O(1)。

同样在生成了本节点的clusterNode后,节点也会想起他节点传递该信息,对于接收到了信息的节点,会在前文中提到的clusterState.Nodes里找到对应的节点,并更新起clusterNode。所以集群中的每个节点都会知道所有的槽的指派信息。

clusterState.slots中的每一项都是指向clusterNode结构的指针。这样在判断槽是否已经全部分配,或者获取负责处理槽i的节点信息。那么复杂度同样为O(1)。若clusterState.slots[i]为空,则说明该槽未被分配,也就说明当前集群是下线的状态。

为什么clusterState.slots保存了集群中所有槽的指派信息后,还要在clusterNode中使用slots来保存单个节点的槽指派信息??

  • 因为当程序需要将某个节点的槽指派信息发送出去时,只要将单个节点的clusterNode.slots发出去就可以了。如果redis不使用clusterNode.slots数组,单独使用clusterState.slots的话,那么每次发送时,就需要遍历整个clusterState.slots找到想要发送的信息。

重新分片

redis集群的重新分片操作,可以讲任意数量的已经指派出去的槽,进行重新分配,并且相关的槽的所属的键值对也会被移动到新的节点上。

重新分片的实现原理

redis重新分片由集群管理软件redis-trib实现,redis提供了所有重新分片所需要的命令,而redis-trib就是用来向各个节点发送命令的。

  1. redis-trib对目标节点发送CLUSTER SETSLOT <slot> IMPORTING <source_id>通知目标节点准备好导入键值对数据。
  2. redis-trib对原节点发送CLUSTER SETSLOT <slot> MIGRATING <target_id>,通知原节点准备好原属于该slot的键制对,发送给目标节点。
  3. redis-trib向原节点发送命令获取到指定数量的属于slot的键值对的key。
  4. redis-trib对每个获取到的key都向目标节点发送命令,进行迁移。
  5. 重复前两个步骤,直到在原节点上所有的该slot的key都被进行了迁移,则表示钱已完成。
  6. 迁移完成后,redis-trib想集群中所有的节点发送消息,通知每一个节点该slot已经行了迁移,对clusterState.slots进行更新。

20190526231003049.png

集群的复制与故障转移

Redis集群中的节点有主节点和从节点的区别,主节点负责提供槽的管理,而从节点用于复制一个主节点,在主节点下线时,顶替主节点继续处理客户端的请求。

如何设置从节点

在从节点上执行命令CLUSTER REPLICATE <node_id>这里node_id为要复制的主节点。

  • 收到命令的从节点会在clusterState.Nodes找到要复制的节点信息,并将clusterState.myself.slaveof指针指向该节点。
  • 关闭REDIS_NODE_MASTER标示,并打开REDIS_NODE_SLAVE标示,将当前节点修改为从节点。
  • 随后从节点向主节点发送slaveof命令进行复制,与单机服务使用的是同一个功能。

故障检测

集群中所有节点会定时向其他节点发送ping消息,以此来检测节点是否在线,若在一定时间内没有收到返回消息,那么就会将目标节点标记为疑似下线。

集群中各个节点会互相发送消息的方式来交换集群中各个节点的状态信息,假设节点A通过节点B获取到消息,C节点疑似下线,那么节点A就会C对应的B的下线报告。

若果一个集群中超过半数的节点报告某个某个节点下线,那么该节点将会被标记为一下线,并且会向集群中所有节点发送下线消息。

故障转移

当一个从节点发现自己关注的主节点下线时,从节点将会对主节点进行故障转移,转移步骤如下:

  • 复制下线主节点的所有节点里边会选出一个作为新的主节点。
  • 新的主节点会撤销所有对已下线的主节点的槽的指派,并且将这些槽重新指派给自己。
  • 新的主节点在指派完后,会对集群中所有的节点进行消息发送(PONG),通知这些主节点,之前主节点的槽已经被重新指派到了他的身上,其他节点将会更新其slots信息。
  • 新的主节点将会开始接受库护短命令。

新的主节点选取方式

当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:

  1. 当集群中的某个节点开始一次故障转移时,集群中记录的纪元将会自动增加1
  2. slave发现自己的master变为FAIL。
  3. 将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息,要求其他具有投票权的节点进行投票。
  4. 其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,表示支持这个节点成为主节点,对每一个纪元只发送一次ack
  5. 尝试failover的slave收集FAILOVER_AUTH_ACK,记录自己获得了多少主节点的支持。
  6. 超过半数后变成新Master,若选举失败没有获得足够多票,那么将会进入新的纪元,继续投票
  7. 广播Pong通知其他集群节点,也就是故障转移中的第三步。

从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票


参考

> Redis设计与实现
sev7e0
Write by sev7e0
end
本文目录