引言
上篇文章中,我描述了metallb一个项目上碰到的问题,引发我对metallb vip 如何实现的源码追溯,但是并没有去深究 metallb speaker 的一致性 memberlist 是怎么实现的,以及 gossip 协议。所以就这个问题的契机,继续往下挖。
gossip 协议
cloud.tencent.com/developer/a...
gossip 协议,这两篇文档讲得很好了。
gossip 的实现 memberlist 库
memberlist 是HashiCorp公司出品的go语言开发库,使用基于Gossip协议管理集群成员和成员失败检测。咱们本文的主题就是memberlist。严格说起来,memberlist是基于Gossip协议变种实现的,它的指导论文是康奈尔大学计算机科学系Abhinandan Das, Indranil Gupta, Ashish Motivala在2002年发表的《SWIM:Scalable Weakly-consistent/Infection-styleProcess Group Membership Protocol》。
create
Create 初始化:
- newMemberlist 会根据给定的配置创建一个新的 Memberlist 的数据结构。
- schedule 开启协程 probe 协程, push/pull 协程,gossip 协程,其中
- probe 协程:进行节点探测,一个个去探测(不是随机)节点,默认探测间隔 1s ,超时时间 500ms 。
- push/pull 协程:进行节点状态、用户数据同步,随机找到一个节点,将本地所有节点状态和对端所有节点状态互换,然后合并。因为是全量同步,占用资源大,所以频率并不高,默认 30s 一次。
- gossip 协程:从广播队列中获取 gossip 协议的报文,随机选 nodes 进行 udp 广播,gossip 协议的报文,发送gossip 报文频率默认 200ms。
join 加入集群
用于将现有的Memberlist加入到一个集群中,通过联系所有给定的主机并执行状态同步
- 列出所有需要同步状态的主机,执行pushPullNode
- pushPullNode用于交换本节点和远程节点的状态,初始的时候 Memberlist 只包含自己的状态,因此执行此操作将使远程节点意识到自己的存在,从而有效加入集群。
- 返回成功联系到的主机数量,如果无法联系到任何主机则返回错误,表示该节点未能加入集群。
节点之间的交互细节
从 Create 里面创建的三个协程说起:
Probe 协程
schedule 里面开启 1s 一次的探测,
从m.nodes里面每次选取一个节点去探测,skip 掉 Dead 或者 Left 状态的节点,进入核心函数 probeNode
probeNode 流程我大致列了下:
- 根据健康意识调整 probe 的周期,如果侦查出现问题,会降低探测频率。
- 准备 ping 消息并设置 ack 处理器。
- 发送 ping 消息到节点,如果节点状态为 Alive, 则通过 UDP 发送 ping 消息; 否则,将 ping 消息和 suspect 消息组合成一个复合消息,通过UDP 发送。附加 supect 的原因是为了让对端尽快否认。
- 等待响应或者超时,如果收到 ack 消息,表示探测成功,函数返回。如果未收到,进入失败处理。
- 失败处理:获取一些随机的alive 节点,发送间接 ping 从这些节点到探测失败节点。 同时尝试TCP 直接和探测失败节点建立连接,等待ack 或者超时,如果收到 ack,表示探测成功,函数返回。
- 如果继续失败,执行suspectNode操作。suspectNode的会启动suspect 计时器,然后广播suspect消息。如果一定时间内还没等到其他节点的确认信息,可疑计时器会触发超时回调。进而进入deadNode
- deadNode 广播目标节点dead的消息。并触发回调NotifyLeave。
补充下,5步的 suspect 计时器有两种算法:
lua
min := suspicionTimeout(m.config.SuspicionMult, n, m.config.ProbeInterval)
max := time.Duration(m.config.SuspicionMaxTimeoutMult) * min
如果 节点数大于 SuspicionMult 使用 max 计时,如果小于 SuspicionMult 使用 min计时。
push/pull 协程
交换和远程节点的节点状态,还有用户自定义信息。
gossip 协程
gossip 用来发送广播报文,上面 probeNode 流程中发送的 suspect 或者 dead 这些广播报文,并不是立刻发送的,而是先放进 broadcast 的消息队列里面,默认情况下 gossip 协程以 200ms 一次的频率调用getBroadcasts 去取这个消息,然后随机kRandomNodes选 node广播出去。