Gossip协议是什么
Gossip协议是什么
Gossip protocol 也叫 Epidemic Protocol (流行病协议), 是基于流行病传播方式的节点或者进程之间信息交换的协议, 也被叫做流言算法, 八卦算法、疫情传播算法等等.
说到 Gossip 协议, 就不得不提著名的六度分隔理论.
简单地说, 你和任何一个陌生人之间所间隔的人不会超过六个. 也就是说, 最多通过六个人你就能够认识任何一个陌生人(Facebook通过实验发现当今这个“网络直径”是 4.57 ), 六度分隔理论也就是 Gossip 协议的雏形了.
定义
Gossip 协议的定义十分简单: 以给定的频率, 每台计算机随机选择另一台计算机, 并共享任何消息.
因为简单的定义, 实现方式和变种也特别多, 根据不同的场景和需求, Gossip 协议的表现方式也不尽相同
原理
假如公司内突然没有了网络, 该如何快速且高效的将一个消息传递给所有员工呢? Gossip 给出的解决方案类似公司内的八卦传播, 一传十, 十传百的将消息同步给所有人. 基本思想就是: 一个节点想要分享一些信息给网络中的其他的一些节点. 于是, 它周期性的随机选择一些节点, 并把信息传递给这些节点. 这些收到信息的节点接下来会做同样的事情, 即把这些信息传递给其他一些随机选择的节点. 一般而言, 信息会周期性的传递给N个目标节点, 而不只是一个.这个N被称为fanout.
工作过程类似下图(可以理解为并行广度优先遍历)
实现
Gossip 协议被广泛用于多种场景, 如 Redis Cluster, Bitcoin 等等. 本文以一个实现了 Gossip 协议的 Golang Repo — memberlist 作为切入点, 简要说明一下其中对 Gossip 协议的具体实现. 该 Repo 被 Alertmanager、Consul 等项目使用, 例如 Alertmanager 就使用其同步多节点之间的 Silence 信息.
SWIM 简介
memberlist 基于 SWIM 协议开发, SWIM 是 Gossip 协议的一种, 用原文来说, SWIM is Scalable Weakly-Consistent Infection-Style Process Group Membership Protocol.
定义里的每一部分都描述了 SWIM 可以做什么:
- 可扩展性: 如果集群中的一个节点出现异常, 至少会有一个其他节点在常数时间内得知该情况, 其他节点得知该情况的速度取决于集群规模(对数级). 集群内每个节点的负载始终是保持不变的, 不会因为集群规模的增大而增大. 这是非常重要的一个性质, 也是 Gossip协议的杀手锏.
- 弱一致性: Gossip 协议只保证最终一致.
- 感染式传播: Gossip 协议中的事件以感染式传播, 这也是可扩展性里传播时间和集群规模为对数比的原因.
- 成员: 集群的每个节点都包含集群所有其他成员的状态表, 并根据从其他节点和广播收到的‘Gossip’来更新该表.
SWIM 组件
SWIM 由两个独立的组件组成——故障检测组件(failure detection)和事件传播组件(event dissemination). 故障检测组件会通过 ping
, ping-req
和 ack
消息对集群中的节点进行存活检测, 事件传播组件则负责通过 UDP 在集群节点之间传递事件.
memberlist 实现
memberlist 实现了 SWIM , 而且在其基础之前增加了不少优化性改动.
让我们来瞅一下 memberlist 的实现, 下面的代码是 memberlist 的主流程代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // Schedule is used to ensure the Tick is performed periodically. This // function is safe to call multiple times. If the memberlist is already // scheduled, then it won't do anything. func (m *Memberlist) schedule() {m.tickerLock.Lock()defer m.tickerLock.Unlock()// 创建probe goroutineif m.config.ProbeInterval > 0 {t := time.NewTicker(m.config.ProbeInterval)go m.triggerFunc(m.config.ProbeInterval, t.C, stopCh, m.probe)m.tickers = append(m.tickers, t)}// 创建push/pull goroutineif m.config.PushPullInterval > 0 {go m.pushPullTrigger(stopCh)}// 创建gossip goroutineif m.config.GossipInterval > 0 && m.config.GossipNodes > 0 {t := time.NewTicker(m.config.GossipInterval)go m.triggerFunc(m.config.GossipInterval, t.C, stopCh, m.gossip)m.tickers = append(m.tickers, t)} } |
probe goroutine, gossip goroutine 可以简单的理解为 memberlist 对于故障检测和事件传播的实现.
消息类型
memberlist 在整个生命周期内, 总共有两种类型的消息:
- udp协议消息: 传输PING消息、间接PING消息、ACK消息、NACK消息、Suspect消息、 Alive消息、Dead消息、消息广播
- tcp协议消息: 用户数据同步、节点状态同步、PUSH-PULL消息
故障检测
memberlist 利用点对点随机探测机制实现成员的故障检测, 因此将节点的状态分为3种:
- Alive: 活动节点
- Suspect: 可疑节点
- Dead: 死亡节点
probe goroutine 通过点对点随机探测实现成员的故障检测, 强化系统的高可用. 整体流程如下:
- 随机探测: 节点启动后, 每隔一定时间间隔, 会选取一个节点对其发送PING消息.
- 重试与间隔探测请求: PING消息失败后, 会随机选取N(由config中
IndirectChecks
设置)个节点发起间接PING请求和再发起一个TCP PING消息. - 间隔探测: 收到间接PING请求的节点会根据请求中的地址发起一个PING消息, 将PING的结果返回给间接请求的源节点.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | HANDLE_REMOTE_FAILURE:// Get some random live nodes.m.nodeLock.RLock()kNodes := kRandomNodes(m.config.IndirectChecks, m.nodes, func(n *nodeState) bool {return n.Name == m.config.Name ||n.Name == node.Name ||n.State != StateAlive})m.nodeLock.RUnlock()// Attempt an indirect ping.expectedNacks := 0selfAddr, selfPort = m.getAdvertise()ind := indirectPingReq{SeqNo: ping.SeqNo,Target: node.Addr,Port: node.Port,Node: node.Name,SourceAddr: selfAddr,SourcePort: selfPort,SourceNode: m.config.Name,}for _, peer := range kNodes {// We only expect nack to be sent from peers who understand// version 4 of the protocol.if ind.Nack = peer.PMax >= 4; ind.Nack {expectedNacks++}if err := m.encodeAndSendMsg(peer.FullAddress(), indirectPingMsg, &ind); err != nil {m.logger.Printf("[ERR] memberlist: Failed to send indirect ping: %s", err)}}// Also make an attempt to contact the node directly over TCP. This// helps prevent confused clients who get isolated from UDP traffic// but can still speak TCP (which also means they can possibly report// misinformation to other nodes via anti-entropy), avoiding flapping in// the cluster.//// This is a little unusual because we will attempt a TCP ping to any// member who understands version 3 of the protocol, regardless of// which protocol version we are speaking. That's why we've included a// config option to turn this off if desired.fallbackCh := make(chan bool, 1)// Wait for the acks or timeout. Note that we don't check the fallback// channel here because we want to issue a warning below if that's the// *only* way we hear back from the peer, so we have to let this time// out first to allow the normal UDP-based acks to come in.select {case v := <-ackCh:if v.Complete == true {return}}// Finally, poll the fallback channel. The timeouts are set such that// the channel will have something or be closed without having to wait// any additional time here.for didContact := range fallbackCh {if didContact {m.logger.Printf("[WARN] memberlist: Was able to connect to %s but other probes failed, network may be misconfigured", node.Name)return}}// Update our self-awareness based on the results of this failed probe.// If we don't have peers who will send nacks then we penalize for any// failed probe as a simple health metric. If we do have peers to nack// verify, then we can use that as a more sophisticated measure of self-// health because we assume them to be working, and they can help us// decide if the probed node was really dead or if it was something wrong// with ourselves.awarenessDelta = 0if expectedNacks > 0 {if nackCount := len(nackCh); nackCount < expectedNacks {awarenessDelta += (expectedNacks - nackCount)}} else {awarenessDelta += 1}// No acks received from target, suspect it as failed.m.logger.Printf("[INFO] memberlist: Suspect %s has failed, no acks received", node.Name)s := suspect{Incarnation: node.Incarnation, Node: node.Name, From: m.config.Name}m.suspectNode(&s) |
- 探测超时标识可疑: 如果探测超时之间内, 本节点没有收到任何一个要探测节点的ACK消息, 则标记要探测的节点状态为suspect.
- 可疑节点广播: 启动一个定时器用于发出一个 suspect 广播, 此期间内如果收到其他节点发来的相同的 suspect 信息时, 将本地 suspect 的确认数 +1 , 当定时器超时后, 该节点信息仍然不是 alive 的, 且确认数达到要求, 会将该节点标记为 dead.
- 可疑消除: 当本节点收到别的节点发来的 suspect 消息时, 会发送 alive 广播, 从而清除其他节点上的 suspect 标记.
- 死亡通知: 当本节点离开集群时或者本地探测的其他节点超时被标记死亡, 会向集群发送本节点dead广播.
- 死亡消除: 如果从其他节点收到自身的 dead 广播消息时, 会发起一个 alive 广播以修正其他节点上存储的本节点数据.
与 SWIM 不同的是, memberlist 将故障节点的状态保留一段时间, 以便可以在完整状态同步中传递有关故障节点的信息. 这有助于集群的状态更快地收敛.
事件传播
gossip goroutine 通过 udp 向 config.GossipNodes
个节点(一般设置为集群节点数/2)发送消息, 节点从广播队列里面获取消息, 广播队列里的消息发送失败超过一定次数后, 消息就会被丢弃.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | // gossip is invoked every GossipInterval period to broadcast our gossip // messages to a few random nodes. func (m *Memberlist) gossip() {// Get some random live, suspect, or recently dead nodesm.nodeLock.RLock()kNodes := kRandomNodes(m.config.GossipNodes, m.nodes, func(n *nodeState) bool {if n.Name == m.config.Name {return true}switch n.State {case StateAlive, StateSuspect:return falsecase StateDead:return time.Since(n.StateChange) > m.config.GossipToTheDeadTimedefault:return true}})m.nodeLock.RUnlock()for _, node := range kNodes {// Get any pending broadcastsmsgs := m.getBroadcasts(compoundOverhead, bytesAvail)if len(msgs) == 0 {return}addr := node.Address()if len(msgs) == 1 {// Send single message as isif err := m.rawSendMsgPacket(node.FullAddress(), &node.Node, msgs[0]); err != nil {m.logger.Printf("[ERR] memberlist: Failed to send gossip to %s: %s", addr, err)}} else {// Otherwise create and send a compound messagecompound := makeCompoundMessage(msgs)if err := m.rawSendMsgPacket(node.FullAddress(), &node.Node, compound.Bytes()); err != nil {m.logger.Printf("[ERR] memberlist: Failed to send gossip to %s: %s", addr, err)}}} } |
这点和 SWIM 有些不同, gossip goroutine 独立于故障检测组件发送 gossip 消息, 这使得我们可以手动调整这个过程的频次(可以比故障检测的频次更高), 以加快收敛速度.
扩展
memberlist 在 SWIM 的基础之上, 还增加了一些扩展
反熵
memberlist 添加了一种反熵机制, 通过该机制, 每个成员通过 TCP 定期与另一个随机选择的成员进行完整状态同步. 这种全状态同步增加了节点更快完全收敛的可能性, 但代价是更多的带宽消耗. 这种机制对于加快从网络分区的恢复来说特别有帮助.
push/pull goroutine 既是对该机制的实现, 其周期性的从已知的 alive 的集群节点中选1个节点进行push/pull 交换信息. 交换的信息包含2种:
- 集群信息: 节点数据
- 用户自定义的信息: 实现
Delegate
接口的struct
push/pull goroutine 可以加速集群内信息的收敛速度, 整体流程为:
- 建立TCP链接: 每隔一个时间间隔, 随机选取一个节点, 跟它建立tcp连接.
- 将本地的全部节点、状态、用户数据发送过去.
- 对端将其掌握的全部节点状态、用户数据发送回来, 然后完成2份数据的合并.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // pushPull is invoked periodically to randomly perform a complete state // exchange. Used to ensure a high level of convergence, but is also // reasonably expensive as the entire state of this node is exchanged // with the other node. func (m *Memberlist) pushPull() {// Get a random live nodem.nodeLock.RLock()// 随机抽取一个Alive的Node进行pushPullnodes := kRandomNodes(1, m.nodes, func(n *nodeState) bool {return n.Name == m.config.Name ||n.State != StateAlive})m.nodeLock.RUnlock()// If no nodes, bailif len(nodes) == 0 {return}node := nodes[0]// Attempt a push pullif err := m.pushPullNode(node.FullAddress(), false); err != nil {m.logger.Printf("[ERR] memberlist: Push/Pull with %s failed: %s", node.Name, err)} } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // pushPullNode does a complete state exchange with a specific node. func (m *Memberlist) pushPullNode(a Address, join bool) error {defer metrics.MeasureSince([]string{"memberlist", "pushPullNode"}, time.Now())// 发送自己的状态信息并获取目标Node的状态信息remote, userState, err := m.sendAndReceiveState(a, join)if err != nil {return err}// 合并状态信息if err := m.mergeRemoteState(join, remote, userState); err != nil {return err}return nil } |
Lifeguard
lifeguard 用于在出现消息处理缓慢(由于 CPU 不足、网络延迟或丢失等因素)的情况下使 memberlist 更加健壮. 例如在 CPU 耗尽的情况下, 不带 lifeguard 的 SWIM 与带 lifeguard 的 SWIM 误报数对比如下:
具体介绍可参阅 Lifeguard : SWIM-ing with Situational Awareness
劣势
末尾来总结下 Gossip 协议的一些劣势:
- 达成最终一致性的时间不确定性
- 消息延迟, 只能实现最终一致性, 传播过程中, 数据不一致
- 虽然可以通过各种参数调节, 但是由于协议本身事件传播方面的冗余性, 广播rpc消息量大, 对网络压力较大
- 拜占庭将军问题, 不允许存在恶意节点, 恶意节点的数据也会传遍整个集群(这点确实和八卦很像XD)
相关文章:

