redis集群是redis提供分布式数据库方案,
集群通过分片(Sharding)来进行数据共享,并提供复制和故障转移功能。
节点
redis集群通常由多个节点(node)组成,在开始每个node 都是相互独立的。
要组建成真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。
命令
cluster meet <ip> <port>
向一个node 发送命令 cluster meet,让节点与ip/port所指定的节点 进行握手(handshake),
当握手成功时,node节点就会将ip/port所指定的节点添加到node节点当前所在的集群中。
举例:
三个节点
- 127.0.0.1:7000
- 127.0.0.1:7001
- 127.0.0.1:7002
启动节点
一个节点就是一个运行在集群模式下的redis服务器,redis会在启动时 根据 cluster-enabled 配置选项
确定是否来开启集群模式。
运行在集群模式的redis服务器,会继续使用所有在单机模式中使用的服务器组件。
cluster meet 命令的实现
通过向节点A发送Cluster MEET 命令,客户端可以让接收命令的节点A将另一个节点B添加到节点A当前所在的集群里面:
很像TCP/IP 三次握手
槽指派
redis集群通过分片的方式来保存数据库中的键值对:
- 集群的整个数据库被分为16384个槽(slot)
- 数据库中的每个键都属于这16384个槽的其中一个
- 集群中的每个节点可以处理0或最多16384个槽
当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok),
如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。
上面使用cluster meet 命令将7000 7001 7002 三个节点连接到同一个集群里,
不过这个集群目前仍然处于下线状态,因为集群中的三个节点都没有处理任何槽
命令:cluster info
命令:cluster addslots
添加一个或多个槽指派(assign)给某节点 来负责
将槽0--槽5000 指派给节点7000 负责
传播节点的槽指派信息
一个节点除了记录自己需处理的槽记录,还会将自己的槽信息,发送给集群中的其他节点;
告诉其他节点,自己所负责的哪些模块。
每个节点都会通告自己的槽记录给其他任何节点,而每个节点也会记录自己的槽记录;
因此,集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点。
在集群中执行命令
在对数据库中的16384个槽都进行了指派之后,集群就会进入上线状态,
这时客户端就可以向集群中的节点发送数据命令。
当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽
并检查这个槽是否指派给了自己:
- 如果键所在的槽正好指派了当前节点,
- 如果键所在的槽并没有指派给当前节点,
- 那么节点会向客户端返回一个moved 错误,
- 指引客户端转向至正确的节点,并再次发送之前想要执行的命令。
流程图
计算键属于哪个槽
def slot_number(key):
return CRC16(key) & 16383
crc16 用于计算键KEY的crc-16校验和,而 & 16383 则用于计算出一个介于0至 16383 之间的整数作为KEY的槽号。
查看键的槽号
命令:cluster keyslot <key>
MOVED错误
moved 错误的格式为:
MOVED <slot> <ip>:<port>
slot 为键所在的槽,而ip/port则是负责处理槽slot的节点的IP地址和端口号。
- 之所以能指定具体的ip,端口号。是因为每个节点都知晓每个槽由哪个节点负责
一个集群客户端通常会与集群中的多个节点创建套接字连接,而所谓的节点转向实际上就是换一个套接字来发送命令
如果客户端尚未与想要转向的节点创建套接字连接,那么客户端会先根据MOVED错误提供的IP地址和端口号来连接节点,
然后再进行转向。
重新分片
redis 集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点)
并且相关槽所属的键值对 也会从源节点被移动到目标节点。
重新分片操作可以在线(online)进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
重新分片的实现原理
重新分片由redis 集群管理软件 redis-trib 负责执行。
redis-trib 对集群的单个槽进行重新分片的步骤如下:
- redis-trib 对目标节点发送 cluster setslot <slot> importing <sorce_id> 命令
- 让目标节点准备好从源节点导入(import)属于槽slot的键值对
- redis-trib 对源节点发送 cluster setslot <slot> migrating <target_id>命令
- 让源节点准备好将属于槽slot 的键值对槽slot的键值对迁移(migrate)
- redis-trib 向源节点发送 cluster getkeysinslot <slot> <count>命令
- 获取最多count 个 属于槽slot的键值对的键名(key name)
- 对于步骤3获取的每个键名,redis-trib都向源节点 发送 migrate <target_ip> <tartget_port> <key_name> 0 <timeout> 命令。
- 重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot 的键值对都被迁移到目标节点为止。
- redis-trib 向集群中的任意一个节点发送 cluster setslot <slot> node <target_id> 命令,
- 将槽slot指派给目标节点,这一信息通过消息发送至整个集群
- 最终集群中的所有节点都会知道槽slot 已经指派给了目标节点。
整个过程:
ASK 错误
当客户端向源节点发送一个域数据库键有关的命令,
并且命令要处理的数据库键值对恰好就属于正在被迁移的槽时:
- 源节点会先在自己的数据库里查找指定的键,如果找到的话,就直接执行客户端发送的命令
- 如果源节点没有找到指定的键,那么这个键有可能已经被迁移到了目标节点
-
- 源节点将向客户端返回一个ASK错误
-
- 指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。
过程
例子:
槽16198 中的love 键 从7002 迁移到7003
ASKING标志位
ASKING命令唯一要做的就是打开发送该命令的客户端的REDIS_ASKING标识。
一般情况下:
- 如客户端向节点发送一个关于槽i的命令,而槽i又没有指派给这个节点,
-
- 如果显示节点正在导入槽i,并且发送命令的客户端带有REDIS_ASKING标识,
-
- 那么节点将破例执行这个关于槽i的命令一次
-
- 虽然槽i 还没有正式分配给节点,但只要有ASKING标识也就破例执行一次。
判断过程
注意:客户端的REDIS_ASKING标识是一个一次性标识;
例子:
如果我们在成功执行GET命令之后,再次向节点7003发送GET命令,
那么第二次发送的GET没拿过来将执行失败,因为这时客户端的REDIS_ASKING标识已经被移除:
ASK错误和MOVED错误的区别
- MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点
-
- 在客户端收到关于槽i 的moved 错误之后,
-
- 客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至moved错误所指向的节点。
- 而ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:
-
- 客户端只是临时将关于槽i的命令请求,发送至ASK 指引的 节点;
- 下次发送还是发送到当前 节点,直到再收到ASK 错误,然后再指引到 新节点。
复制与故障转移
Redis集群中的节点为主节点和从节点,其中主节点用于处理槽,而从节点则用于复制某个主节点,
并在被复制的主节点下线时,代替下线主节点继续处理命令请求。
例子:
如果这时候,节点7000 进入下线状态,那么集群中仍在正常运作的几个主节点将在节点7000 的两个从节点
从7004 和7005 中选出一个节点作为新的主节点,这个新的主节点将接管原来节点7000 负责处理的槽,并
继续处理客户端发送的命令请求。
设置从节点
发送命令:
CLUSTER REPLICATE <node_id>
可以让接收命令的节点成为node_id 所指定节点的从节点,并开始对主节点进行复制:
故障检测
集群中的每个节点都会定期地向集群中的其他节点发送ping消息,以此来检测对方是否在线,
如果接收ping消息的节点每页在规定的时间内,发送ping消息的节点回复,那么源节点则将目标节点标记为疑似下线(probale fail,PFAIL)
集群中的各个节点会通过相互发送消息的方式来交换集群中各个节点的状态信息。
若果一个集群里面,半数以上的主节点 都将某个主节点 报告为疑似下线,那么这个主节点将被标记为已下线(FAIL).
将主节点标记为已下线的节点会向集群广播一条关于该主节点的FAIL消息,所有收到这条FAIL消息的节点都会立即将
该主节点标记为已下线。
故障转移
当一个从节点发现自己正在复制的主节点进入了已下线状态时,
从节点将开始对下线主节点进行故障转移,故障转移步骤:
- 下线主节点的所有从节点里面,会有一个被选中作为新的主
- 被选中的从节点会执行SLAVEOF no one ,成为 新的主
- 新主节点,处理原主的所有槽
- 新主节点向集群广播一条pong消息,让集群中的其他节点立即知道此节点已经变成主节点
- 新主节点开始接受和自己负责处理的槽有关的命令请求,故障转移完成。
选举新的主节点
集群选举新的主节点的方法:
- 集群的配置纪元是一个自增计数器,它的初始值为0
- 当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会增一
- 对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,
- 而第一个向主节点要求投票的从节点将获得主节点的投票
- 当从节点发现自己的主节点下线,
- 从节点向集群广播一条消息,要求收到消息的主节点,投票给自己。
-
- CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息
- 如果主节点有投票权(它正在处理槽),并且尚未投票。
- 将向要求投票的从库,返回一条信息
-
- CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
- 表示这个主节点支持该从节点成为新的主节点
- 从节点通过接收消息的个数,统计自己获得了多少主节点的支持
- CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK
- 如集群中有N个具有投票权的主节点,那么从节点收集到大于等于N/2+1张支持票时,从节点当选为新的主节点。
- 因为在每个配置纪元里,主节点只能投一次票,所以得到N/2+1的从节点只会有一个。
- 如果在一个配置纪元里没有从节点收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止
注意:选举新主的方法与sentinel的方法非常类似,都是基于Raft算法的领头选举方法来实现的。
消息
集群中的各节点通过发送和接收消息来进行通信。
节点发送的消息主要有以下五种:
- MEET消息
-
- 当发送者接到客户端发送的CLUSTER MEET命令时
- 发送者会向接收者发送MEET消息,请求接收者加入到发送者当前所处的集群里面。
- PING消息
-
- 集群里面每个节点默认每秒会从已知节点列表随机选出5个节点,
- 对这5个节点中最长时间没有发送过PING消息的节点发送PING消息,以此来检测被选中的节点是否在线
- 此外,PONG消息会对PING消息产生影响
-
- 如节点A最后一次收到节点B发送的PONG消息的时间,
- 距离当前时间超过了节点A的cluser-node-timeout的一半
- 则A 向B 发送PING 消息。
- 防止节点A 长时间没有随机选中B,导致节点B的信息更新滞后
- PONG消息
-
- 当接受者收到MEET消息或者PING消息,回一条PONG消息以确认收到这条消息。
- 此外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点立即刷新关于自身的认识。
-
- 如,故障转移操作成功之后,新的节点会向集群广播一条PONG消息,
- 以此来让集群中的其他节点立即知道这个节点已经变成了主节点。
- FAIL消息
-
- 当主节点A判断主节点B已经进入FAIL状态时,
- 节点A向集群广播一条关于B的FAIL消息,
- 所有收到这条消息的节点立即向B 节点标记为已下线
- PUBLISH消息
-
- 当节点接收到一个PUBLISH命令时,节点会执行这个命令,
- 并向集群广播一条PUBLISH消息,
- 所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。
例子:
发送PING 消息和返回PONG消息的例子,假设在一个包含A,B,C,D,E,F六个节点的集群里:
- 节点A向节点D发送PING消息,并且消息里面包含了节点B和节点C的信息
-
- 当节点D收到这个PING消息,它将更新自己对节点B和节点C的认识
- 之后,节点D将向节点A放回一条PONG消息,并且消息里面包含了节点E和节点F的消息,
-
- 当节点A收到这条PONG消息时,它将更新自己对节点E和节点F的认识。
整个过程
FAIL消息实现的过程
PUBLISH消息实现过程
总结:
- 节点通过握手来将其他节点添加到自己所处的集群当中。
- 集群中的16384个槽可以分别指派给集群中的各个节点,
-
- 每个节点都会记录哪些槽指派给了自己
- 而哪些槽又被指派给了其他节点
- 节点在接到一个命令请求时,会先检查这个命令请求要处理的键所在的槽是否自己负责
-
- 如果不是,节点将向客户端返回MOVED错误,
- MOVED错误携带的信息,可以指引客户端转向正确的节点
- 对Redis集群的重新分片工作是由redis-trib负责执行的。
-
- 重新分片的关键是将属于某个槽的所有键值对从一个节点转移到另一个节点
- 如果节点A正在迁移槽i至B节点,
-
- 那么当节点A没能在自己的数据库中找到命令指定的数据库键时,
-
- 节点A会向客户端返回一个ASK错误,
- 指引客户端到节点B继续查找指定的数据库键
- MOVED错误表示槽的负责权已经从一个节点转移到了另一个节点,
-
- ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施
- 集群里的从节点用于复制主节点,并在主节点下线时,代替主节点继续处理命令请求
- 集群中的节点通过发送和接收消息来进行通行,常见消息类型:
-
- MEET,PING,PONG,PUBLISH,FAIL