当前位置: 首页 > news >正文

Redis Cluster Gossip 协议

概述

  前面我们讲解了Redis高可用架构的整体演进方式,其中包括「基本的主从架构」「改进的主从架构」「基于Sentinel(哨兵模式)的高可用架构」「集群模式」四种常用的Redis架构,其中「基于Sentinel(哨兵模式)的高可用架构」引入了Sentinel集群来管理Redis节点,来实现故障检测、故障发现、故障转移等保证Redis的高可用。「集群模式」则将数据分配在各个节点上,某种意义上也算的上一种分治思想,通过集群中各个节点间的相互通信来保证集群信息的透明性。
  集群的引入就会需要有相关的通信机制与协议来相互会话,以此来感知集群中各个节点的状态、信息等(可以理解为集群信息的透明性),从而来对集群中的各个节点来进行故障诊断、故障发现、故障转移、保证集群的高可用性。通信机制与对应的通信协议是集群实现高可用的底层基础之一(其他的有共识协议等),就像人与人之间的沟通,语言的统一带来了相互之间的可沟通性。Sentinel集群与Redis集群都实现了基于Gossip协议的通信机制。本文将介绍Gossip协议及通信机制的原理及工作流程,帮助大家更好的来理解Redis集群的实现原理。

元数据管理方式

  集群间的信息通信,首先需要确认的就是具体的通信信息,基本的信息就是节点ip,节点状态,主从标志等,这些信息通常就被称作为「元数据」。信息的出现就涉及到信息的管理,常见的信息管理方式有两种:「集中式」和「分散式」。其他的还有就是两种的混合折中方案。
  元数据管理是分布式系统的核心组件,不同的管理方式直接影响系统的性能、可靠性和扩展性。

集中式元数据管理

  从命名就可以看出,通过统一管理与维护,对于元数据的获取/变更,都统一在一组管理服务器上(通常是外部节点或集群)。
元数据集中式管理

典型实现

  • 前面文章介绍的HBase存储及分布式架构
  • 传统数据库的中央目录服务

核心特点

  • 单一权威源:所有元数据存储在中央节点
  • 强一致性:客户端总是获取最新元数据视图
  • 简单架构:易于实现和理解

优点

  • 一致性保证:天然保证全局一致性
  • 管理简便:维护和备份策略简单
  • 事务支持:容易实现跨操作的事务
  • 快速决策:全局视图便于优化决策

缺点

  • 单点故障:中央节点故障导致系统不可用
  • 性能瓶颈:所有请求必须经过中央节点
  • 扩展性限制:元数据规模受单节点容量限制
  • 网络依赖:客户端必须能连接中央节点

分散式元数据管理

  将元数据分散在集群中的各个节点上或部份节点上,相关的节点都各自有一份整个集群的元数据信息。
分散式元数据管理

典型实现

  • Redis Cluster (Gossip协议)
  • Cassandra (一致性哈希)

核心特点

  • 多副本存储:元数据分散在多个节点
  • 最终一致性:各节点视图可能短暂不一致
  • 自组织能力:节点间自动同步状态

优点

  • 高可用性:无单点故障
  • 水平扩展:随节点增加自动扩展
  • 分区容忍:网络分区时仍可部分工作
  • 本地访问:部分请求可本地处理

缺点

  • 实现复杂:需要处理并发和冲突
  • 一致性延迟:可能出现短暂不一致
  • 协调开销:节点间同步需要额外通信
  • 监控难度:全局状态难以即时掌握

数据一致性

  元数据分散式存储,就意味着需要一种同步机制来保证各个节点存储的信息是一致的,这才能进行整体集群的管理。常见的同步协议(算法)有:Paxos、Raft 和 Gossip。其中Paxos、Raft是抢一致性协议,Gossip是最终一致性协议。

Gossip 协议

  Gossip 协议又称 epidemic 协议(epidemic protocol),是基于流行病传播方式的节点或者进程之间信息交换的协议,是一种反熵(Anti-Entropy)算法,熵是物理学上的一个概念,代表杂乱无章,而反熵就是在杂乱无章中寻求一致。在P2P网络和分布式系统中应用广泛。就像流言蜚语一样,利用一种随机、带有传染性的方式,将信息传播到整个网络中,并在一定时间内使得系统内的所有节点数据一致

Gossip协议核心思想

  在一个处于有界网络的集群里,如果每个节点都随机与其他节点交换特定信息,经过足够长的时间后,集群各个节点对该份信息的认知终将收敛到一致

注意:

  • 所在的网络是一个有界网络,即整个集群中节点的数量是有限的。
  • “特定信息”一般就是指集群状态、各节点的状态以及其他元数据等。
  • Gossip协议是完全符合 BASE 原则,可以用在任何要求最终一致性的领域,比如分布式存储和注册中心。另外,它可以很方便地实现弹性集群,允许节点随时上下线,提供快捷的失败检测和动态负载均衡等。

特点

  • 去中心化:没有主节点控制通信
  • 随机性:节点随机选择其他节点通信
  • 最终一致性:信息最终会传播到所有节点
  • 容错性强:能容忍节点故障和网络分区

