一直都对分布式协议比较感兴趣,选择了 Gossip
和 Raft
作为起点,之所以这么选择有两个原因。
- 它们足够简单。
- 一个基于
AP
,一个基于CP
,分别是可用性优先和一致性优先的代表。
Gossip
Gossip
协议主要通过谣言传播的形式,传播给其他节点。
我这里称 Gossip
为 协议 而不是算法是因为这只是个思想,基于这个思想有很多的变种。
Gossip 能够正常运作需要以下三种实现组合。
- 广播
- 反熵(Anti-entropy)
- 谣言传播
反熵
反熵
其实就是通过推拉
的形式,将两个节点的数据进行交换,进而达成一致。之所以有了广播还要有反熵去推拉,是因为有可能缓存区满了,丢了数据,或者是一个新节点刚刚上线,它肯定就没办法得到之前广播出来的消息啦,那就需要反熵进行修复。
谣言传播
其实谣言传播和广播大多数时候都是做到一块的,换句话说 谣言传播是随机从节点里选K个进行广播。
Gossip 实现
主要分析 memberlist 的实现,其依赖于 Gossip
的变种,SWIM
: Scalable Weakly-consistent Infection-style Process Group Membership Protocol
每个节点有有以下四种状态,存活,怀疑,死亡,离开(相当于死亡的一种补充)。
1 | const ( |
Create
根据配置创建节点
1 | func Create(conf *Config) (*Memberlist, error) { |
newMemberlist
填充结构体,建立 TCP 与 UDP 连接。
1 | func newMemberlist(conf *Config) (*Memberlist, error) { |
TCP 处理
节点状态同步,Push-Pull,用户数据同步。
读出数据,根据消息类型进行操作,反熵体现在 pushPullMsg 这个类型中。
1 | func (m *Memberlist) handleConn(conn net.Conn) { |
UDP 处理
各种消息处理。
将数据转为命令进行处理,用户自定义数据分优先级。
1 | func (m *Memberlist) ingestPacket(buf []byte, from net.Addr, timestamp time.Time) { |
schedule
开三个协程
- probe 协程
- push-pull 协程
- gossip 协程
1 | func (m *Memberlist) schedule() { |
probe 协程
随机选取一个节点,然后通过UDP发送 ping 消息,如果不通则通过 indirect-ping 消息完成,意思是发给其他随机几个节点,由他们替你去 ping。
如果配置打开 TCP 开关,也会通过 TCP 去 ping(如果 TCP 判断存活,UDP间接判断不存活,还是认为存活)。
1 | func (m *Memberlist) probeNode(node *nodeState) { |
push-pull 协程
随机选 1 个节点,通过 UDP 进行推拉,反熵修复值。
1 | func (m *Memberlist) pushPull() { |
gossip 协程
根据配置随机找几个节点,通过 UDP 进行谣言传播,即从广播队列(TCP 同步节点状态的时候会将消息放入广播队列)中取出来进行广播。
1 | func (m *Memberlist) gossip() { |
如果配置打开 TCP 开关,也会通过 TCP 去 ping(如果 TCP 判断存活,UDP间接判断不存活,还是认为存活)。
结语
Gossip
是一个 AP
的分布式协议,总体来说还是比较简单的。