Gossip 协议
Gossip
协议,也叫做流言算法,或者疫情传播协议,在这个协议当中,所有的节点的角色地位都是一样的,节点间的数据怎么同步呢?大致的步骤如下
- 随机选取周边的k个节点
- 向这k个节点进行数据广播同步
- 这k个节点再次重复刚刚的操作
因此,最终一个消息经过多轮广播之后,所有的节点都同步了此数据。
gossip
协议存在三种数据同步方式
- Push : 节点 A 将数据 (key,value,version) 及对应的版本号推送给 B 节点,B 节点更新 A 中比自己新的数据
- Pull : A 仅将数据 key, version 推送给 B,B 将本地比 A 新的数据(Key, value, version)推送给 A,A 更新本地
- Push/Pull : 与 Pull 类似,只是多了一步,A 再将本地比 B 新的数据推送给 B,B 则更新本地
memberlist 的实现
那么现在来分析一下一个Gossip
的实现——hashicorp/memberlist
创建一个 Memberlist
1 | func Create(conf *Config) (*Memberlist, error) { |
接着这一块代码就很重要了,在scheuld
里面调度了三个重要的任务,这三个任务都有各自的指责,现在我们来看一下
1 | // Schedule is used to ensure the Tick is performed periodically. This |
probe
首先我们先来分析下m.probe
是做什么事情的
这里会通过m.config.ProbeInterval
参数来判断是否开启Probe
任务,如果设置了的话,则会开启一个定时的TimerTicker
1 | if m.config.ProbeInterval > 0 { |
接下来就是Probe
任务的正体了。
1 | // Tick is used to perform a single round of failure detection and gossip |
上面这段代码看着其实就挺简单的,其核心就是,判断该节点是否可以进行探测任务。总结下几个步骤
- 当前循环测试是否超过了节点数量,超过则跳出循环
- 当前节点的索引是否超过节点数量,是则重置索引位置,重新循环
- 当前节点是否是自己,或者是否以及处于死亡或者离开的状态,是则跳过重新循环
- 进行节点探测
接着我们来看probeNode(&node)
做的事情
1 | // probeNode handles a single round of failure checking on a node. |
至此,probe
任务到这里就讲完了
pushPullTrigger
接着,我们来看看m.pushPullTrigger
是做了什么
1 | // 这里是 gossip 的 push-pull 定时触发任务,并且这里 memberlist 做了一个优化:根据当前集群节点数据来决定,从而避免影响当前网络状况。 |
这里的pushPullScale
就是一个优化的措施,如果当前集群节点的数量超出了阈值,则进行scale操作。
1 | func pushPullScale(interval time.Duration, n int) time.Duration { |
这里是通过 push-pull 操作,与对方节点交换自己的全部信息,这实际上是一个很昂贵的操作,因为要交换的数据可能很多很大。因此在一个 tick 内只会与一个节点进行一次 push-pull 操作
1 | func (m *Memberlist) pushPull() { |
当通过随机选取K个节点完成之后,会选择这k个节点的第一个节点进行两个节点间的数据同步操作。
1 | // pushPullNode does a complete state exchange with a specific node. |
那自己节点发送的数据是什么呢?也就是m.sendAndReceiveState
这一步做了什么事情
1 | // sendLocalState is invoked to send our local state over a stream connection. |
因此,到这里,push-pull操作中的push操作就完成了,其pull操作也基本类似,将自己的信息推给对方节点之后,就准备“拉取对方节点的数据”,在接收到对方节点发送来的消息之后,需要将对方节点的数据与自己的进行合并
1 | // mergeRemoteState is used to merge the remote state with our local state |
到这里,push-pull的流程就走完了。
Gossip
现在,我们来看最后一步,也就是Gossip
,这一步就是将自己的数据随机的广播给多个节点去。
1 | // gossip is invoked every GossipInterval period to broadcast our gossip |