局限性

  • 信息传播有一定延迟(最终一致性)
  • 在大型集群中可能产生较多网络流量
  • 故障检测和恢复需要一定时间

Redis Cluster Gossip

  Redis Cluster 使用 Gossip 协议来实现节点间的信息传播和集群状态维护。这种去中心化的通信机制是 Redis Cluster 能够自动发现、故障检测和配置更新的核心。
Gossip协议通信拓扑图

  上图展示了 Redis Cluster 的通信拓扑图,其中实线表示节点间的主从复制关系,而虚线表示各个节点之间的 Gossip 通信网络。
  Redis Cluster中每个节点都维护了一份整个集群状态(元数据信息):

  • 当前集群状态
  • 集群中各节点所负责的 slots信息,及其migrate状态
  • 集群中各节点的master-slave状态
  • 集群中各节点的存活状态及怀疑Fail状态

  上图中Redis Cluster集群内虚线代表的Gossip协议传播的内容也就是每个节点自己维护的这份整个集群的信息状态数据,每个节点直接通过频繁的相互传播自己的内容,同时与其他节点传播过来的信息进行比较进行相应的一致性处理,最终整个集群中各个节点的信息将保持一致。

Gossip消息类型

  1. MEET消息:
     a.用于手动将新节点加入集群
     b.CLUSTER MEET {ip} {port} 命令触发
  2. PING消息:
     a.节点间定期发送的心跳消息
     b.携带发送节点视角的集群状态信息
  3. PONG消息:
     a.对PING或MEET的响应
     b.同样携带集群状态信息(PING消息接收节点上维护的集群信息)
  4. FAIL消息:
     a.节点故障的广播通知
     b.当某个节点被多数主节点判定为下线时触发(只会有一个节点进行发出,后续会讲到)

  如下是Redis源码中 cluster.h 文件中定义的消息类型:

// 注意,PING 、 PONG 和 MEET 实际上是同一种消息。
// PONG 是对 PING 的回复,它的实际格式也为 PING 消息,
// 而 MEET 则是一种特殊的 PING 消息,用于强制消息的接收者将消息的发送者添加到集群中(如果节点尚未在节点列表中的话)
#define CLUSTERMSG_TYPE_PING 0          /* Ping 消息 */
#define CLUSTERMSG_TYPE_PONG 1          /* Pong 用于回复Ping */
#define CLUSTERMSG_TYPE_MEET 2          /* Meet 请求将某个节点添加到集群中 */
#define CLUSTERMSG_TYPE_FAIL 3          /* Fail 将某个节点标记为 FAIL */
#define CLUSTERMSG_TYPE_PUBLISH 4       /* 通过发布与订阅功能广播消息 */
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 5 /* 请求进行故障转移操作,要求消息的接收者通过投票来支持消息的发送者 */
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6     /* 消息的接收者同意向消息的发送者投票 */
#define CLUSTERMSG_TYPE_UPDATE 7        /* slots 已经发生变化,消息发送者要求消息接收者进行相应的更新 */
#define CLUSTERMSG_TYPE_MFSTART 8       /* 为了进行手动故障转移,暂停各个客户端 */
#define CLUSTERMSG_TYPE_COUNT 9         /* 消息总数 */

  通过这些消息,集群内的每个节点都可以与其他任意一个节点进行相互的信息交互,保证整个集群内信息的透明性。

消息传播机制

定时任务(定时 PING/PONG 消息):

  1. 每100ms执行一次集群定时任务(clusterCron)
  2. 随机选择部分节点进行通信
详细过程

  Redis Cluster 中的节点都会定时地向其他节点发送 PING 消息,来交换各个节点状态信息,检查各个节点状态,包括在线状态、疑似下线状态 PFAIL 和已下线状态 FAIL。

  1. 每个实例之间会按照一定的频率,从集群中随机挑选一些实例,把 PING 消息发送给挑选出来的实例,用来检测这些实例是否在线,并交换彼此的状态信息。PING 消息中封装了发送消息的实例自身的状态信息、部分其它实例的状态信息,以及 Slot 映射表。
  2. 一个实例在接收到 PING 消息后,会给发送 PING 消息的实例,发送一个 PONG 消息。PONG 消息包含的内容和 PING 消息一样。

消息传播机制之定时ping/pong

新节点上线:

  Redis Cluster 加入新节点时,客户端需要执行 CLUSTER MEET 命令。过程如下:

  1. 节点A在执行 CLUSTER MEET 命令时会首先为新节点创建一个 clusterNode 数据,并将其添加到自己维护的 clusterState 的 nodes 字典中。(后续会讲解到 clusterState 和 clusterNode 关系)。
  2. 节点A会根据据 CLUSTER MEET 命令中的 IP 地址和端口号,向新节点发送一条 MEET 消息。新节点接收到节点一发送的MEET消息后,新节点也会为节点A创建一个 clusterNode 结构,并将该结构添加到自己维护的 clusterState 的 nodes 字典中。
  3. 新节点向节点A返回一条PONG消息。节点A接收到节点B返回的PONG消息后,得知新节点已经成功的接收了自己发送的MEET消息。
  4. 节点A还会向新节点发送一条 PING 消息。新节点接收到该条 PING 消息后,可以知道节点A已经成功的接收到了自己返回的PONG消息,从而完成了新节点接入的握手操作。

  MEET 操作成功之后,节点A会通过上面讲的定时 PING /PONG机制将新节点的信息发送给集群中的其他节点,让其他节点也与新节点进行握手,最终,经过一段时间后,新节点会被集群中的所有节点认识。
