集群
在集群中执行命令
MOVED错误。
当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指引客户端转向至正在负责槽的节点,MOVED错误的格式为:
MOVED <slot> <ip>:<port>
其中slot为键所在的槽,而ip和port则是负责处理槽slot的节点的IP地址和端口号。例如错误:
MOVED 10086 127.0.0.1:7002
表示槽10086正由IP地址为127.0.0.1,端口号为7002的节点负责,又例如错误:
MOVED 789 127.0.0.1:7000
表示槽789正由IP地址为127.0.0.1,端口号为7000的节点负责。
当客户端接收到节点返回的MOVED错误时,客户端会根据MOVED错误中提供的IP地址和端口号,转向至负责处理槽slot的节点,并向该节点重新发送之前想要执行的命令。以前面的客户端从节点7000转向至7001的情况作为例子:
127.0.0.1:7000> SET msg "happy new year!"
-> Redirected to slot[6257] located at 127.0.0.1:7001
OK
如图所示展示了客户端向节点7000发送SET命令,并获得MOVED错误的过程
客户端根据MOVED错误,转向至节点7001,并重新发送SET命令的过程
一个集群客户端通常会与集群中的多个节点创建套接字连接,而所谓的节点转向实际上就是换一个套接字来发送命令。如果客户端尚未与想要转向的节点创建套接字连接,那么客户端会先根据MOVED错误提供的IP地址和端口号来连接节点,然后再进行转向
被隐藏的MVOED错误
集群模式的redis-cli客户端在接收到MOVED错误时,并不会打印出MOVED错误,而是根据MOVED错误自动进行节点转向,并打印出转向信息,所以是看不见节点返回的MOVED错误的
redis-cli -c -p 7000 #集群模式
127.0.0.1:7000> SET msg "happy new year!"
-> Redirected to slot[6257] located at 127.0.0.1:7001
OK
但是如果,使用单机(stand alone)模式的redis-cli客户端,再次向节点7000发送相同的命令,那么MOVED错误就会被打印出来:
redis-cli -p 7000 # 单机模式
127.0.0.1:7000> SET msg "happy new year!"
(error) MOVED 6257 127.0.0.1:7001
这是因为单机模式的redis-cli客户端不清除MOVED错误的作用,所以它只会直接将MOVED错误直接打印出来,而不会进行自动转向。
节点数据库的实现
集群节点保存键值对以及键值对过期时间的方式,与单机模式下保存键值对以及对过期时间的方式完全相同。节点和单机服务器在数据库方面的一个区别是,节点只能使用0号数据库,而单机Redis服务器则没有这一限制。
slots_to_keys跳跃表每个节点的分值(score)都是一个槽号,而每个节点的成员(member)都是一个数据库键:
- 1.每当节点往数据库中添加一个新的键值对时,节点就会将这个键以及键的槽号关联到slots_to_keys跳跃表
- 2.当节点删除数据库中某个键值对时,节点就会在slots_to_keys跳跃表接触被删除键与槽号的关联
例子
- 举个例子。如图展示了节点7000的数据库状态,数据库中包含列表键"lst",哈希键"book",以及字符串键"date",其中键"lst"和键"book"带有过期时间。另外除了将键值对保存在数据库里面之外,节点还会用clusterState结构中的slots_to_keys跳跃表来保存槽和键之间的关系:
typedef struct clusterState {
//...
zskiplist *slot_to_keys;
// ...
}clusterState;
- 举个例子。对于上图所示的数据库,节点7000将创建类似如图所示的slots_to_keys跳跃表:
1.键"book"所在跳跃表节点的分值为1337.0,这表示键"book"所在的槽位1337
2.键"date"所在跳跃表节点的分值位2022.0,这表示键"date"所在的槽为2022
3.键"lst"所在跳跃表节点的分值为3347.0,这表示键"lst"所在的槽为3347
通过在slots_to_keys跳跃表中记录各个数据库键所属的槽,节点可以很方便地对属于某个或某些槽的所有数据库进行批量操作,例如命令CLUSTER GETKEYSINSLOT 命令可以返回最多count个属于槽slot的数据库键,而这个命令就是通过遍历slots_to_keys跳跃表来实现的
重新分片
Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点。重新分片操作可以在线(online)进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。
例子
- 举个例子。对于之前提到的7000、7001、7002三个节点的集群来说,我们可以向这个集群中添加一个IP为127.0.0.1,端口号为7003的节点
redis-cli -c -p 7000
127.0.0.1:7000> CLUSTER MEET 127.0.0.1 7003
OK
可以用CLUSTER NODES进行查看各个节点之前所负责的槽的区间
重新分片的实现原理
Redis集群的重新分片操作是由Redis的集群管理软件redis-trib负责执行的,Redis提供了进行重新分片所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。redis-trib对集群的单个槽slot进行重新分片的步骤如下:
- 1.redis-trib对目标节点发送CLUSTER SETSLOT IMPORTING <source_id>命令,让目标节点准备好从源节点导入(import)属于槽slot的键值对
- 2.redis-trib对源节点发送CLUSTER SETSLOT MIGRATING <target_id>命令,让源节点准备好将属于槽slot的键值对迁移(migrate)至目标节点
- 3.redis-trib向源节点发送CLUSTER GETKEYSINSLOT 命令,获得最多count个属于槽slot的键值对的键名(key name)
- 4.对于步骤3获得的每个键名,redis-trib都向源节点发送一个MIGRATE <target_ip> <target_port> <key_name> - 命令,将被选中的键原子地从源节点迁移至目标节点
- 5.重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止。每次迁移的过程如图所示
- 6.redis-trib向集群中的任意一个节点发送CLUSTER SETSLOT NODE <target_id>命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会直到槽slot已经指派给了目标节点
如果重新分片涉及多个槽,那么redis-trib将对每个给定的槽分别执行上面给出的步骤,流程图如图所示