Redis 3.x——集群节点通信

Redis 3.x——集群节点通信
集群节点通信1、通信流程2、Gossip消息3、节点选择1、通信流程在分布式存储中需要提供维护节点元数据信息的机制所谓元数据是指节点负责哪些数据是否出现故障等状态信息。常见的元数据维护方式分为集中式和P2P方式。Redis集群采用P2P的Gossip流言协议Gossip协议工作原理就是节点彼此不断通信交换信息一段时间后所有的节点都会知道集群完整的信息这种方式类似流言传播如图所示。通信过程说明集群中的每个节点都会单独开辟一个TCP通道用于节点之间彼此通信通信端口号在基础端口上加10000。每个节点在固定周期内通过特定规则选择几个节点发送ping消息。接收到ping消息的节点用pong消息作为响应。集群中每个节点通过一定规则挑选要通信的节点每个节点可能知道全部节点也可能仅知道部分节点只要这些节点彼此可以正常通信最终它们会达到一致的状态。当节点出故障、新节点加入、主从角色变化、槽信息变更等事件发生时通过不断的ping/pong消息通信经过一段时间后所有的节点都会知道整个集群全部节点的最新状态从而达到集群状态同步的目的。2、Gossip消息Gossip协议的主要职责就是信息交换。信息交换的载体就是节点彼此发送的Gossip消息了解这些消息有助于我们理解集群如何完成信息交换。常用的Gossip消息可分为ping消息、pong消息、meet消息、fail消息等它们的通信模式如图所示。meet消息用于通知新节点加入。消息发送者通知接收者加入到当前集群meet消息通信正常完成后接收节点会加入到集群中并进行周期性的ping、pong消息交换。ping消息集群内交换最频繁的消息集群内每个节点每秒向多个其他节点发送ping消息用于检测节点是否在线和交换彼此状态信息。ping消息发送封装了自身节点和部分其他节点的状态数据。pong消息当接收到ping、meet消息时作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。fail消息当节点判定集群内另一个节点下线时会向集群内广播一个fail消息其他节点接收到fail消息之后把对应节点更新为下线状态。所有的消息格式划分为消息头和消息体。消息头包含发送节点自身状态数据接收节点根据消息头就可以获取到发送节点的相关数据结构如下typedef struct{char sig[4];/* 信号标示 */ uint32_t totlen;/* 消息总长度 */ uint16_t ver;/* 协议版本*/ uint16_ttype;/* 消息类型,用于区分meet,ping,pong等消息 */ uint16_t count;/* 消息体包含的节点数量仅用于meet,ping,ping消息类型*/ uint64_t currentEpoch;/* 当前发送节点的配置纪元 */ uint64_t configEpoch;/* 主节点/从节点的主节点配置纪元 */ uint64_t offset;/* 复制偏移量 */ char sender[CLUSTER_NAMELEN];/* 发送节点的nodeId */ unsigned char myslots[CLUSTER_SLOTS/8];/* 发送节点负责的槽信息 */ char slaveof[CLUSTER_NAMELEN];/* 如果发送节点是从节点记录对应主节点的nodeId */ uint16_t port;/* 端口号 */ uint16_t flags;/* 发送节点标识,区分主从角色是否下线等 */ unsigned char state;/* 发送节点所处的集群状态 */ unsigned char mflags[3];/* 消息标识 */ union clusterMsgData data /* 消息正文 */;}clusterMsg;集群内所有的消息都采用相同的消息头结构clusterMsg它包含了发送节点关键信息如节点id、槽映射、节点标识主从角色是否下线等。消息体在Redis内部采用clusterMsgData结构声明结构如下union clusterMsgData{/* ping,meet,pong消息体*/ struct{/* gossip消息结构数组 */ clusterMsgDataGossip gossip[1];}ping;/* FAIL 消息体 */ struct{clusterMsgDataFail about;}fail;// …};消息体clusterMsgData定义发送消息的数据其中ping、meet、pong都采用cluster MsgDataGossip数组作为消息体数据实际消息类型使用消息头的type属性区分。每个消息体包含该节点的多个clusterMsgDataGossip结构数据用于信息交换结构如下typedef struct{char nodename[CLUSTER_NAMELEN];/* 节点的nodeId */ uint32_t ping_sent;/* 最后一次向该节点发送ping消息时间 */ uint32_t pong_received;/* 最后一次接收该节点pong消息时间 */ char ip[NET_IP_STR_LEN];/* IP */ uint16_t port;/* port*/ uint16_t flags;/* 该节点标识, */}clusterMsgDataGossip;当接收到ping、meet消息时接收节点会解析消息内容并根据自身的识别情况做出相应处理对应流程如图所示。接收节点收到ping/meet消息时执行解析消息头和消息体流程解析消息头过程消息头包含了发送节点的信息如果发送节点是新节点且消息是meet类型则加入到本地节点列表如果是已知节点则尝试更新发送节点的状态如槽映射关系、主从角色等状态。解析消息体过程如果消息体的clusterMsgDataGossip数组包含的节点是新节点则尝试发起与新节点的meet握手流程如果是已知节点则根据cluster MsgDataGossip中的flags字段判断该节点是否下线用于故障转移。消息处理完后回复pong消息内容同样包含消息头和消息体发送节点接收到回复的pong消息后采用类似的流程解析处理消息并更新与接收节点最后通信时间完成一次消息通信。3、节点选择虽然Gossip协议的信息交换机制具有天然的分布式特性但它是有成本的。由于内部需要频繁地进行节点信息交换而ping/pong消息会携带当前节点和部分其他节点的状态数据势必会加重带宽和计算的负担。Redis集群内节点通信采用固定频率定时任务每秒执行10次​。因此节点每次选择需要通信的节点列表变得非常重要。通信节点选择过多虽然可以做到信息及时交换但成本过高。节点选择过少会降低集群内所有节点彼此信息交换频率从而影响故障判定、新节点发现等需求的速度。因此Redis集群的Gossip协议需要兼顾信息交换实时性和成本开销通信节点选择的规则如图所示。根据通信节点选择的流程可以看出消息交换的成本主要体现在单位时间选择发送消息的节点数量和每个消息携带的数据量。1. 选择发送消息的节点数量集群内每个节点维护定时任务默认每秒执行10次每秒会随机选取5个节点找出最久没有通信的节点发送ping消息用于保证Gossip信息交换的随机性。每100毫秒都会扫描本地节点列表如果发现节点最近一次接受pong消息的时间大于cluster_node_timeout/2则立刻发送ping消息防止该节点信息太长时间未更新。根据以上规则得出每个节点每秒需要发送ping消息的数量110*num(node.pong_receivedcluster_node_timeout/2)因此cluster_node_timeout参数对消息发送的节点数量影响非常大。当我们的带宽资源紧张时可以适当调大这个参数如从默认15秒改为30秒来降低带宽占用率。过度调大cluster_node_timeout会影响消息交换的频率从而影响故障转移、槽信息更新、新节点发现的速度。因此需要根据业务容忍度和资源消耗进行平衡。同时整个集群消息总交换量也跟节点数成正比。2.消息数据量每个ping消息的数据量体现在消息头和消息体中其中消息头主要占用空间的字段是myslots[CLUSTER_SLOTS/8]​占用2KB这块空间占用相对固定。消息体会携带一定数量的其他节点信息用于信息交换。具体数量见以下伪代码defget_wanted():inttotal_sizesize(cluster.nodes)# 默认包含节点总量的1/10intwantedfloor(total_size/10);ifwanted3:# 至少携带3个其他节点信息 wanted3;ifwantedtotal_size-2:# 最多包含total_size-2个 wantedtotal_size-2;returnwanted;根据伪代码可以看出消息体携带数据量跟集群的节点数息息相关更大的集群每次消息通信的成本也就更高因此对于Redis集群来说并不是大而全的集群更好。