Redis高可用实现原理——集群

前言

redis高可用有3种方式:主从,哨兵,集群

集群模式通过分片来解决写热点数据容量问题,同时支持主从复制功能,解决读热点问题,并提供故障转移功能,实现高可用

本文将介绍集群中槽位的表示,在集群中执行命令的流程,重新分配槽位,以及复制与故障转移的流程

槽位

集群的数据被分为16384个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽

每个节点会为集群中所有节点维护clusterNode 结构,用于记录其分配的槽位

struct clusterNode {
    // ...
    unsigned char slots[16384/8];
    int numslots;
    // ...
};

clusterNode.slots能表示16384个比特位,如果数组中某下标对应的值为1,,表示该下标对应的槽位属于该节点

同时也会记录集群中,每个槽位被分配给了哪个节点:

typedef struct clusterState {
    // ...
    clusterNode *slots[16384];
    // ...
} clusterState;

clusterState.slots数组的每个元素代表一个节点,表示每个下标对应的槽位,被分配给了哪个节点

这里用两个结构保存的原因是:

  • 想知道槽i是否已经被指派,或者槽i被指派给了哪个节点,只需访问clusterState.slots[i]即可,时间复杂度为O(1)
    • 若通过clusterNode查找,需要遍历所有的节点信息
  • 每次要将节点A的槽指派信息传播给其他节点时,访问clusterNode结构即可

    • 若通过clusterState查找,需要遍历16384个槽位

也就是说,当一种结构无法满足多种业务场景时,redis通过冗余数据的方式,为每种业务场景都维护高效的数据结构

在集群中执行命令

当集群中全部16384个槽位都被指定后,集群就会进入上线状态

某节点收到客户端的命令后,会检查要处理的键属于哪个槽:

  • 如果槽就在当前节点,直接执行命令
  • 如果槽没有指派给当前节点,那么会向客户端返回一个MOVED错误,包含槽位和目标地址,指引客户端转向至正确的节点,并再次发送之前想要执行的命令

怎么计算某个键属于哪个槽位?

  • 如果客户端没有指定。内部用如下公式确定:CRC16(key) & 16383。先计算出key的CRC-16校验和,再&16383,计算出0到16383中间的一个数,就是槽位

重新分配槽位

当集群中有节点下线,或进行扩缩容时,就需要重新分配槽位

由Redis的集群管理软件redis-trib执行:

假设要迁移的槽位是slot

  1. 对目标节点发送命令,让目标节点准备好从源节点导入属于槽slot的键值对
  1. 对源节点发送命令,让源节点准备好将属于槽slot的键值对迁移至目标节点
  1. 向源节点发送命令,获得最多count个属于槽slot的键值对的键名
  1. 对于步骤3获得的每个键名,都向源节点发送命令,将被选中的键原子地从源节点迁移至目标节点
  1. 重复执行步骤3,4,直到属于槽slot的元素都迁移完为止
  1. 想集群中广播槽位指派信息

在进行重新分片期间,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里

面,而另一部分键值对则保存在目标节点里面

当源节点收到客户端的命令,且要处理的恰好就在被迁移的槽位里:

  • 源节点现在本地查找,如果能找到,就执行该命令
  • 如果没找到,则该键可能本来就没有,也可能已经迁移到目标节点了,因此向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,再次发送之前想要执行的命令

接到ASK错误的客户端会根据错误提供的IP地址和端口号,转向至正在导入槽的目标节点,然后首先向目标节点发送一个ASKING命令,之后再重新发送原本想要执行的命令

ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识

为啥需要ASKING命令?

  • 因为此时该槽位还没正式分配给目标节点,如果直接访问,该节点将向客户端返回一个MOVED错误
  • 如果目标节点发现该槽位正在迁入,且该客户端有REDIS_ASKING标识,就破例执行一次命令

MOVED和ASK的区别

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

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

复制与故障转移

Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求

故障检测

集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定的时间内向发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线,并将该信息发给集群中其他节点

如果半数以上负责处理槽的主节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线,将主节点x标记为已下线的节点会向集群广播一条关于主节点x的FAIL消息,这样所有收到这条FAIL消息的节点都会立即将主节点x标记为已下线

故障转移

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

  1. 从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票
  1. 如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点
  1. 每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并统计票数,如果大于半数,这个从节点就会当选为新的主节点
  1. 在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于半数张支持票的从节点只会有一个
  1. 如果没有一个从节点收到足够多的票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止

注意从节点并不是一发现主节点进入已下线状态就进行选举,而是会延迟一定时间

为啥要延迟?

  • 确保主节点已下线状态有时间在集群中传播,因为如果立刻发起选举,可能其他主节点还没收到该节点已下线信息,进而拒绝投票

延迟时间的计算公式为:500ms + random(0 ~ 500)ms + SLAVE_RANK * 1000ms

其中SLAVE_RANK代表从节点已经从主节点复制数据的总量,RANK越小,代表数据越新,这样理论上有最新数据的从节点会先发起选举,符合一般业务要求

然后该节点执行如下操作,完成主从切换:

  1. 执行slave of no one命令
  2. 撤销所有对已下线主节点的槽指派,并全部指向自己
  3. 向集群广播一条PONG消息,通知其他节点自己已经变成了主节点,并接管了原有的槽位
  4. 开始接受和处理自己负责的槽位的命令请求

你可能感兴趣的:(redis)