redis源码分析(八):集群–cluster
redis集群我们可以使用sentinel的模式(详情点击这里),这个模式有几个缺点
- 1.sentinel是用来监控redis的,这个进程本该对客户端隐藏,但是sentinel模式下,master如果down了,某个slave成为master后,客户端无法感知,因此需要客户端还需要连接sentinel来获取master的地址。
- 2.sentinel部署方式本质还是一主多从的模式,master的压力比较大。
redis官方还提供了另一种集群的方式 — cluster, 一个集群中数据根据其key的哈希值被分别存储到 0 ~ 16383个槽中(slot)。每个槽均要被唯一添加到集群master节点中,在所有槽都被添加以后,集群的状态才会变成可用。
集群搭建
如果需要启用cluster集群的话,需要更改配置如下:
port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
appendonly yes
拷贝6份可执行程序到不同文件夹,从node1到node6,端口范围7001~7006,编写脚本startup.sh以便调试,内容如下:
pkill redis-server
cd node1
./redis-server ./redis.conf &
cd ..
cd node2
./redis-server ./redis.conf &
cd ..
cd node3
./redis-server ./redis.conf &
cd ..
cd node4
./redis-server ./redis.conf &
cd ..
cd node5
./redis-server ./redis.conf &
cd ..
cd node6
./redis-server ./redis.conf &
cd ..
编写cleanup.sh来清除节点信息和数据
rm ./*/nodes.conf
rm ./*/*.aof
启动一个干净的集群集合以后。
我们通过一下操作来创建一个简单的集群。1.连接各节点,2.分配槽 3.用cluster nodes 获取到各节点的名称,然后用cluster replicate设置各个slave的master节点。
1. redis-cli -p 7001
> cluster meet 127.0.0.1 7002
> cluster meet 127.0.0.1 7003
> cluster meet 127.0.0.1 7004
> cluster meet 127.0.0.1 7005
> cluster meet 127.0.0.1 7006
2. redis-cli -h 127.0.0.1 -p 7001 cluster addslots {0..5461}
redis-cli -h 127.0.0.1 -p 7002 cluster addslots {5462..10922}
redis-cli -h 127.0.0.1 -p 7003 cluster addslots {10923..16383}
3. redis-cli -p 7004 //重复三次
> cluster replicate $MasterName
用redis-cli -c 来连接集群服务端,每次写操作,如果key映射出来的槽不是这个节点处理的,他会返回一条MOVED错误和应该接收这条写命令的master节点信息。客户端需要重定向到这个地址去写入数据。
流程分析
接下来我们就可以将代码附加到其中一个程序开始观察整个流程了。
redis启动时除了监听port端口以外,还启动了port+10000的端口,用于集群服务各进程间互联,每个客户端连接的socket处理读事件的函数是clusterReadHandler。
集群的命令
集群模式下有个重要的命令”cluster”,下面解析几个重要的:
cluster meet $host $port 与其他集群的节点建立链接。
cluster info 查看当前集群的简略信息。
cluster nodes 查看当前的节点信息。
cluster addslots/delslots $slot1 $slot2 … $slotn 添加(删除)槽到当前节点。可以使用 redis-cli -h $host -p $port cluster addslots {$begin..$end} 来批量添加的连续end-begin个槽,(进入了客户端内后敲不行)。注意同一个槽不能重复添加。
cluster flushslots 删除当前所有的槽。
cluster setslot $slot migrating $targetName 将$slot槽从本节点迁移到目标节点,节点名称为每个节点的runid。
cluster setslot $slot importing $sourceName 将$slot槽从源节点迁移到本节点,为上条的逆操作。
cluster keyslot $key 计算$key应该被放到哪个槽中。
cluster replicate $targetName 设置当前节点为slave,并从对应的master中复制数据,注意slave有旧数据或被分配了槽会操作失败。
这里有一个小插曲,(测试的时候发现还没有调用addslots来添加槽,有的节点就凭白无故多了几个固定的槽,后来跟了代码发现是该节点aof文件没有清除,导致了节点启动的时候读取数据并加给自己了槽。)
集群的时间事件。
集群的时间事件处理函数是clusterCron。我们看看它都干了什么。
1.连接集群内的其他未连接上的服务进程,调用”cluster meet $ip $port”时,会新建一个集群节点放入“server.cluster->nodes”,在下次时间周期到来时连接port+10000端口,并设置读事件处理函数为clusterReadHandler,注意到这个和本地监听的集群端口处理函数是同一个。首次连接,给其发送meet命令(断线重连,直接发ping)。
2.每十次事件循环执行一次,随机ping五个集群内的其他进程。
3.断开连接超时的链接,在下个周期会重连。
4.由slave来统计各个master上的slave数量。如果当前有master下的slave都挂了,且当前的集群保存最多slave的master上有大于2个的slave,那么可以分一个slave给那个”光杆司令”master。具体的迁移处理函数是clusterHandleSlaveMigration,具体算法就是对比各个slave的runid字符串,如果自身的最小,则开始迁移
5.如果主节点挂了(超半数节点认为他挂了),而且当前节点是它的slave,那么此时开始发送一个故障转移授权请求。
6.故障转移,如果当前节点是slave,对应的master挂了且获得了故障转移权限,则开始故障转移,将自身升级为master,继承原master的槽,并通知其他节点自己升级了。此时如果原master又启动了,发现纪元比新的master小,只能降级为slave。
对集群中其他节点发来消息的处理
处理函数是上文提及的clusterReadHandler,我们直接看调用到的核心函数clusterProcessPacket。
1.处理ping和meet消息,均返回pong,将新消息的数据更新到本节点。注意到在所有ping,pong,meet消息中,均会去尝试携带二者(自己和对方)之外节点的gossip信息,特别是状态信息。这个机制保证了,在有限的时间内,所有节点均可以meet each other。。
2.如果有从节点的master发生了变更,修改本地数据。如果有master的槽信息发生了变化,更新之。
3.异常处理–(如果有master重启可能会发生。)如果对方是master,且本节点记录的master中,管理的槽和对方有重复。则通知纪元较小的修正;同理,接收到了CLUSTERMSG_TYPE_UPDATE请求,如果本节点的纪元较小,也需更新本节点的槽信息。
4.解析对方节点A的gossip信息,如果gossip中的目标节点B的状态是疑似下线或者已下线,将A添加节点B的failedlist中。如果大多数节点都认为这个节点下线了,那么我们将其从疑似下线转变为下线。
5.假设master A下线了,其slave会发起一个故障转移的授权请求,只有master有投票权。所有接受到该消息的master节点如果确定A确实下线了,那么投B一票。投票的过程和sentinel选举新的master的过程类似,都是基于纪元累增,胜出的slave将执行故障转移。
只是略读了一下代码,大致理清楚了其中的业务处理和故障处理流程,master挂了各节点会怎么做? slave挂了各节点会怎么做?
但是集群涉及到多个节点共同合作,细节很多,比如数据一致性的问题。自己脑袋确实不够用了,先写到这里吧。