Gossip协议是什么
Gossip协议是什么 Gossip protocol 也叫 Epidemic Protocol (流行病协议), 是基于流行病传播方式的节点或者进程之间信息交换的协议, 也被叫做流言算法, 八卦算法、疫情传播算法等等. 说到 Gossip 协议, 就不得不提著名的六度分隔理论. 简单地说, 你和任何一个陌生人之间所间…...

【java学习】this关键字(27)
文章目录 1. this是什么?2. this的作用 1. this是什么? 在 java 中,this关键字比较难理解,它的作用和其词义很接近。 ①它在方法内部使用,即这个方法所属对象的引用; ②它在构造器内部使用,表示…...

27、元组
区分: 数组:纯粹 一个[]中的数据类型都是一致的 元组:不纯粹 一个[]中可能有不同类型的数据项 意义 当赋值或访问一个已知索引的元素时,可以得到正确的类型 let miao: [string, number] [cat, 18]; miao[0] cat miao[1] 18…...

1km分辨率逐月降雨量和最高温度数据集(1901-2022)--数据处理
1km分辨率逐月降雨量和最高温度数据集(1901-2022)的下载可以参考我的另外一篇博客: 这里的温度和降雨数据集都是NC格式的,需要将其处理为tif格式,我采用的处理软件是MATLAB。 本篇博客以处理温度数据为例,…...

