一、写在文章开头
今天我们来聊点有意思的,关于redis中集群间通信的设计与实现,本文将从源码的角度分析redis集群节点如何利用Gossip协议完成节点间的通信与传播,希望对你有帮助。
二、详解Redis集群节点通信的设计与实现
1. 详解Gossip协议
在此之前我们先简单介绍一下Gossip协议,该协议是分布式集群的一种通信协议,我们都知道管理集群的方式有中心化和去中心化两种方式,中心化的方式是通过第一个第三方的管理中心,例如zookeeper等来维护一份集群节点的信息、状态:
而redis采用的是去中心化的方式实现集群节点通信,即通过Gossip协议进行节点通信,让各个节点之间两两通信,广播与自己保持交流的节点,由此将节点串联起来构成一张关系网:
我们以一个简单的场景为例介绍一下Gossip协议,默认情况下我们的当前有3个节点的集群,各个节点彼此按照通信要求发送自己的信息和与自己保持交流的节点,由此将有限的资源共享出去构成一个集群。
此时,我们需要横向扩展一个节点4,我们只需配置/redis-cli --cluster add-node 新节点IP:新节点端口 任意存活节点IP:任意存活节点端口,这个存活节点后续和其他节点通信时,就会将当前新添加的节点4发送出去,由此其他节点收到这个消息并存储下来,经过各个节点的不断反复通信,这个集群中的各个节点就会拥有集群中所有节点的信息。
2. 集群消息协定
任何通信都是需要按照协议规范进行,redis集群也一样,为了保证节点间通信的规范,redis要求集群节点通信的消息的类型可以是以下几种:
- ping消息,用来向其他节点发送节点信息。
- 回复ping的pong消息。
- 如果当前节点中存在新添加的节点,则通过meet格式的消息发送给其他节点。
- 如果节点出现故障,则发送fail消息告知集群其他节点。
对此我们给出消息的宏定义代码,位于cluster.h中:
3. 集群节点消息体
后续集群都会通过clusterMsg来表示一条消息,它记录消息长度以及发送节点名称、负责的slots以及节点端口号等信息:
这里我们对这个消息体clusterMsgData进行展开说明一下,可以看到他用一段共用体维护各种类型消息的结构,这其中我们只需要了解的是ping消息,从注释可以看到ping消息这个结构体可以发送ping、meet、pong等类型消息,ping消息类型其内部用clusterMsgDataGossip数组维护,这一点这个消息可以包含多个节点信息存于数组中:
步入clusterMsgDataGossip即可看到这个结构体存储的是需要发送给它人的节点名称、ping和收到ping的时间以及端口号等信息:
我们来简单小结一下,假设我们的某个节点向其他节点发送ping消息告知自己维护的节点信息和状态,那么对应的消息格式大体如下图所示:
4. 详解集群节点ping流程
集群节点的指向流程也是交由redis的时间事件serverCron执行,它会每个100ms执行一次集群的定任务clusterCron方法,其内部会检查这个定时任务是否执行了10次,一旦执行10次(也就是100ms*10即每1秒)后就会随机从当前节点维护的其他节点信息字典表中抽取5个节点,找到最早回复pong给当前节点发送一条ping消息:
对此我们给出定时执行的serverCron函数,可以看到其内部每100ms执行一次集群定时任务clusterCron:
我们步入clusterCron即可看到,该定时任务会随机抽取5个节点然后找到最早给该节点发送pong的节点发送ping消息包:
步入clusterSendPing即可看到我们所说的核心逻辑,即按照公式计算出要发送给最早回复pong的节点对应节点数,然后封装成消息发送出去:
5. 等待pong消息回复并解析
每个集群的节点都会定时检查和对端链接的连接是否断开,如果断开的尝试异步非阻塞向其发送建立连接请求,并注册一个处理器clusterReadHandler处理对端的ping等消息,所以我们上文的ping消息实际上就是通过这个函数进行解析读取:
对此我们给出这段源码的入口即可集群的定时任务clusterCron方法,可以看到其内部会便利当前节点通信的节点,查看连接是否为空,若为空则发起连接并注册clusterReadHandler处理消息:
步入clusterReadHandler即可看到redis服务端解析消息存储到buf并通过clusterProcessPacket解析的逻辑:
而clusterProcessPacket即是该方法的核心所在,它会将对端节点发送的消息进行解析与处理,这里我们就以收到pong消息为例说明一下流程,假设回复pong的是master节点,它会更新收到这条网络连接pong响应时间,然后解析报文内容,如果发现有个节点不在我们的节点列表中,将其存入node字典表中:
步入clusterProcessGossipSection即可看到该函数会遍历消息中的节点,一旦发现该节点是新添加节点则调用clusterStartHandshake其存入nodes字典表中:
我们给出clusterStartHandshake中将其存入server的cluster的nodes字典表的逻辑:
三、小结
来简单小结一下Redis集群节点如何通过Gossip协议构建集群网络的:
- 新节点通过meet和集群中某个节点a建立连接。
- 当前节点执行clusterCron定时任务时,随机抽取5个节点并找到最早回复pong的实例,假设是节点a,发送ping消息。
- 注册clusterReadHandler处理器其他节点发送的消息。
- 收到节点a的pong消息回复,判断查看该节点是否是已知节点,如果是则调用clusterProcessGossipSection解析报文内容,如果存在新节点则进行握手通信,如果连接建立成功则将该节点存入当前实例的nodes节点中。