Redis 底层学习-集群模式

Redis 集群是 Redis 提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移功能。

节点

一个 Redis 集群通常由多个节点组成,在开始的时候,每个节点都是相互独立的,它们都处于一个只包含自己的集群中,要组建一个真正可用的集群,我们必须将各个独立的节点连接起来,构建一个包含多个节点的集群。

每个节点都保存着 clusterState 结构,这个结构记录了在当前节点的视角下,集群目前所处的状态。

image-20200910171316066

其中 clusterNode 节点保存的是节点对应配置消息和状态

image-20200910171435982

添加节点到集群

通过 cluster meet 命令添加节点到集群。

cluster meet 命令的实现

通过向节点 A 发送 cluster meet 命令,客户端可以让接收命令的节点 A 将另一个节点 B 添加到节点 A 当前所在的集群中

收到命令的节点 A 将于节点 B 进行握手,以此来确认彼此的存在,并为将来进一步的通信打好基础

  1. 节点 A 会为节点 B 创建一个 clusterNode 结构,并将该结构添加到自己的 clusterState.node 字典里面
  2. 节点 A 将根据 cluster meet 命令给定的 Ip 地址和端口号,向节点 B 发送一条 meet 消息
  3. 节点 B 收到节点 A 的 meet 消息,节点 B 将会为节点 A 创建一个 clusterNode 结构 ,并将该结构添加到自己的 clusterState.node 字典里面,节点 B 将向节点 A 发送一条 pong 消息。
  4. 节点 A 接收到节点 B 返回的 pong 消息,通过这条 pong 消息,节点 A 知道节点 B 已经成功的接收到自己发送 meet 消息。节点 A 将向节点 B 返回一条 ping 消息
  5. 节点 B 接收到节点 A 返回的 ping 消息,通过这条 ping 消息,节点 B 知道节点 A 已经成功收到了自己返回的 pong 消息,握手完成。

image-20200910172441434

槽指派

Redis 集群通过分片的方式来保存数据库中的键值对。集群的整个数据库被分为 16384 个槽,数据库中的每个键都属于这 16384 个槽的其中一个,集群中每个节点可以处理 0 个或者 16384 个槽。

当数据库中的 16384 个槽都有节点在处理时,集群处于上线状态,相反,如果数据库中有任务一个槽没有得到处理,那么集群就处于下线状态。

指定槽

通过向节点发送 cluster addslots 命令,可以将一个或多个槽指派给节点负责。

eg:cluster addslots 1 2 3 4 … 5000 将槽 0 至槽 5000 指派给 7000 负责。

记录槽信息

image-20200910173357876

clusterNode 结构的 slots 属性和 numslot 属性记录了节点负责处理哪些槽

slots:slots 时一个二进制数组,如果 slots 数组在索引 i 的二进制的值是 1,那么标示节点负责处理槽 i,否则不负责。

numslot:记录当前节点的负责槽的数量。

image-20200910174140527

clusterState 结构中 slots 数组记录了集群中所有 16384 个槽的指派信息。

集群中执行命令

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:

  • 如果键所在的槽正好就指派给了当前节点,那么节点就直接执行这个命令
  • 如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个 moved 错误,指引客户端转向至正确的节点,并再次发送之前想要执行的命令。

计算键属于哪个槽

通过计算键的哈希值与 16384 做与运算

1
2
def slot_number(key)
return CRC16(key) & 16384

重新分片

Redis 集群的重新分片操作可以将任意数量已经指派给某个节点(原节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从原节点被移动到目标节点。

重新分片操作可以在线进行,在重新分片的过程中,集群不需要下线,并且原节点和目标节点都可以继续处理命令请求。

实现原理

Redis 集群的重新分片操作是由 Redis 的集群管理软件,redis’-trib 负责执行的,Redis 提供了进行重新分片所需的所有命令,而 redis-trib 则通过向原节点和目标节点发送命令来进行重新分片操作。

redis-trib 对集群的单个槽进行重新分片的步骤如下。

  1. redis-trib 对目标节点发送 cluster setslot importing 命令,让目标节点准备好从原节点导入属于槽 slot 的键值对
  2. redis-trib 对原节点发送 cluster setslot migrating 命令,让原节点准备好将属于槽 slot 的键值对迁移至目标节点。
  3. redis-trib 对原节点发送 cluster getkeysinslot 命令,获得最多 count 个属于槽 slot 的键值对的键名。
  4. 对于步骤 3 获得的每个键名,redis-trib 都向原节点发送一个 migrate 0 命令,将被选中的键原子地从原节点迁移到目标节点
  5. 重复执行步骤 3 和步骤 4,直到原节点保存的所有属于槽 slot 的键值对都被迁移到目标节点为止。
  6. redis-trib 向集群中任意一个节点发送 cluster setslot node 命令,将槽 slot 指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽 slot 已经指派给了目标节点。

image-20200911151019083

ASK 错误

在进行重新分片期间,客户端向原节点发送一个数据库键相关的命令,并且命令要处理的数据库键恰好就属于正被迁移的槽时:

  • 原节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令
  • 如果没有找到,那么这个键有可能已经被迁移到了目标节点,原节点将客户端返回一个ask错误,指引客户端转向正在导入槽的目标节点,并在此发送之前想要执行的命令。

ASK 错误和 MOVED 错误的区别

ASK 错误和 MOVED 错误都会导致客户端转向,它们的区别在于:

  • MOVED 错误代表槽的负责权已经从一个节点转移到另一个节点,在客户端收到关于槽 i 的 MOVED 错误后,客户端每次遇到关于槽 i 的命令,都可以直接将命令请求发送到 MOVED 错误所指向的节点。
  • ASK 错误只是两个节点在迁移槽的过程中使用的一种临时措施,在客户端收到关于槽 i 的 ASK 错误之后,客户端只会在接下来的一次命令请求中将关于槽 i 的命令请求发送至 ASK 错误所指向的节点。

故障转移

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

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

选举新的主节点

新的主节点是通过选举产生的

  1. 集群的配置纪元是一个自增计数器,它的初始值为 0
  2. 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值就会被增一
  3. 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票
  4. 当从节点发现自己正在复制的主节点进入以下线状态时,从节点会向集群广播一条 clusterrmsg_type_failover_auth_request 消息,要求所有收到这条消息,并且具有投票权的主节点向这个从节点投票
  5. 如果一个主节点具备投票权,并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条 cluster_typefailover_auth_ack 消息,标示这个主节点支持从节点成为新的主节点
  6. 每个参与选举的从节点都会接收 ack 消息, 并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持
  7. 如果集群里有 N 个具有投票权的主节点,那么当一个从节点收集到大于等于 n/2+1 张支持票时,这个从节点就会当选为新的主节点
  8. 因为在每一个配置纪元里面 ,每个具有投票权的主节点只能投一次票,所以如果有 n 个主节点进行投票,那么具有大于等于 n/2+1 张支持票的从节点只会有一个,这确认了新的主节点只会有一个。
  9. 如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。

这个选举新主节点的方法和选举领导 sentinel 的方法非常相似,都是基于 raft 算法的领头选举方法实现的。