Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据库共享,并提供复制和故障转移功能。
一、节点
一个Redis集群通常由多个节点(node)组成,在开始的时候,每个节点都是互相独立的,它们都处于一个只包含自己的集群当中,要组件一个真正可工作的集群,我们必须将多个独立的节点连接起来,构成一个包含多个节点的集群。
命令:cluster meet <IP> <PORT>,可以将IP、PORT指定的节点添加到node节点所在的集群中,如:
127.0.0.1:7000> cluster meet 127.0.0.1 7001
可以将节点127.0.0.1:7001添加到节点127.0.0.1:7000所在的集群中。
1、节点启动
一个节点就是一个运行在集群模式下的Redis服务器,Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。
cluster-enabled yes
2、集群数据结构
clusterNode结构保存了一个节点的当前状态,比如节点的创建时间、节点的名字、节点的当前纪元、节点的IP地址和端口号等等。
每个节点都会用一个clusterNode结构来记录自己的状态,并为集群中的所有其他节点(包括主节点和从节点)都创建一个相应的clusterNode结构,以此来记录其他节点的状态。
clusterNode节点的link属性是一个clusterLink结构,该结构保存了连接节点所需的有关信息,比如套接字描述符,输入缓冲区和输出缓冲区。
每个节点都保存着一个clusterState结构,这个结构记录了在当前节点的视角下,集群目前所属的状态,例如集群是在线还是下线,集群包含多少个节点,集群当前的配置纪元等等。
二、槽指派
Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),数据库的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。
当数据库中的这16384个槽都有节点在处理时,集群处于在线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。
通过向节点发送cluster addslots命令,我们可以将一个或多个槽指派(assign)给节点负责:
cluster addslots <slot> [slot ...]
例如:
127.0.0.1:7000> cluster addslots 0 1 2 3 ... 5000
OK
可以将槽0~5000指派给节点127.0.0.1:7000。
cluster addslots命令执行完毕之后,节点会通过发送消息告知集群中的其他节点,自己目前正在负责处理哪些槽。
当数据库中的16384的节点都被指派给相应的节点后,集群进入上线状态。
记录节点的槽指派信息:
1、记录节点的槽指派信息
clusterNode结构的slots属性和numslot属性记录了节点负责处理哪些槽。
2、传播节点的槽指派信息
一个节点除了会将增加负责处理的槽信息记录在clusterNode结构的slots属性和numslots属性之外,它还会将自己的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。
3、记录集群所有槽的指派信息
clusterState结构中的slots数组记录了集群中所有16384个槽的指派信息。
4、CLUSTER ADDSLOTS命令
CLUSTER ADDSLOTS命令接受一个或多个槽作为参数,并将所有输入参数的槽指派给接收该命令的节点负责。如:
CLUSTER ADDSLOTS 1 2
三、在集群中执行命令
在对数据库中的16384个槽都进行了指派之后,集群就会进入上线状态,这是客户端就可以向集群中的节点发送数据命令了。
当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己:
(1)如果键所在的槽正好就指派给了当前的节点,那么节点之间执行这个命令。
(2)如果键所在的槽并没有指派给当前的节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。
MOVED错误:
一个集群客户端通常会与集群中的多个节点创建套接字连接,而所谓的节点转向实际上就是换一个套接字来发送命令。
如果客户端尚未与想要转向的节点创建套接字连接,那么客户端会先根据MOVED错误提供的IP地址及端口号来连接节点,然后再进行转向。
跳跃表:
除了将键值对保存在数据库里面之外,节点还会用clusterState结构中的slot_to_keys跳跃表来保存槽与键之间的关系。
四、重新分片
Redis集群的重新分片操作可以将任意数量已经分派给指定节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动目标节点。
重新分片操作可以在线(online)进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
五、ASK错误
在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。
当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:
(1)源节点会先在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
(2)相反地,如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点上,并再次发送之前想要执行的命令。
六、复制与故障转移
Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。
举例:对于包含7000、7001、7002、7003四个主节点的集群来说,我们可以将7004、7005两个节点添加到集群里面,并将这两个节点设定为节点7000的从节点。如图:
1、设置从节点
向一个节点发送命令:
CLUSTER REPLICATE <node_id>
可以让接收命令的节点成为node_id所指定的节点的从节点,并开始对主节点进行复制。
节点的复制和单机Redis服务器的复制功能使用相同的代码,所以让从节点复制主节点相当于向从节点发送命令
SLAVEOF <master_ip> <master_port>
一个节点成为从节点,并开始复制某个主节点这一信息会通过消息发送给集群中的其他节点,最终集群中的所有节点都会知道某个节点正在复制某个主节点。
2、故障检测
集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定时间内,向发送PING消息的节点回复PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线(probable fail, PFAIL)。
集群中的各个节点会通过相互发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是处于在线状态、疑似下线状态(PFAIL)、还是已下线状态(FAIL)。
如果在一个集群里,半数以上处理槽的主节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线(FAIL),将主节点x标记为已下线的节点会向集群广播一条关于主节点x的FAIL信息,所有收到这条FAIL信息的节点都会将主节点x标记为已下线。
3、故障转移
当一个节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对已下线主节点进行故障转移,以下是故障转移的执行步骤:
(1)复制下线主节点的所有从节点里面,会有一个从节点被选中。
(2)被选中的从节点会执行SLAVEOF NO ONE 命令,成为新的主节点。
(3)新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。
(4)新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。
(5)新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。