一.Gossip 协议的执行过程:
Gossip 过程是由种子节点发起,当一个种子节点有状态需要更新到网络中的其他节点时,它会随机的选择周围几个节点散播消息,收到消息的节点也会重复该过程,直至最终网络中所有的节点都收到了消息。这个过程可能需要一定的时间,由于不能保证某个时刻所有节点都收到消息,但是理论上最终所有节点都会收到消息,因此它是一个最终一致性协议。
二.算法原理
三.Gossip类型
Gossip 有两种类型:
其实,Anti-entropy 反熵是一个很奇怪的名词,之所以定义成这样,Jelasity 进行了解释,因为 entropy 是指混乱程度(disorder),而在这种模式下可以消除不同节点中数据的 disorder,因此 Anti-entropy 就是 anti-disorder。换句话说,它可以提高系统中节点之间的 similarity。
在 SI model 下,一个节点会把所有的数据都跟其他节点共享,以便消除节点之间数据的任何不一致,它可以保证最终、完全的一致。由于在 SI model 下消息会不断反复的交换,因此消息数量是非常庞大的,无限制的(unbounded),这对一个系统来说是一个巨大的开销。
但是在 Rumor Mongering (SIR Model) 模型下,消息可以发送得更频繁,因为消息只包含最新 update,体积更小。而且,一个 Rumor 消息在某个时间点之后会被标记为 removed,并且不再被传播,因此,SIR model 下,系统有一定的概率会不一致。
而由于,SIR Model 下某个时间点之后消息不再传播,因此消息是有限的,系统开销小。
四.Gossip 中的通信模式
具体而言Gossip Protocol的通信模式可以分为Push-based、Pull-based和Pull/Push三种。
Push-based Gossip Protocol的具体工作流程如下:
Pull-based Gossip Protol的协议过程刚好相反:
六.gossip配置源码分析
type Config struct {
// 节点的名字,其在列表中唯一
Name string
//Transport是一个用于提供与其他节点通信的关联,如果保留为nil,则默认为memberlist。
//使用此结构中的BindAddr和BindPort创建NetTransport。
Transport Transport
// 与绑定到哪个地址和侦听的端口有关的配置。该端口用于UDP和TCP八卦。
// 默认其他节点在这个端口上运行,但不是必须
BindAddr string
BindPort int
// 与向其他群集成员advertise的地址有关的配置。用于NAT遍历。
AdvertiseAddr string
AdvertisePort int
// ProtocolVersion is the configured protocol version that we
// will _speak_. This must be between ProtocolVersionMin and
// ProtocolVersionMax.
ProtocolVersion uint8
// TCPTimeout是与远程节点建立流连接以进行完全状态同步以及流读写操作的超时。
TCPTimeout time.Duration
// 间接检测是在直接检测失败的情况下,要求节点执行间接检测的节点数。成员列表等待来自任何单个间接节点的ack,因此增加这个数目将增加以带宽为代价的间接探测成功的可能性。
IndirectChecks int
// ReTrimeMult是对通过八卦广播的消息尝试重传次数的乘数。重传的实际计数使用公式计算:
// Retransmits = RetransmitMult * log(N+1)
//这允许重传与簇大小适当地缩放。RetransmitMult ,失败的广播越有可能以增加的带宽为代价收敛。 //
RetransmitMult int
// 无法访问的节点在声明death之前被认为是suspicion,超出suspicion时间则被认为节点death
// 计算公式如下
// SuspicionTimeout = SuspicionMult * log(N+1) * ProbeInterval
SuspicionMult int
// ProbeInterval和ProbeTimeout用于配置探测成员列表。
//
// ProbeInterval是随机节点探测之间的间隔。设置
//更低会导致memberlist(更频繁)的进行随机节点间的探测
//
// ProbeTimeout是等待来自被探测节点的确认的超时,取决于您网络上的RTT(往返时间)。
ProbeInterval time.Duration
ProbeTimeout time.Duration
// DisableTcpPings will turn off the fallback TCP pings that are attempted
// if the direct UDP ping fails. These get pipelined along with the
// indirect UDP pings.
DisableTcpPings bool
// AwarenessMaxMultiplier will increase the probe interval if the node
// becomes aware that it might be degraded and not meeting the soft real
// time requirements to reliably probe other nodes.
AwarenessMaxMultiplier int
// GossipInterval and GossipNodes被用于配置gossip的成员列表
//GossipNodes是发送八卦消息的随机节点数
GossipInterval time.Duration
GossipNodes int
GossipToTheDeadTime time.Duration
}
EventDelegate是一个更简单的委托,仅用于接收有关成员加入和离开的通知。
type EventDelegate interface {
//当检测到某个节点已加入时,将调用NotifyJoin。
//不得修改Node参数。
NotifyJoin(* Node)
//当检测到一个节点离开时,调用NotifyLeave。
//不得修改Node参数。
NotifyLeave(* Node)
//检测到节点时调用NotifyUpdate
//更新,通常涉及元数据。Node参数
//不得修改。
NotifyUpdate(* 节点)
}
transport用于抽象与其他对等方的通信。假设分组接口是尽力而为,并且假定流接口是可靠的。
type Transport interface {
// FinalAdvertiseAddr给出用户配置的值(可能是空的),并返回所需的IP和端口,
//以便向集群的其余部分进行advertise。
FinalAdvertiseAddr(ip string, port int) (net.IP, int, error)
// WriteTo在传输中帮助在探测期间进行准确的RTT测量
WriteTo(b []byte, addr string) (time.Time, error)
// PacketCh返回可被读取以接收来自其他对等体的传入分组的信道。
PacketCh() <-chan *Packet
// 传输超时控制
DialTimeout(addr string, timeout time.Duration) (net.Conn, error)
// StreamCh返回一个可以读取的通道来处理来自其他对等点的传入流连接
StreamCh() <-chan net.Conn
// 关闭节点
Shutdown() error
}
七.gossip流程分析
1 )节点状态
节点的state有3种
2.节点动作
state.go
// 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()
// If we already have tickers, then don't do anything, since we're
// scheduled
if len(m.tickers) > 0 {
return
}
// Create the stop tick channel, a blocking channel. We close this
// when we should stop the tickers.
stopCh := make(chan struct{})
// Create a new probeTicker
if m.config.ProbeInterval > 0 {
t := time.NewTicker(m.config.ProbeInterval)
// 动作1
go m.triggerFunc(m.config.ProbeInterval, t.C, stopCh, m.probe)
m.tickers = append(m.tickers, t)
}
// Create a push pull ticker if needed
if m.config.PushPullInterval > 0 {
// 动作2
go m.pushPullTrigger(stopCh)
}
// Create a gossip ticker if needed
if m.config.GossipInterval > 0 && m.config.GossipNodes > 0 {
t := time.NewTicker(m.config.GossipInterval)
// 动作3
go m.triggerFunc(m.config.GossipInterval, t.C, stopCh, m.gossip)
m.tickers = append(m.tickers, t)
}
// If we made any tickers, then record the stopTick channel for
// later.
if len(m.tickers) > 0 {
m.stopTick = stopCh
}
}
动作1 probe
周期性的探测所有集群中状态为alive和suspect的节点
动作2 pushpull
周期性的从已知的alive的集群节点中选1个节点进行push/pull交换信息
交换的信息包含2种
a) 集群信息
b) 用户自定义的状态信息
动作3 gossip
其实它是广播所有处于dead的节点(只广播一次)
注意
这里需要说明的是
1)广播其实也是一种Gossip,发布者并不把消息发给集群中的每一个节点,而是随机挑选n个(默认是3个),将消息发送出去
2)处于dead状态的节点,仍然会被保留在集群信息中一段时间,以便Push/Pull的时候,这个状态能够被扩散出去