docker入门加实战—docker常见命令
docker入门加实战—docker常见命令 在介绍命令之前,先用一副图形象的展示一下docker的命令: 常见命令 docker的常见命令和文档地址如下表: 命令说明文档地址docker pull拉取镜像docker pulldocker push推送镜像到DockerRegistrydocker pus…...

【C/C++】使用 g++ 编译器编译 C++ 程序的完全指南
本文介绍了 g 编译器的使用方法和常见参数解释,帮助您编译和构建 C 程序。 引言 在 C 程序开发中,选择一个合适的编译器是至关重要的。g 是 GNU 编译器集合(GCC)中的 C 编译器,提供了丰富的功能和选项,帮…...

ARM中断实验
设置按键中断,按键1按下,LED亮,再按一次,灭 按键2按下,蜂鸣器响。再按一次,不响 按键3按下,风扇转,再按一次,风扇停 main.c #include "uart1.h" #include …...

Vue条件渲染
一、使用v-show条件渲染 语法格式: v-show"表达式" // true 或 false 当表达式的值为true的时候就显示,表达式值为false的时候隐藏。 下面是使用v-show实现的一个点击按钮切换显示和隐藏的小案例 : 值得注意的是,使…...

k8s中如何使用gpu、gpu资源讲解、nvidia gpu驱动安装
前言 环境:centos7.9、k8s 1.22.17、docker-ce-20.10.9 gpu资源也是服务器中常见的一种资源,gpu即显卡,一般用在人工智能、图文识别、大模型等领域,其中nvidia gpu是nvidia公司生产的nvidia类型的显卡,amd gpu则是adm…...

