当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理数据库键属于哪个槽,并检查这个槽是否指派给了自己:
- 如果键所在的槽正好指派给了当前的节点,那么节点直接执行这个命令
- 如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向至正确的节点,并再次发送之前想要执行的命令。
假设现在有节点100和节点200,节点100负责前8192个槽,节点200负责后8192个槽 假设键msg 处在槽8192 也就是节点200的第一个槽里面,键name处在槽0号位置 127.0.0.1:100 >set name 'zhangsan' ok 127.0.0.1:100 >set msg 'wudi' ->Redirected to slot [8192] located at 127.0.0.1:200
计算键属于那个槽
//节点使用这个算法来判断键Key输入哪个槽 CRC16(key) & 16383
- CRC16(Key)用于计算key的CRC-16的校验和,而&16384语句则用于计算出一个介于0至16384之间的整数作为键key的槽号。
//查看键属于哪个槽 CLUSTER KEYSLOT<key> //上面命令的伪代码实现 def CLUSTER_KEYSLOT(key): #计算槽号 slot = slot_number(key) #将槽号返回给客户端 reply_client(slot)
判断槽是否由当前节点负责处理
当节点计算出键所在的槽i之后,节点就会检查自己在clusterState.slots数组之中的选项i,判断键所在的槽是否由自己负责:
- 如果clusterState.slots[i] = clusterState.myself ,那么说明槽i由当前节点负责,节点可以执行客户端发送的命令
- 不等于则说明槽i并非由当前节点负责,节点会根据clusterState.slots[i]指向的clusterNode结构所记录的节点IP和端口号,向客户端返回MOVED错误,指引客户端转向至正在处理槽 i 的节点。
MOVED错误
//MOVED错误的格式为 MOVED <slot> <ip> : <port>
- slot为键所在的槽,而ip和port则是负责处理槽slot的节点和ip地址端口号
- 当客户端接收到节点返回的MOVED错误时,客户端会根据MOVED错误提供的IP地址和端口号,转向至负责槽slot的节点,并向该节点重新发送之前想要执行的命令。
- 一个集群的客户端通常会与集群中的多个节点创建套接字连接,所谓的节点转向实际上就是换一个套接字来发送命令。如果客户端尚未与想要转向的节点创建套接字连接,那么客户端会先根据MOVED错误提供的I地址和端口号来连接节点,然后进行转向。
节点数据库的实现
节点和单机服务在数据库方面的区别是:节点只能使用0号数据库,而单机Redis服务器则没有这限制(16个数据库)
节点数据库除了将键值对保存在数据库里面之外,节点还会用clusterState结构中的slots_to_keys跳跃表来保存槽和键之间的关系。
tupedof struct clusterState{ zskiplist *slots_to_keys; }
slots_to_keys 跳跃表每个节点的分值都是一个槽号,而每个节点的成员都是一个数据库键:
- 每当节点往数据库中添加一个新的键值对时,节点就会将这个键以及键的槽号关联到slots_to_keys跳跃表。
- 当节点删除数据库中的某个键值对时,节点就会在slots_to_keys跳跃表接触被删除键与槽号的关联。
通过在slots_to_key 跳跃表中记录各个数据库键所属的槽,节点可以很方便地对属于某个或某些槽的所有数据库键进行批量操作
// 命令可以返回最多count个属于槽slot的数据库键,这个命令就是通过遍历slots_to_keys跳表来实现的 CLUSTER GETKEYSINSLOT <slot><count>
重新分片
Redis集群的重新分配操作可以将任何数量已经指派给某个节点的槽改为指派给另一个节点,并且相关槽所属的键值对也会从源节点移动到目标节点。重新分配操作可以在线进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点可以继续处理命令请求。
重新分片的实现原理
Redis集群的重新分片操作是由Redis的集群管理软件redis-trib负责执行的,Redis提供了进行重新分片所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。
redis-trib对集群的单个槽slot进行重新分片的步骤如下:
1.redis-trib对目标节点发送CLUSTER SETSLOT <SLOT> IMPORTING <source_id>,让目标节点准备好从源节点导入属于槽slot的键值对。
2.redis-trib对源节点发送CLUSTER SETSLOT <SLOT> MIGRATING <target_id> 命令,让源节点准备好将属于槽slot的键值对迁移至目标节点。
3.redis-trib对源节点发送CLUSTER GETKEYSINSLOT <SLOT><COUNT> 命令,获得最多count个属于槽slot的键值对的键名。
4.对于步骤3获得的每个键名,redis-trib都会向源节点发送一个MIGRATE<target_ip><target_port><key_name> 0 <timeout>命令,将被选择的键原子地从源节点迁移至目标节点。
5.重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止。
6.redis-trib 向集群中的任意一个目标节点发送CLUSTER SETSLOT <slot> NODE <targer_id> 命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽slot已经指派给了目标节点。