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 27 Redis
5分钟教你docker安装启动redis全教程(全新方式)
May 29 Redis
k8s部署redis cluster集群的实现
Jun 24 Redis
Redis 彻底禁用RDB持久化操作
Jul 09 Redis
Redis读写分离搭建的完整步骤
Sep 14 Redis
Redis的字符串是如何实现的
Oct 24 Redis
redis的list数据类型相关命令介绍及使用
Jan 18 Redis
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
Mar 16 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
sentinel支持的redis高可用集群配置详解
Apr 01 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 Redis
Redis主从复制操作和配置详情
Sep 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基于Closure类创建匿名函数的方法详解
2017/08/17 PHP
JS中Iframe之间传值的方法
2013/03/11 Javascript
javascript获取隐藏dom的宽高 具体实现
2013/07/14 Javascript
JS 去前后空格大全(IE9亲测)
2013/07/15 Javascript
javascript history对象(历史记录)使用方法(实现浏览器前进后退)
2014/01/07 Javascript
javascript日期对象格式化为字符串的实现方法
2014/01/14 Javascript
vue-hook-form使用详解
2017/04/07 Javascript
利用nodeJs anywhere搭建本地服务器环境的方法
2018/05/12 NodeJs
实例介绍JavaScript中多种组合继承
2019/01/20 Javascript
angular 表单验证器验证的同时限制输入的实现
2019/04/11 Javascript
深入理解vue中的slot与slot-scope
2019/04/22 Javascript
Vue实现导航栏点击当前标签变色功能
2020/08/19 Javascript
layui禁用侧边导航栏点击事件的解决方法
2019/09/25 Javascript
Vue中使用matomo进行访问流量统计的实现
2019/11/05 Javascript
Python中的True,False条件判断实例分析
2015/01/12 Python
Python使用multiprocessing创建进程的方法
2015/06/04 Python
Python中的一些陷阱与技巧小结
2015/07/10 Python
python elasticsearch从创建索引到写入数据的全过程
2019/08/04 Python
Python pandas实现excel工作表合并功能详解
2019/08/29 Python
python实现字典嵌套列表取值
2019/12/16 Python
一款利用纯css3实现的超炫3D表单的实例教程
2014/12/01 HTML / CSS
使用html5制作loading图的示例
2014/04/14 HTML / CSS
卡塔尔航空官方网站:Qatar Airways
2017/02/08 全球购物
美国知名女性服饰品牌:New York & Company
2017/03/23 全球购物
洲际酒店集团英国官网:IHG英国
2019/07/10 全球购物
Electric官网:美国高级眼镜和配件品牌
2020/06/04 全球购物
描述JSP和Servlet的区别、共同点、各自应用的范围
2012/10/02 面试题
小学开学寄语
2014/01/19 职场文书
高中语文课后反思
2014/04/27 职场文书
拾金不昧锦旗标语
2014/06/27 职场文书
最美护士演讲稿
2014/08/27 职场文书
学生检讨书范文
2014/10/30 职场文书
幼师自荐信范文
2015/03/06 职场文书
党员个人承诺书
2015/04/27 职场文书
紫日观后感
2015/06/05 职场文书
成本低的5个创业项目:投资小、赚钱快
2019/08/20 职场文书