Redis集群节点通信过程/原理流程分析


Posted in Redis onMarch 18, 2022

简介

        本文介绍Redis的Cluster(集群)的节点通信的流程。

通信流程

        在分布式存储中需要提供维护节点元数据信息的机制, 所谓元数据是指: 节点负责哪些数据, 是否出现故障等状态信息。 常见的元数据维护方式分为: 集中式和P2P方式。 Redis集群采用P2P的Gossip(流言) 协议,Gossip协议工作原理就是节点彼此不断通信交换信息, 一段时间后所有的节点都会知道集群完整的信息, 这种方式类似流言传播, 如下所示

Redis集群节点通信过程/原理流程分析

通信过程说明:

  • 集群中的每个节点都会单独开辟一个TCP通道, 用于节点之间彼此通信, 通信端口号在基础端口上加10000。
  • 每个节点在固定周期内通过特定规则选择几个节点发送ping消息。接收到ping消息的节点用pong消息作为响应。
  • 集群中每个节点通过一定规则挑选要通信的节点, 每个节点可能知道全部节点, 也可能仅知道部分节点, 只要这些节点彼此可以正常通信, 最终它们会达到一致的状态。 当节点出故障、 新节点加入、 主从角色变化、 槽信息变更等事件发生时, 通过不断的ping/pong消息通信, 经过一段时间后所有的节点都会知道整个集群全部节点的最新状态, 从而达到集群状态同步的目的。 

Gossip消息

消息流程

        Gossip协议的主要职责就是信息交换。 信息交换的载体就是节点彼此发送的Gossip消息, 了解这些消息有助于我们理解集群如何完成信息交换。

        常用的Gossip消息可分为: ping消息、 pong消息、 meet消息、 fail消息等, 它们的通信模式如下图所示:

Redis集群节点通信过程/原理流程分析

  • 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_t type; /* 消息类型,用于区分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消息时, 接收节点会解析消息内容并根据自身的识别情况做出相应处理, 对应流程如下图所示:

Redis集群节点通信过程/原理流程分析

接收节点收到ping/meet消息时, 执行解析消息头和消息体流程:

  • 解析消息头过程:

消息头包含了发送节点的信息, 如果发送节点是新节点且消息是meet类型, 则加入到本地节点列表; 如果是已知节点, 则尝试更新发送节点的状态, 如槽映射关系、 主从角色等状态。

  • 解析消息体过程:

如果消息体的clusterMsgDataGossip数组包含的节点是新节点, 则尝试发起与新节点的meet握手流程; 如果是已知节点, 则根据cluster MsgDataGossip中的flags字段判断该节点是否下线, 用于故障转移。
消息处理完后回复pong消息, 内容同样包含消息头和消息体, 发送节点接收到回复的pong消息后, 采用类似的流程解析处理消息并更新与接收节点最后通信时间, 完成一次消息通信。

节点选择

        虽然Gossip协议的信息交换机制具有天然的分布式特性, 但它是有成本的。 由于内部需要频繁地进行节点信息交换, 而ping/pong消息会携带当前节点和部分其他节点的状态数据, 势必会加重带宽和计算的负担。 Redis集群内节点通信采用固定频率(定时任务每秒执行10次) 。 因此节点每次选择需要通信的节点列表变得非常重要。 通信节点选择过多虽然可以做到信息及时交换但成本过高。 节点选择过少会降低集群内所有节点彼此信息交换频率,从而影响故障判定、 新节点发现等需求的速度。 因此Redis集群的Gossip协议需要兼顾信息交换实时性和成本开销, 通信节点选择的规则如下图所示

Redis集群节点通信过程/原理流程分析

        根据通信节点选择的流程可以看出消息交换的成本主要体现在单位时间选择发送消息的节点数量和每个消息携带的数据量。

1.选择发送消息的节点数量

        集群内每个节点维护定时任务默认每秒执行10次, 每秒会随机选取5个节点找出最久没有通信的节点发送ping消息, 用于保证Gossip信息交换的随机性。 每100毫秒都会扫描本地节点列表, 如果发现节点最近一次接受pong消息的时间大于cluster_node_timeout/2, 则立刻发送ping消息, 防止该节点信息太长时间未更新。 根据以上规则得出每个节点每秒需要发送ping消息的数
量=1+10*num(node.pong_received>cluster_node_timeout/2) , 因此cluster_node_timeout参数对消息发送的节点数量影响非常大。 当我们的带宽资源紧张时, 可以适当调大这个参数, 如从默认15秒改为30秒来降低带宽占用率。 过度调大cluster_node_timeout会影响消息交换的频率从而影响故障转移、 槽信息更新、 新节点发现的速度。 因此需要根据业务容忍度和资源消耗进行平衡。 同时整个集群消息总交换量也跟节点数成正比。

2.消息数据量

        每个ping消息的数据量体现在消息头和消息体中, 其中消息头主要占用空间的字段是myslots[CLUSTER_SLOTS/8], 占用2KB, 这块空间占用相对固定。 消息体会携带一定数量的其他节点信息用于信息交换。 具体数量见以下伪代码:

def get_wanted():
    int total_size = size(cluster.nodes)
    
    # 默认包含节点总量的1/10
    594int wanted = floor(total_size/10);
    if wanted < 3:
    # 至少携带3个其他节点信息
    wanted = 3;
    if wanted > total_size -2 :
    # 最多包含total_size - 2个
    wanted = total_size - 2;
    return wanted;

        根据伪代码可以看出消息体携带数据量跟集群的节点数息息相关, 更大的集群每次消息通信的成本也就更高, 因此对于Redis集群来说并不是大而全的集群更好, 对于集群规模控制的建议见之后“集群运维”。

其他网址

《Redis开发与运维》=> 第10章 集群=> 10.3 节点通信

到此这篇关于Redis集群节点通信过程/原理的文章就介绍到这了,更多相关Redis集群节点通信内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
基于Redis延迟队列的实现代码
May 13 Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
May 21 Redis
redis requires ruby version2.2.2的解决方案
Jul 15 Redis
使用redis实现延迟通知功能(Redis过期键通知)
Sep 04 Redis
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
Mar 16 Redis
一文搞懂Redis中String数据类型
Apr 03 Redis
Redis官方可视化工具RedisInsight安装使用教程
Apr 19 Redis
使用Redis实现分布式锁的方法
Jun 16 Redis
Redis唯一ID生成器的实现
Jul 07 Redis
Redis sentinel哨兵集群的实现步骤
Jul 15 Redis
redis protocol通信协议及使用详解
Jul 15 Redis
如何使用注解方式实现 Redis 分布式锁
Jul 23 Redis
redis数据一致性的实现示例
高并发下Redis如何保持数据一致性(避免读后写)
Mar 18 #Redis
redis击穿 雪崩 穿透超详细解决方案梳理
Redis调用Lua脚本及使用场景快速掌握
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
Redis中有序集合的内部实现方式的详细介绍
Mar 16 #Redis
You might like
php实现快速排序的三种方法分享
2014/03/12 PHP
PHP实现生成唯一编号(36进制的不重复编号)
2014/07/01 PHP
PHP针对中英文混合字符串长度判断及截取方法示例
2017/03/31 PHP
PHP多进程之pcntl_fork的实例详解
2017/10/15 PHP
Laravel框架实现超简单的分页效果示例
2019/02/08 PHP
通过javascript的匿名函数来分析几段简单有趣的代码
2010/06/29 Javascript
extjs中form与grid交互数据(record)的方法
2013/08/29 Javascript
读取input:file的路径并显示本地图片的方法
2013/09/23 Javascript
用Js实现的动态增加表格示例自己写的
2013/10/21 Javascript
JavaScript禁止页面操作的示例代码
2013/12/17 Javascript
js获取select标签选中值的两种方式
2014/01/09 Javascript
javascript实现别踩白块儿小游戏程序
2015/11/22 Javascript
js获取对象、数组的实际长度,元素实际个数的实现代码
2016/06/08 Javascript
jquery的父、子、兄弟节点查找,节点的子节点循环方法
2016/12/07 Javascript
Bootstrap基本组件学习笔记之列表组(11)
2016/12/07 Javascript
JS编写函数实现对身份证号码最后一位的验证功能
2016/12/29 Javascript
react-router JS 控制路由跳转实例
2017/06/15 Javascript
Node.js学习之TCP/IP数据通讯(实例讲解)
2017/10/11 Javascript
vue mint-ui 实现省市区街道4级联动示例(仿淘宝京东收货地址4级联动)
2017/10/16 Javascript
快速搭建React的环境步骤详解
2017/11/06 Javascript
js判断节假日实例代码
2017/12/27 Javascript
使用Angular CLI进行Build(构建)和Serve详解
2018/03/24 Javascript
Vue调用后端java接口的实例代码
2019/10/28 Javascript
[03:48]2014DOTA2 TI专访71DK夺冠不靠小组赛高排名
2014/07/11 DOTA
使用python调用zxing库生成二维码图片详解
2017/01/10 Python
Pycharm学习教程(3) 代码运行调试
2017/05/03 Python
django models里数据表插入数据id自增操作
2020/07/15 Python
加拿大廉价机票预订网站:CheapOair.ca
2018/03/04 全球购物
英国领先的豪华时尚家居网上商店:Amara
2019/08/12 全球购物
班主任新年寄语
2014/04/04 职场文书
国旗下的讲话演讲稿
2014/05/08 职场文书
2014年党支部学习材料
2014/05/19 职场文书
办公室主任四风问题对照检查材料思想汇报
2014/09/28 职场文书
2015年计生协会工作总结
2015/04/24 职场文书
小学同学聚会感言
2015/07/30 职场文书
初中教务主任竞聘演讲稿(范文)
2019/08/20 职场文书