Redis Cluster新节点加入通信流程

节点疑似下线和真正下线(故障检测全流程)

  1. Redis Cluster 中的节点会定期检查已经发送 PING 消息的接收方节点是否在规定时间 ( cluster-node-timeout ) 内返回了 PONG 消息,如果没有则会将其标记为疑似下线状态,也就是 PFAIL 状态,如下图所示。

「图片待补充1」

  1. 节点A会通过 PING 消息,将节点二处于疑似下线状态的信息传递给其他节点,例如节点C。节点C接收到节点A的 PING 消息得知节点B进入 PFAIL 状态后,会在自己维护的 clusterState 的 nodes 字典中找到节点B所对应的 clusterNode 结构,并将主节点A的下线报告添加到 clusterNode 结构的 fail_reports 链表中。

「图片待补充2」

  1. 随着时间的推移,如果节点N(举个例子) 也因为 PONG 超时而认为节点B疑似下线了,并且发现自己维护的节点B的 clusterNode 的 fail_reports 中有半数以上的主节点数量的未过时的将节点B标记为 PFAIL 状态报告日志,那么节点N将会把节点B将被标记为已下线 FAIL 状态,并且节点N会立刻向集群其他节点广播主节点B已经下线的 FAIL 消息,所有收到 FAIL 消息的节点都会立即将节点B状态标记为已下线。如下图所示。

「图片待补充3」

注意:报告疑似下线记录是由时效性的,如果超过 cluster-node-timeout *2 的时间,这个报告就会被忽略掉,让节点B又恢复成正常状态。

目标选择算法

  1. 每个节点维护其他节点的最新通信时间戳
  2. 优先选择长时间未通信的节点(超过cluster-node-timeout/2)
  3. 每次PING选择1~3个目标节点
def select_gossip_targets(nodes):# 1. 筛选候选节点(排除自身和已断开节点)candidates = [n for n in nodes if n.connected and n != self]# 2. 按通信时间排序(优先选择最久未通信的节点)candidates.sort(key=lambda n: n.last_ping_sent)# 3. 使用逆指数分布随机选择selected = []for i in range(min(3, len(candidates))):if random.expovariate(1.0) > 0.5:  # 衰减因子控制selected.append(candidates[i])return selected

配置纪元(Epoch)冲突解决

双纪元机制

1.currentEpoch:全局单调递增计数器(uint64)
2.configEpoch:主节点唯一标识(故障转移时递增)

决策规则
  1. 节点总是接受更高纪元的消息
  2. 纪元相同时按节点ID字典序裁决
if (msgEpoch > localEpoch) {acceptNewConfig();
} else if (msgEpoch == localEpoch) {if (memcmp(senderID, selfID, 20) < 0) { // 比较节点IDacceptNewConfig();}
}
http://www.xdnf.cn/news/1074529.html

相关文章:

  • 在Linux系统中部署Java项目
  • 设计模式之装饰者模式
  • 2.安装Docker
  • 怎样学习STM32
  • 暴力风扇方案介绍
  • HarmonyOS实战:自定义表情键盘
  • FPGA实现CameraLink视频解码,基于Xilinx ISERDES2原语,提供4套工程源码和技术支持
  • llama.cpp学习笔记:后端加载
  • 图书管理系统练习项目源码-前后端分离-使用node.js来做后端开发
  • Conda 环境配置之 -- Mamba安装(causal-conv1d、mamba_ssm 最简单配置方法)-- 不需要重新配置CDUA
  • 领域驱动设计(DDD)【26】之CQRS模式初探
  • AlpineLinux安装部署elasticsearch
  • Kafka4.0初体验
  • Python爬虫:Requests与Beautiful Soup库详解
  • 重写(Override)与重载(Overload)深度解析
  • 【C++】C++中的友元函数和友元类
  • 71. 简化路径 —day94
  • Bugku——WEB篇(持续更新ing)
  • documents4j导出pdf
  • Ubuntu服务器(公网)- Ubuntu客户端(内网)的FRP内网穿透配置教程
  • 数据结构 哈希表、栈的应用与链式队列 6.29 (尾)
  • 现代 JavaScript (ES6+) 入门到实战(八):总结与展望 - 成为一名现代前端开发者
  • day46/60
  • H3C-路由器交换机-中继
  • 计算机组成原理与体系结构-实验一 进位加法器(Proteus 8.15)
  • 5 c++核心——文件操作
  • MySQL技巧
  • 如何优化RK3588集群的性能?支持12个RK3588云手机阵列
  • C++ 格式化输入输出
  • Java中对JSON的操作