《Redis设计与实现》——读书笔记之Redis集群

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

1、节点

一个Redis集群通常是由多个节点(node)组成的,在刚开的时候这些节点是互相独立的。
需要使用 cluster meet 命令来实现节点之间的连接。

向一个节点发送cluster meet 命令,可以让node节点与ip 和port 所指定的节点进行握手,当握手成功时,node节点就会将ip 和 port 所指定的节点加入到当前所在的集群中。

1.1 启动节点

一个节点就是运行在集群模式下的Redis服务器,Redis服务器在启动的时候会根据Cluster - enabled 配置选项是否为yes 来决定是否开启服务器的集群模式。

1.2 集群数据结构

  • clusterNode: 结构保存了一个节点的当前状态,比如节点的创建时间,节点的名字,节点当前的配置纪元,节点的IP地址和端口号
  • clusterLink: clusterNode结构的link属性是一个clusterLink结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输入缓冲区和输出缓冲区。
  • clusterState : 最后每个节点都保存着一个clusterState结构,这个结构记录了在当前节点的视角下,集群目前所处的状态 例如集群是在线还是下线,集群包含多少个节点,集群当前的配置纪元。

1.3 CLUSTER MEET命令的实现

在这里插入图片描述

2、槽指派

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

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

通过想节点 发送 cluster addslots,我们可以将一个或多个槽指派给节点负责。

例如

  • 0 - 5000 槽由端口为7000的服务器负责
  • 5001 - 10000 槽由端口为7001的服务器负责
  • 100001 - 16384 槽由端口为7002的服务器负责

2.1、记录节点的槽指派信息

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

unsigned char slots[16384 / 8];
int numslots;

slots属性是一个二进制位数组,这个数字的长度为 2048 个字节,共包含16384个二进制位。

  • 如果slots数组在索引i上的二进制位为1,那么节点负责处理槽i
  • 如果slots数组在索引i上的二进制为为0,那么表示节点不负责处理槽i
  • 取出和设置slots数组中的任意一个二进制位的值的复杂度仅为O(1)
  • numslots属性则记录节点负责处理的槽的数量。

2.2、传播节点的槽指派的信息

节点还会将自己的slots数组通过消息发给集群中的其他节点,每个接收到slots数组的节点都会将数组将数组保存到相应的clusterNode结构里面,因此集群中的每个几点都会知道数据库中的16384个槽分别指派给了集群中的哪些节点
在这里插入图片描述

2.3 记录集群所有槽的指派信息

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

  • 如果slots[i]指针指向NULL,那么表示槽i尚未指派给任何节点
  • 如果slots[i]指针指向一个clusterNode结构,那么表示槽i已经指派给了clusterNode结构所代表的节点。
  • 只需要O(1)的时间复杂度就可以直到该槽是否已经被指派,又获得取得槽i的节点。
  • clusterState.slots 数组记录了集群中所有槽的指派信息
  • clusterNode.slots 记录单个节点的槽指派信息

2.4 cluster addslots

cluster addslots 命令接受一个或多个槽作为参数,并将所有输入的槽指派给接收该命令的节点负责。
在cluster addslots 命令执行完毕之后,节点会通过发送消息告知集群中的其他的节点,自己目前正在负责哪些槽。

3、在集群中执行命令

当16384个槽都进行了指派之后,集群就会进入了上线状态。
当客户端香节点发送数据库键有关的命令的时候,接受命令的节点会计算出命令要处理的数据库属于哪个槽,并检查这个槽是否指派给了自己

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

3.1 计算键属于哪个槽

节点使用 CRC16(key) & 16383 算法来计算给定槽key属于哪个槽。

  • CRC16(key) 语句用于计算键key的CRC - 16 校验和
  • & 16383 语句则用于计算 一个介于0 至 16383 之间的整数作为key的槽号。

3.2 判断槽是否由当前节点处理

  • 如果 clusterState.slots[i]等于clusterState.myself,那么说明槽i由当前节点负责,节点可以执行客户端发送的命令
  • 如果clusterState.slots[i]不等于clusterState.myself,节点会根据clusterState.slots[i]指向的clusterNode锁记录的节点IP和端口号,向客户端返回MOVED错误。

