在集群中执行命令
前面已经说过,集群对于键值对的处理是分槽的,然后将槽分给集群里面的各个结点,必须所有槽都分配了,集群才能进入上线状态,并对外服务
下面就来看看在集群中是怎么执行客户端的命令的(客户端肯定得要连接一个服务器,发送命令就是发送到服务器,但至于是该服务器的集群中哪个服务器处理,就要看槽是分配给谁的啦,然后槽分配给谁也涉及到一个权重问题,也就是负载均衡)
当客户端向集群结点发送与数据库命令键有关的命令时,接受命令的结点会计算出命令会计算出要处理的数据库键属于哪一个槽,并要检查这个槽是否指派给了自己
那么就会产生下列两种情况
- 如果键所在的槽正好就指派给了当前结点,那么当前结点就会直接执行这个命令
- 如果键所在的槽并没有指派给当前结点,那么结点会向客户端返回一个MOVER错误,并且指引客户端转向至正确的结点,客户端再次发送之前想要执行的命令
计算键是属于哪个槽
节点会使用以下的算法来计算给定键Key是属于哪个槽的
def slot_number(key):
return CRC16(key) & 16383
计算key的CRC-16校验和,然后通过与16383的与运算,得出一个比16383小的值(16383对应的是槽位的数量,不能超过这个值)
判断槽是否由当前节点负责处理
当计算出槽值之后,节点就会检查自己在clusterState.slots数组(前面提到过该数组存储集群中所有节点负责处理的槽)对应的索引对应的值是否为自己(ClusterNode)
- 如果clusterState.slots[i]等于clusterState.myself,那么说明槽i是由当前节点负责,节点可以执行客户端的命令
- 如果不等于,那么就说明不是由当前节点负责,节点会根据clusterStats.slots[i]指向的clusterNode结构所记录的节点IP和端口号,向客户端发送一个MOVED错误,指向客户端转向至正确的节点
MOVED错误
下面,我们看看这个MOVED错误是怎么生成的
当节点发现键所在的槽并非由自己负责处理的时候,节点就会向客户端返回一个MOVED错误,指向客户端转向至正在负责槽的节点
MOVED错误格式为
MOVED <slot> <ip>:<port> //slot表示键的所在槽 ip和port是正确的节点
一个集群客户端(连接上集群上某个节点的客户端)通常会与集群中的多个节点建立套接字(套接字其实就是域名加端口)连接
- 如果前面未与转向的节点进行连接,则要进行套接字连接,然后再进行转向
- 如果已经连接过,直接换一个套接字去发送命令
注意:MOVED错误会被客户端隐藏起来,即不会被打印出来,而是自动进行转向,所以是看不见MOVED错误的
节点数据库的形成
集群节点保存键值对以及键值对过期时间的方式,跟前面讲过的Redis单机服务器是一样的,以及键对过期时间的方式也是完全相同的。
但这里要注意的是,节点数据库只可以使用0号数据库(Redis有16个数据库,0~15),而单机服务器是没有限制的,可以随意切换
另外,键值对除了保存在数据库外,还需要额外去记录键与槽之间的关系(一个节点可能指派了多个槽,那么这些键在槽中怎么存储)
在clusterState结构中会有一个slots_to_keys属性去保存键与槽之间的关系,底层是一个跳跃表
typedef clusterState(
//...
zskiplist *slots_to_keys;
//...
)clusterState;
slots_to_keys属性
底层为跳跃表,每个节点的分值(score)都是一个槽号,而每个节点的成员(member)都是一个数据库键(根据member排序,member不可以重复,但分支score可以重复)
- 当节点往数据库添加一个新的键值对时,节点就会将这个键以及键的槽号关联在slots_to_keys跳跃表
- 当节点删除数据库中的某一个键值对时,节点就会在slots_to_keys跳跃表解除被删除键与槽号的关联
忘记跳跃表,回看前面的zset底层原理(跳跃表)
重新分片
Redis集群的重新分片操作是指:将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所关联的键值对也要从源节点发送到目标节点
这里要注意的是,重新分片的操作是在线上执行的,集群不需要下线也是可以继续处理的,也就是源目标与目标节点都会继续处理重新分片的命令请求
重新分片的实现原理
Redis集群的重新分片是由Redis的集群管理软件(redis-trib)负责执行的,Redis提供了进行重新分片的所有命令,而redis-trib则是通过向源节点和目标节点发送命令进行重新分片的操作
下面就来看看redis-trib对集群的单个槽slot进行重新分片的步骤
-
redis-trib对目标节点发送cluster setslot IMPORTING <source_id>命令,让目标节点准备好从源节点(source_id)导入属于槽slot的键值对
-
redis-trib对源节点发送cluster setslot MIGRATING <target_id>命令,让源节点准备好将属于槽slot的键值对发送给目标节点(前两步都是准备工作)
-
redis-trib向源节点发送**cluster getkeysinslot **命令,获得最多count个属于槽slot键值对的键名(key name)
-
redis-trib遍历第三步发来的所有键名,根据每一个键名去向源节点发送**MIGRATING <target_ip> <target_port> <key_name> 0 **命令,将源节点中所有被选中的键值对从源节点迁移到目标节点
-
重复3和4的步骤,因为每一次都只有count个,所以要重复执行,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止
-
redis-trib向集群中的任意一个节点发送cluster setslot NODE <target_id>命令,告诉这个节点,这个槽归为目标节点管了,这个节点要对应去修改clusterState.slots属性,然后将该消息发送到集群上,也就是这一指派信息会通过消息发送至整个集群,最终整个集群中的所有节点都知道该slot被指派给目标节点了,然后也去修改clusterState.slots属性