redis cluster模式
通过前几章,我们已经了解redis从单机版、高并发的主从架构和保证高可用的哨兵模式。但是主从架构的高并发是针对那些读远大于写的场景,如果要保证读和写都是高并发的场景呢?那就是可以使用redis集群模式。
节点
redis集群模式通常是有多个redis节点组成。Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。
# cluster-enabled yes
cluster-enabled yes
当启动集群模式后,在集群中的每个节点都会存在一个clusterState数据结构。这个结构记录了在当前节点的视角下,集群目前所处的状态,例如集群是在线还是下线,集群包含多少个节点,集群当前的配置纪元等信息。如下图为node1的clusterState结构。
槽指派
在一个三个redis节点组成的redis集群中,当我们往该集群写入一个命令,那这个命令会被写入到哪个节点呢?
redis集群将整个数据库分为16384个槽(slot)。每个redis节点负责其中一部分的槽,当16384个槽都有redis节点负责时,整个redis集群才能正常工作。
为啥是16384个槽(slot)
所以往redis集群写入一个命令后,当前节点通过CRC16(key)和&12384计算出一个位于1~16384之间的值,该值决定这对键值对属于哪个槽。
命令写入
在redis每一个节点中都保存在这个一个关于redis集群的clusterState结构,这个结构包含这个当前这个集群的相关信息,其中也包含着所有槽的指派信息。
如上图所示,在一个三个redis节点的集群中,每一个redis节点都存在这clusterState信息。ClusterState结构中有slots属性,并指向一个大小为16384的redisNode数组。
命令写入的流程:
首先写入其中一个节点,该节点开始通过槽分配算法计算槽值M。 在slots属性中,redisNode[m]快速定位到该槽负责的节点。 判断负责的节点是否为自己,是执行命令,否返回moved命令(moved : )。该命令包含正确的节点的ip和端口号。 客户端接收到moved的错位信息,将命令发送到正确的节点。
槽维护
在起初所有槽没有被指派时,可以通过执行以下命令将一个或者多个槽指派当前执行该命令的redis节点。
# CLUSTER MEET <slot> [slot...]
CLUSTER MEET 0,1,2,3
槽信息会被保存在redisnode节点结构的slots和numslot属性中,其中slots是一个二进制数组大小为16383,该数组的索引正好对应16384个槽,值为0或1,1就表示该索引对应槽由当前redis节点负责;numslot记录当前redis节点负责的槽总数。
在一个集群的中redis节点会相互网络连接发送信息,并告知对方自己在负责哪些槽。其他redis节点收到信息后,会更新自己的clusterState结构信息,主要是包含全部槽信息的clusterState.slots属性和clusterState.nodes中对应节点的clusterNode.slots和numslot信息。因此每一个redis节点中都保存在完整的槽节点指派信息。
重新指派
在三个节点的redis集群,当要给该集群在增加一个节点node4时,槽在已被全部分配,但是node4需要被分配一部分槽给他,不然node4相当于没有工作,所以需要重新分片。以node3需要把槽15000~16384重新分配给node4为例:
开始对15000槽重新分配。 node4准备导入属于15000槽的键值对。 node3准备迁移属于15000槽的键值对。 如果node3存在15000槽的键值对,将这些键值对导入node4。 将槽指派给node4,完成对15000槽的重新指派。 其他槽的重新指派重复以上步骤。
当15555槽正在从node3转移到node4时,会出现一种情况就是15555槽对应的键值对有一部分在node3,另一部分在node4。当客户端需要对15555槽下的一个键值对的更新时,node3会首先检查该键值对的key是否在当前节点,在就更新,不在返回ask错信信息,并指引客户端将该键值对写入node4。
复制和故障转移
在redis集群存在主从节点,主节点负责处理槽,从节点负责复制主节点以及当主节点下线时,替代下线的主节点继续处理槽。
复制
可以通过一下命令让接受该命令的节点成为node_id所指节点的从节点。
cluster replicate <node_id>
执行该命令的流程:
接收到该命令的节点首先会在自己的clusterState.nodes字典中找到node_id所对应节点的clusterNode结构,并将自己的clusterState.myself.slaveof指针指向这个结构,以此来记录这个节点正在复制的主节点。 修改自己的clusterNode属性flags为REDIS_NODE_SLAVE和saveof属相指向主节点clusterNode1。 从节点调用复制代码,复制主节点。
一个节点的成为从节点,并开始复制主节点的信息会被告知其他所有所有节点,并去更新所有节点的clusterState中该主节点对应clusterNode结构的slaves和numslaves属性。
故障转移
集群中的每个节点互相ping其他节点,当没有收到有效回复,就会认定其节点下线。当集群中有一半的节点认定该节点下线,就会广播一条消息告知全部节点,将该节点认定为下线状态,并开始故障转移。
故障转移步骤:
在下线主节点的所有从节点里面选择一个从节点。 被选中的从节点会执行SLAVEOF no one命令,成为新的主节点。 新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。 新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。 新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。
新的主节点选举基本和在redis的Sentinel模式中选举领头Sentinel基本相似,就不在多做详述可以翻开redis哨兵模式笔记。
参考:
redis设计与实现