3.3 MOVED 错误

当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED 错误,指引客户端转向正在负责槽的节点。

  • MOVED SLOT IP : PORT

在这里插入图片描述
一个集群的客户端通常会与集群中的多个几点创建套接字连接,而所谓的节点转向实际上就是换一个套接字来发送命令 (类似redirect 去 换一个URL 访问)

另外

集群模式的 redis - cli 客户端在接收到MOVED错误的时候,并不会打印出MOVED错误,而是根据MOVED自动进行节点转向,并打印出转向信息。所以我们是看不见节点返回的MOVED错误的。

3.4 、节点数据库的实现

集群节点保存键值对以及键值对过期时间的方式和单机Redis服务器完全相同

但是节点Node只能使用0号数据库,而单机Redis服务器则没有这个限制

除了将键值对保存在数据库里面之外,节点还会用clusterState结构中的slots_to_keys跳跃表来保存槽和键之间的关系

slots_to_keys跳跃表每个节点的分值 score都是一个槽号,而每个节点的成员都是一个数据库键。

  • 每当节点往数据库中添加一个新的键值对的时候,节点就会将这个键以及键的槽号关联到slots - to -keys 跳跃表
  • 当节点删除数据库中的某个键值对的时候,节点就会在slots_to-keys跳跃表接触被删除键与槽号之间的关联。

例如 :

  • 键book所在的跳跃表节点的分值为1337 这表示键book所在的槽为1337

通过slots_to_keys跳跃表记录各个数据库键所属的槽,节点可以很方便的对属于某个或某些槽所有数据库进行批量操作。

4、重新分片

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

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

例如将原本属于7002的槽15001 到 16383 改为指派给节点 7003。

4.1重新分片原理

重新分片操作是由Redis集群管理软件 redis - trib负责执行的。

在这里插入图片描述
一个槽一个槽的迁移,如果重新配片涉及多个槽,那么redis - trib将对给定的槽分别执行上面给出的步骤。

5、ASK错误

再重新分片的期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一个情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面

当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽的时候

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

在这里插入图片描述

ASK错误也不会打印

5.1 cluster setslot importing 命令的实现

clusterState结构的import_slots_form数组记录了当前节点正在从其他节点导入的槽.

5.2 cluster setslot MIGRATING 命令的实现

clusterState结构的migrating_slots_to数组记录了当前节点正在迁移至其他节点的槽。

5.3 ASK错误

如果节点收到一个关于键key的命令请求,并且键key所属的槽i正好指派给了这个节点,那么节点会尝试在自己的数据库里面查找key

如果节点没有在自己的数据库连找到key,那么节点就会检查自己的clusterState migrating_slots_to[i] 看键key所属的槽i是否正在进行迁移,如果槽i的确在进行迁移的话,那么节点就会向客户端发送一个ASK错误,引导客户端到正在导入槽i的节点去查找键key。

在这里插入图片描述

5.4 ASKING 命令

ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识,一般情况下,如果客户端向节点发送一个关于槽i的命令,而槽i又没有指派给这个节点的话,那么节点将向客户端返回一个MOVED错误,但是,如果节点的import_slots_form显示节点正在导入槽i,并且发送命令的客户端带有REDIS_ASKING标识,那么节点将魄力执行这个关于槽i的命令一次。

在这里插入图片描述

5.5 ASK 错误和MOVED错误的区别

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

6、复制与故障转移

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

6. 1 设置从节点

向一个节点发送命令 cluster replicate node_id 可以让接受命令的节点称为node_id的从节点,并开始对主节点进行复制

一个点称为从节点,并开始复制某个主节点这一信息会通过消息发送给集群中的其他的节点,最终集群中的所有节点都会知道某个从节点正在复制某个主节点

6. 2 故障检测

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

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

6. 3 故障转移

当一个从节点发现正在复制的主节点进入了已下线的状态的时候,从节点就要对下线的主节点进行故障转移

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