VRRP 虚拟路由器冗余协议的解析和配置
VRRP的解析 个人简介 原理和HSRP的差不多,少了一些状态就只有了三种状态 还有不同的就是VRRP严格按照抢占要求 一个VRRP组中具有最高优先级的设备成为Master路由器缺省优先级为100若优先级相同,具有最高接口IP地址最大的路由器成为Master路由器抢占(Pr…...

旅游网站HTML
代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>旅游网</title> </head> <body><!--采用table编辑--> <!--最晚曾table,用于整个页面那布局--><table width&q…...

Unity - Normal mapping - Reoriented normal mapping - 重定向法线、混合法线
文章目录 目的核心代码PBR - Filament - Normal mappingShader效果BlendNormal_Hill12BlendNormal_UDNBlendNormals_Unity_Native - 效果目前最好 ProjectReferences 目的 备份、拾遗 核心代码 half3 blended_normal normalize(half3(n1.xy n2.xy, n1.z*n2.z));PBR - Filam…...

CSS 常用样式background背景属性
一、背景颜色 background-color CSS中的background-color是用来设置HTML元素的背景颜色的一个属性。它可以接受各种颜色值,包括具有名称的颜色和十六进制颜色值。 以下是一些示例代码: 设置元素的背景颜色为红色: background-color: red…...

Java开发利器,让你事半功倍!
Java是一种广泛使用的编程语言,它具有跨平台、面向对象、安全性高等特点,因此在企业级应用开发中得到了广泛的应用。在Java开发过程中,选择合适的开发工具可以大大提高工作效率,本文将为大家介绍几款Java开发利器,帮助…...

Redis面临的挑战
Redis的数据结构丰富,一般不会在功能性上造成困扰。但随着请求量的增加,SLA要求的提高,我们势必会对Redis进行一些改造和定制性开发。 高可用挑战 Redis提供了主从、哨兵、cluster等三种集群模式,其中cluster模式为目前大多数公…...

10月12日
3个按键中断 key_it.h #ifndef __KEY_IT_H__ #define __KEY_IT_H__ #include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_exti.h" #include "stm32mp1xx_gic.h" void key_it_config(); void led_init()…...

Windows 下 Qt 可执行程序添加默认管理员权限启动(QMAKE、MinGW MSVC)
记录 Qt/QMAKE 为可执行程序添加管理员权限 MSVC Windows下 MSVC 套件地位超然,只需要在 .pro 文件中加入: QMAKE_LFLAGS /MANIFESTUAC:\"level\requireAdministrator\ uiAccess\false\\"重新构建 MinGW 与MSVC相比,MinGW所需…...

深度思考面试常考sql题
1 推荐工具 在线运行SQL 2 阿里一面 3 百度一面 sql题 学生表student(id,name) 课程表course(id,name) 学生课程表student_course(sid,cid,score) CREATE TABLE student (id INT AUTO_INCREMENT PRIMARY KEY,name VARCHAR(50) NOT NULL ); CREATE TABLE course (id INT AU…...

使用springboot服务端远程调试? 试试HTTP实现服务监听
🎬 鸽芷咕:个人主页 🔥 个人专栏: 《初阶数据结构》《C语言进阶篇》 ⛺️生活的理想,就是为了理想的生活! 文章目录 前言1. 本地环境搭建1.1 环境参数1.2 搭建springboot服务项目 2. 内网穿透2.1 安装配置cpolar内网穿透2.1.1 wi…...

CSS图文悬停翻转效果完整源码附注释
实现效果截图 HTML页面源码 <!DOCTYPE html> <html><head><meta http-equiv="content-type...

MQTT C库下载
方法一、从Eclipse paho下载 https://eclipse.dev/paho/index.php?pagedownloads.php 方法二,从MQTT官网下载 https://mqtt.org/software/ https://os.mbed.com/teams/mqtt/code/MQTTPacket/ MQTTPacket源码和paho下载的差不多 方法三、从Keil5 包管理工具…...

android U广播详解(一)
概念介绍 进程队列 BroadcastQueueModernImpl 的设计围绕着为设备上的每个潜在进程维护一个单独的 BroadcastProcessQueue 实例。表明用于传送到特定进程的Pending {link BroadcastRecord} 条目队列。整个类都标记为 {code NotThreadSafe},因为调用者有责任始终与…...

input标签的23种type类型
一、概述 随着html5的出现,input标签新增了多种类型,用以接收各种类型的用户输入。其中传统输入控件有10种,新增输入控件有13种。 二、传统类型 传统输入控件有10种,如下所示。 text 定义单行文本输入框 password 定…...

分类预测 | MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输入分类预测
分类预测 | MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输入分类预测 目录 分类预测 | MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输入分类预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 1.MATLAB实现基于RF-Adaboost随机森林结合AdaBoost多输…...

解决echarts配置滚动(dataZoom)后导出图片数据不全问题
先展现一个echarts,并配置dataZoom,每页最多10条数据,超出滚动 <div class"echartsBox" id"echartsBox"></div>onMounted(() > {nextTick(() > {var chartDom document.getElementById(echartsBox);…...

【vue3+ts】项目初始化
1、winr呼出cmd,输入构建命令 //用vite构建 npm init vitelatest//用cli脚手架构建 npm init vurlatest2、设置vscode插件 搜索volar,安装前面两个 如果安装了vue2的插件vetur,要禁用掉,否则插件会冲突...

c++视觉图像----扩充边界
图像扩充边界 #include <opencv2/opencv.hpp> #include <opencv2/highgui/highgui.hpp>int main() {// 读取图像cv::Mat image cv::imread("1.jpg", cv::IMREAD_COLOR);if (image.empty()) {std::cerr << "Could not open or find the imag…...

邮政编码,格式校验:@ZipCode(自定义注解)
目标 自定义一个用于校验邮政编码格式的注解ZipCode,能够和现有的 Validation 兼容,使用方式和其他校验注解保持一致(使用 Valid 注解接口参数)。 校验逻辑 有效格式 不能包含空格;应为6位数字; 不校验…...

Appium自动化测试框架:关键字驱动+数据驱动
1. 关键字驱动框架简介 原理及特点 关键字驱动测试是数据驱动测试的一种改进类型,它也被称为表格驱动测试或者基于动作字的测试。主要关键字包括三类:被操作对象(Item)、操作行为(Operation)和操作值&…...

简单多状态dp【动态规划】
目录 一、按摩师 二、打家劫舍 三、删除并获得点数 四、粉刷房子 五、买卖股票的最佳时机 六、买卖股票的最佳时机(含手续费) 七、买卖股票的最佳时机III 八、买卖股票的最佳时机IV 一、按摩师 class Solution { public:int massage(vector<int>…...