Gossip协议,又称epidemic协议,基于流行病传播方式的节点或进程之间信息交换的协议,在分布式系统中被广泛使用。
在1987年8月由施乐-帕洛阿尔托研究中心发表ACM上的论文《Epidemic Algorithms for Replicated Database Maintenance》中被提出。原本用于分布式数据库中节点同步数据使用,后被广泛用于数据库复制、信息扩散、集群成员身份确认、故障探测等。
六度分隔理论(Six Degrees of Separation):一个人通过6个中间人可以认识世界任何人。数学公式: n = l o g ( N ) l o g ( W ) n=\frac{log(N)}{log(W)} n=log(W)log(N),n
表示复杂度,N
表示人的总数,W
表示每个人的联系宽度。依据邓巴数,即一个人认识150人,其六度就是 15 0 6 150^6 1506=11,390,625,000,000(约11.4万亿)。
基于六度分隔理论,任何信息的传播其实非常迅速,且网络交互次数不会很多。
Gossip协议利用一种随机的方式将信息传播到整个网络中,并在一定时间内使得系统内的所有节点数据一致。一种去中心化思路的分布式协议,解决状态在集群中的传播和状态一致性的保证两个问题。
Gossip协议执行过程:
A->B
,则B进行散播时,不再发给A。Goosip协议的信息传播和扩散通常需要由种子节点发起。整个传播过程可能需要一定的时间,由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,因此它是一个最终一致性协议。
Gossip协议是一个多主协议,所有写操作可以由不同节点发起,并且同步给其他副本。Gossip内组成的网络节点都是对等节点,是非结构化网络。
Gossip协议可以支持以下需求:
使用Gossip协议的技术组件或框架:
消息传播方式有两种:
一般来说,为了在通信代价和可靠性之间取得折中,需要将这两种方法结合使用。
反熵传播是以固定的概率传播所有的数据。所有参与节点只有两种状态:Suspective(病原)、Infective(感染)。这种模型叫做simple epidemics,SI model。处于infective状态的节点代表其有数据更新,并且会将这个数据分享给其他节点;处于susceptible状态的节点代表其并没有收到来自其他节点的更新。
种子节点会把所有的数据都跟其他节点共享,以便消除节点之间数据的任何不一致,它可以保证最终、完全的一致。缺点是消息数量非常庞大,且无限制;通常只用于新加入节点的数据初始化。
每个节点周期性地随机选择其他节点,然后通过互相交换自己的所有数据来消除两者之间的差异。这种方法非常可靠,但是每次节点两两交换自己的所有数据会带来非常大的通信负担,因此不会频繁使用。
谣言传播是以固定的概率仅传播新到达的数据。所有参与节点有三种状态:Suspective(病原)、Infective(感染)、Removed(愈除)。这种模型叫做complex epidemics,SIR model。相比Anti-Entropy多一种状态:removed,处于removed状态的节点说明其已经接收到来自其他节点的更新,但是其并不会将这个更新分享给其他节点。
Rumor消息会在某个时间标记为removed,然后不会发送给其他节点,所以Rumor-Mongering类型的Gossip协议有极小概率使得更新不会达到所有节点。
消息只包含最新update,谣言消息在某个时间点之后会被标记为removed,并且不再被传播。缺点是系统有一定的概率会不一致,通常用于节点间数据增量同步。
当一个节点有新的信息后,这个节点变成活跃状态,并周期性地联系其他节点向其发送新信息。直到所有的节点都知道该新信息。因为节点之间只是交换新信息,所以大大减少通信的负担。
Anti-Entropy和Rumor-Mongering都涉及到节点间的数据交互方式,节点间的交互方式主要有三种:Push、Pull及Push&Pull。
如果把两个节点数据同步一次定义为一个周期,则在一个周期内,Push需通信1次,Pull需2次,Push/Pull则需3次。消息数增加,但从效果上来讲,Push/Pull最好,理论上一个周期内可以使两个节点完全一致。直观上,Push/Pull的收敛速度也是最快的。
O(logN)
轮就可以将信息传播到所有的节点,其中N
代表节点的个数。每个节点仅发送固定数量的消息,并且与网络中节点数目无法。在数据传送的时候,节点并不会等待消息的ack,所以消息传送失败也没有关系,因为可以通过其他节点将消息传递给之前传送失败的节点。系统可以轻松扩展到数百万个进程。由于以上优缺点,适合于AP场景的数据一致性处理,常见应用有:P2P网络通信、Apache Cassandra、Redis Cluster、Consul。
Consul使用两种不同的Gossip池:
Consul在gossip上的实现实际上是使用的memberlist库,其实现集群内节点发现、节点失效探测、节点故障转移、节点状态同步等。
节点状态有3种
Redis3.0版本加入Redis Cluster,主从架构的Redis Cluster架构图:
其中虚线表示各个节点之间的Gossip通信。
Gossip协议是个松散的协议,没有对数据交换的格式做特别的约束,各框架可自由设定实现机制。Redis Cluster有以下9种消息类型的定义,详情可见注释。
memberlist是hashicorp开源的go语言实现版本,参考GitHub。
GitHub给出的README文档:
list, err := memberlist.Create(memberlist.DefaultLocalConfig())
if err != nil {
panic("Failed to create memberlist: " + err.Error())
}
// Join an existing cluster by specifying at least one known member.
n, err := list.Join([]string{"1.2.3.4"})
if err != nil {
panic("Failed to join cluster: " + err.Error())
}
// Ask for members of the cluster
for _, member := range list.Members() {
fmt.Printf("Member: %s %s\n", member.Name, member.Addr)
}
与memberlist交互入口就是Config配置struct类,源码见链接。
这个类里面定义各种配置,如BindAddr、BindPort、AdvertiseAddr、AdvertisePort。同时基于Config,有3种实现类方便初始化一个Gossip集群:
memberlist提供的功能主要分为两块:维护成员状态(gossip)及数据同步(boardcast、SendReliable)。