6. 4 选举新的主节点(Raft算法 和 选举领头 Sequtinel类似)

  • 集群的配置纪元是一个自增计数器,它的初始值为0
  • 当集群里的某个节点开始一次故障转移操作的时候,集群配置纪元的值会增加1
  • 对个每个配置纪元,集群里负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将或得主节点的的投票
  • 当从节点发现自己正在复制的主节点进入了已下线的状态的时候,从节点会向集群广播一条
    CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所哟偶收到这条消息,并且有投票权的主机诶单向这个从节点投票
  • 如果一个主节点具有投票权,并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点称为新的主节点
  • 每个参与选举的从节点都会接受CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到了多少条这种消息来统计自己或得了多少主节点的支持。
  • 如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N / 2 + 1张支持票的时候,这个从节点就会当选为新的主节点
  • 因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所有如果有N个主节点进行投票,那么超过半数的从节点只会有一个,这确保了新的主节点只会有一个。
  • 如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并在此进行选举,直到选出新的主节点为止。

7、 节点通信机制

7.1 两个端口

在哨兵系统中,节点分为数据节点和哨兵节点:前者存储数据,后者实现额外的控制功能,在急群众,没有数据节点与非数据节点之分,所有的节点都存储数据,也都参与集群的维护,为此,集群中的每个节点,都提供了两个TCP端口

  • 普通端口 : 用于为客户端提供服务,与单机节点类似,但是在节点之间的数据迁移的时候也会使用
  • 集群端口:端口号是普通端口 + 10000 ,集群端口只用于节点之间的通信,如搭建集群,增减节点 故障转移等操作时的节点通信。

7 .2 Gossip 协议

节点之间的通信,按照通信协议可以分为几种类型:单对单、广播、 Gossip协议等。重点是广播与Gossip协议之间的对比

  • 广播是指向向集群内的所有节点发送消息。优点是集群内所有节点获得集群的消息是一致的,缺点是每条消息都要发送给所有的节点,CPU带宽等消耗比较大。
  • Gossip协议的特点是:在节点数量有限的网络当中,每个节点都"随机的与部分节点通信",不是真正的随机,而是根据特定的规则选择通信的节点。经过一番杂乱无章的通信,每个节点的状态很快就会达到一致。
    Gossip协议的有点是负载比较低,去中心化,容错率高,缺点是集群的收敛速度慢

7 .3 消息类型

集群中的节点采用10秒一次的定时任务进行通信相关的工作:判断是否需要发送消息及消息类型、确定接受节点发送消息等。如果集群状态发生了变化,如增减节点,槽状态变更,通过节点之间的通信,所有节点会很快得知整个集群的状态。

  • Meet消息:在节点握手阶段,当节点收到客户端CLUSTER MEET命令的时候,向新加入的节点发送MEET消息,请求新节点加入到当前集群;新节点收到MEET消息后会回复一个PONG消息
  • PING消息:集群里每个节点每秒钟会选择部分节点发送PING消息,接受者收到消息后会回复一个PONG消息,PING消息的内容是自身节点和其他节点的状态信息,作用是彼此交换信息,以及检测节点是否在线,PING消息使用Gossip协议发送,接受节点的选择兼顾了收敛速度和带宽成本,具体规则如下(1)随机找5个节点,在其中选择最久没有通信的一个节点
    (2)扫描节点列表,选择最近一次收到PONG消息大于cluster_node_timeout/ 2的所有节点,防止这些节点长时间未更新
  • PONG消息:PONG消息封装了自身状态数据,可以分为两种:第一种是在接收到MEET / PING消息后恢复PONG消息。第二种是指节点向集群广播PONG消息,这样其他节点可以获知该节点的最新消息**,例如故障恢复后新的主节点会广播PONG消息**
  • FAIL消息:当一个主节点判断另一个主节点进入FAIL状态的时候,会向集群广播这一FAIL消息;接收节点会将这一FAIL消息保存起来,便于后续的判断。
  • PUBLISH消息:节点收到publish命令之后,会先执行该命令,然后向集群广播这一消息,接受节点也会执行该PUBLISH命令。

参考 :
《Redis设计与实现》
Redis(9)——史上最强【集群】入门实践教程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值