在一个如以下列表三主三从的Redis Cluster中,集群中每个节点会在内存中保存一张关于集群信息的ClusterState和ClusterNode结构,如下所示。
主机名 | IP地址 | 角色 |
redis-master-01 | 172.16.101.54 | Master |
redis-master-02 | 172.16.101.55 | Master |
redis-master-03 | 172.16.101.56 | Master |
redis-slave-01 | 172.16.101.58 | Slave(Master:172.16.101.54) |
redis-slave-02 | 172.16.101.59 | Slave(Master:172.16.101.55) |
redis-slave-03 | 172.16.101.60 | Slave(Master:172.16.101.56) |
一. 集群节点
1. 节点启动初始化
一个启用集群功能的节点在默认状态下,没有加入任何集群,而且认为节点自己就是集群中的master,在启动节点时会看到如下log
25630:M 12 Apr 00:59:59.477 * No cluster configuration found, I'm 89a83af689b194b18c7f5c0dae105c329a6a831f
18608:M 13 Apr 23:41:18.607 * No cluster configuration found, I'm 2840512295a3e863a4b817510323565fa5bc78e3
同时,通过集群cluster nodes命令可以看到各个节点的状态均为master节点
redis-master-01:6379> cluster nodes dc4ee5e34a946ae7a20b58c023ce93b2775ac30d :6379 myself,master - 0 0 0 connected
redis-master-02:6379> cluster nodes 2840512295a3e863a4b817510323565fa5bc78e3 :6379 myself,master - 0 0 0 connected
各个集群节点在内存中初始化一个ClusterState内存结构,并将自己的节点信息添加到字典nodes属性中。
2. 节点与其他节点消息交互
一个集群节点启动成功后,可以通过“cluster meet ip port”命令与其他节点进行信息交互,邀请其他节点加入到自己所在的集群中,
redis-master-01:6379> cluster meet 172.16.101.55 6379 OK
其meet过程如下:
1) 节点redis-master-01通过发送"meet"消息与节点redis-master-02进行握手(handshake),同时将节点redis-master-02信息添加到内存结构ClusterState中的字典nodes属性中。
#define CLUSTERMSG_TYPE_MEET 2 /* Meet "let's join" message */
2) 节点redis-master-02收到节点redis-master-01的"meet"消息后,节点redis-master-02也会将节点redis-master-01添加到内存结构ClusterState中的字典nodes属性中,并向节点redis-master-01返回一条“pong”消息。
#define CLUSTERMSG_TYPE_PONG 1 /* Pong (reply to Ping) */
3) 节点redis-master-01收到节点redis-master-02的“pong”消息后,认为节点redis-master-02已经收到自己的"meet"消息,再次向节点redis-master-02发送一条“ping”消息.
#define CLUSTERMSG_TYPE_PING 0 /* Ping */
节点redis-master-02收到“ping”消息后,认为节点redis-master-01已经收到自己的"pong"回复,节点握手(handshake)完成。
4) 集群中其他节点通过gossip消息得知新节点加入集群后,使用同样的方式与新节点握手(handshake),并将新节点添加到内存结构ClusterState中的字典nodes属性中,最终,新节点会被集群中所有其他节点熟知。
redis-master-01:6379> cluster nodes dc4ee5e34a946ae7a20b58c023ce93b2775ac30d 172.16.101.54:6379 myself,master - 0 0 0 connected dbe28b335ba69c551029902eb83bdef92431300f 172.16.101.56:6379 master - 0 1586795007334 2 connected 2840512295a3e863a4b817510323565fa5bc78e3 172.16.101.55:6379 master - 0 1586795006331 1 connected
二. 槽指派
1.槽指派过程
集群通过槽(slot)的方式保存键值对,一个数据库被分成16384个slot,注意范围是0~16383,集群中的每个节点负责处理其中部分slot,每个键值对都存入对应的slot中,这也说明了如果某个节点宕机,该节点上对应的slot也会下线,集群将处于下线状态,slot个数定义如下。
#define CLUSTER_SLOTS 16384
即使已经有节点加入到集群中,但是集群仍然不可用,因为并没有定义集群中各个节点应该存放多少个slot。
redis-master-01:6379> cluster info cluster_state:fail cluster_slots_assigned:0 cluster_slots_ok:0 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:3 cluster_size:0 cluster_current_epoch:5 cluster_my_epoch:0 cluster_stats_messages_sent:5350 cluster_stats_messages_received:5350
通过如下方式将16384分slot分别指派到各个节点中
将0~5000的slot分配到redis-master-01
[redis@redis-master-01 ~]$ redis-cli -h redis-master-01 cluster addslots {0..5000}
将5001~10000的slot分配到redis-master-02
[redis@redis-master-01 ~]$ redis-cli -h redis-master-02 cluster addslots {5001..10000}
将10001~16383的slot分配到redis-master-03
[redis@redis-master-01 ~]$ redis-cli -h redis-master-03 cluster addslots {10001..16383}
这里需要注意:不能在redis-cli交互式命令中执行,否则会报错
redis-master-01:6379> cluster addslots {0..5000} (error) ERR Invalid or out of range slot
slot分配完成之后,再次查看cluster状态,集群功能已经上线
redis-master-01:6379> cluster info cluster_state:ok cluster_slots_assigned:16384 cluster_slots_ok:16384 cluster_slots_pfail:0 cluster_slots_fail:0 cluster_known_nodes:6 cluster_size:3 cluster_current_epoch:5 cluster_my_epoch:0 cluster_stats_messages_sent:6950 cluster_stats_messages_received:6950
每个节点的slot信息保存在每个节点内存结构ClusterState中nodes字典对应的ClusterNode的slot属性中,该slot为长度16384的二进制数组,如果该节点拥有某个slot,就将该slot对应的索引值置为1,如果没有,就将该slot置为0,如在节点redis-master-01中保存了0~5000的slot,那么0~5000的slot对应的二进制索引值均为1,而5001~16383对应的二进制索引位均为0.
2. 关于槽指派的内部实现
一个集群节点启动完成之后,节点的内存数据结构ClusterState的slots属性记录的0~16383个slot的指针均指向null,说明16384个slot暂时未指派给任何节点,例如在节点reds-master-01上“cluster addslots 0..5000”时,ClusterState的slots属性中的0~5000个slot的指针发生变化,这些指针分别指向reds-master-01节点的ClusterNode数据结构,并且该节点的ClusterNode数据结构的slots二进制位中的0~5000的索引值被置为1,同时,该节点会向集群中其他节点告知自己目前已经处理了0~5000个槽。
3. 关于键操作
在对集群键进行操作的时候,集群节点首先判断该键是否由自己所在的节点进行维护,例如,reds-master-01节点负责处理的槽位是0~5000,reds-master-02节点负责处理的槽位是5001~10000。
reds-master-01节点接收到一个写入建的请求后,首先对该key进行CRC16运算,得到一个小于等于16384的整数,该整数即为要存入数据的槽号。然后根据该整数判断该建是否由自己维护,如果是的话,就直接执行写入操作。
redis-master-01:6379> cluster keyslot age (integer) 741 redis-master-01:6379> set age 20 OK
如果CRC校验后的整数大于5000小于10000,则会将key自动转向到负责处理该slot的节点上
$ redis-cli -c -h redis-master-01 redis-master-01:6379> cluster keyslot name (integer) 5798 redis-master-01:6379> set name "Ting,Chris" -> Redirected to slot [5798] located at 172.16.101.55:6379 OK redis-master-01:6379> get name -> Redirected to slot [5798] located at 172.16.101.55:6379 "Ting,Chris"
注意,我们进入redis-cli交互式时添加了cluster参数,说明我们进入的是集群模式的cli,如果进入单机模式的cli执行上述命令会报错,指导你到对应的节点上执行键操作。
redis-master-01:6379> set name "Ting,Chris" (error) MOVED 5798 172.16.101.55:6379 redis-master-01:6379> get name (error) MOVED 5798 172.16.101.55:6379