edis源码分析(八):集群--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 ..

拷贝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挂了各节点会怎么做?

但是集群涉及到多个节点共同合作,细节很多,比如数据一致性的问题。自己脑袋确实不够用了,先写到这里吧。