Redis设计与实现-13.集群(1)
Redis集群是Redis提供的分布式数据库方案,集群通过分片来进行数据共享,并提供复制和故障转移。
节点
一个Redis集群通常由多个节点组成,每个节点互相关联,构成一个多个节点组成的集群。可以通过CLUSTER MEET命令完成连接各个节点的工作。
CLUSTER MEET <ip> <port>
假设有三个独立的节点7000,7001,7002,首先连接7000客户端,并且通过命令查看集群节点信息,发现只有一个节点7000
$redis-cli -c -p 7000 127.0.0.1:7000> CLUSTER NODES asdxcv231fga182esadnash123asds989csa :0 myself,master - 0 0 0 connected
连接其他两个节点
127.0.0.1:7000> CLUSTER MEET 127.0.0.1 7001 127.0.0.1:7000> CLUSTER MEET 127.0.0.1 7002
查看集群信息
127.0.0.1:7000> CLUSTER NODES asdxcv231fga182esadnash123asds989csa 127.0.0.1:7001, master - 0 1388204848376 0 connected asd18783cv231fga182hgdnash3asd9csaq 127.0.0.1:7002, master - 0 1388204848621 0 connected 71vbnik8iopj9azxcj4234iyhmqd552uoipol :0 myself,master - 0 0 0 connected
启动节点
集群下的每个redis节点服务器启动,首先会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。如果yes,表示开启集群模式,如果no表示开启单机Redis服务器。
即使是身为redis集群的节点,也会按照单机模式中使用的相关组件功能,比如定时定期函数以及任务,以及RDB和AOF持久化,发布订阅等。
槽
Redis通过分片的方式保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于其中一个槽,集群中的每个节点可以处理0~16384个槽。当数据库中的16384个槽都有节点在处理时,集群处于上线状态,相反的,如果数据库中有任何一个槽没有得到处理,集群就处于下线状态。
指派
可以通过CLUSTER ADDSLOTS命令进行槽指派。
127.0.0.1:7000>CLUSTER ADDSLOTS 0 1 2 3 4 ... 5000 ok 127.0.0.1:7000> CLUSTER NODES asdxcv231fga182esadnash123asds989csa 127.0.0.1:7001, master - 0 1388204848376 0 connected asd18783cv231fga182hgdnash3asd9csaq 127.0.0.1:7002, master - 0 1388204848621 0 connected 71vbnik8iopj9azxcj4234iyhmqd552uoipol :0 myself,master - 0 0 0 connected 0-5000
以此类推,将16384个槽分别指派给三个服务器,查看集群状态
127.0.0.1:7000> CLUSTER INFO cluster_state:OK cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_konwn_nodes:3 cluster_size:3 cluster_current_epoch:0 cluster_stats_message_sent:3699 cluster_stats_message_received:2617
槽指派信息记录
clusterNode结构的slots属性和numslot属性记录了几点负责处理哪些槽。
其中slots属性是二进制数组,数组长度为16384/8=2048个字节,共包含16384个二进制位。每个二进制位都代表一个槽位置,根据对应索引上的二进制为的值来判断节点是否处理该槽(0表示不处理,1表示处理)。
因为取出以及设置slots数组中的任意一个二进制的值时间复杂度为O(1),所以对于一个给定节点的slots数组来说,检查处理某个槽和指派某个槽的操作时间复杂度都为O(1)。
至于numslot属性则记录负责处理的槽的数量
传播节点的槽信息
一个节点除了会将自己负责处理的槽记录在clusterNode结构的slots属性和numslots属性外,还会将自己的slots数组通过消息发送给集群中其他节点,来告诉其他节点目前自己处理那些槽。
记录集群中所有槽信息
Redis集群通过clusterState结构中的slots属性记录槽指派信息。如图所示
clusterState.slots数组记录了集群中所有槽的信息。同样使用clusterNode结构中的slots数组来记录单个的也是有必要的:
- 程序需要将某个节点的槽指派信息通过消息发送给其他节点时候,只需要将对应节点的slots数组发出去就可以了
- 如果redis不使用clusterNode.slots数组,而单独使用clusterState.slots数组的话,那么每次都要将节点A的槽指派信息传给其他节点的话,程序必须遍历整个clusterState.slots数组。
集群中的命令执行
槽指派之后集群就进入上线状态,客户端就可以向集群发送数据命令了。具体流程如下图:
计算键属于哪个槽
def slot_number(key): return CRC16(key)&16383
节点使用上面的算法计算键属于哪个槽
CRC16(key)语句用于计算key的CRC-16校验和,而&16383则是用于计算出一个位于0和16383之间的整数作为键key的槽号。
判断槽是否由当前节点处理
当节点计算出键的所属槽之后,节点会检查自己的clusterState.slots数组中的项i,判断键所在的槽是否由自己负责:
- 如果clusterState.slots[i]等于clusterState.myself,那么说明槽i由当前节点负责,节点可以执行客户端发送的命令。
- 反之,如果不相等,那么即诶单会根据clusterState.slots[i]指向的clusterNode结构锁记录的节点IP和端口号,向客户端返回MOVED错误,指引客户端转向至正在处理槽i的节点。
MOVED错误信息
如果请求的Redis节点不处理当前槽,那么会返回moved错误信息,但是集群模式下redis客户端在接受到MOVED错误的时候,并不会打印出MOVED错误,而是自行进行转点,打印出转向信息。所以我们是看不到MOVED错误信息的。
但是当我们是单机模式下的redis客户端,就会打印出MOVED错误,原因是单机模式下的redis客户端不清楚MOVED错误的作用,所以就会打印出MOVED错误信息。
节点数据库
我们都直到,redis服务器默认会有16个数据库,单机模式下我们可以使用任意一个数据库,但是集群节点中,我们只可以使用0号数据库。
节点存储数据的方式以及键过期的方式和单机数据库是一样的,只是我们的键值对除了保存在数据库中之外,节点还会用clusterState.slot_to_key属性来保存槽与键之间的关系,而slot_to_key使用跳跃表来实现。
重新分片
redis集群的重新分片操作可以将任意数量已经指派给某个节点的槽改为指派给另外一个新的节点,并且相关槽所属的键值对也会从源节点被移动到目标节点。
重新分片可以在线操作,不需要下线节点,任何节点都可以正常工作。具体流程如下:
ASK错误
在进行重新分片期间,源节点和目标节点迁移的过程中,会出现请求槽中一部分键已经迁移成功,一部分还未迁移的情况。处理这种特殊命令的流程如下:
ASK错误和MOVED错误一样,在集群模式下,客户端不会打印ASK错误,而是直接进行转向动作,但是单机模式下会打印出ASK错误。