众所周知周知,疫情仍然在全球各地肆虐。据最新数据统计,截至北京时间 2020-05-28,全球累计确诊 5698703 例,累计死亡 352282 例,累计治愈 2415237 例。html
从上面的统计数据,咱们能够看出,新冠病毒在人与人之间的传播是极其高效的,且影响范围广。若是咱们把「新冠病毒」想象成一小段数据,将「人与人之间传播」想象成数据交换,那么,咱们能够得出结论,在不考虑免疫系统和人为干预等一些因素,通过反复迭代,数据(新冠病毒)能够被发送(感染)到每一个节点(人)上。node
这个就是今天要介绍的 Gossip 协议,该协议早在 1987 年就被发表在 ACM 上的论文《Epidemic Algorithms for Replicated Database Maintenance》中。当时主要用在分布式数据库系统中各个副本节点间同步数据。git
Gossip 协议分为 Push-based 和 Pull-based 两种模式,具体工做流程以下:github
Push-based 的 Gossip 协议:数据库
Pull-based 的 Gossip 协议,正好相反:微信
这边简单分析下 HashiCorp 公司的 Serf 的核心库 Memberlist。这家公司研发了 Consul(基于 raft 实现的分布式存储)、Vagrant(声明式虚拟机编排)等优秀的产品。最近因为中美矛盾升级,也陷入到了舆论的漩涡中,爆出禁止在中国使用他们的产品的传闻。不过,这是题外话。网络
Memberlist 这个 Golang 的代码库,基于 Gossip 协议,实现了集群内节点发现、 节点失效探测、节点故障转移、节点状态同步等。app
其核心实现的大体以下:dom
发送端处理流程:分布式
// Create a gossip ticker if needed
if m.config.GossipInterval > 0 && m.config.GossipNodes > 0 {
t := time.NewTicker(m.config.GossipInterval) go m.triggerFunc(m.config.GossipInterval, t.C, stopCh, m.gossip) m.tickers = append(m.tickers, t)
}
// gossip is invoked every GossipInterval period to broadcast our gossip
// messages to a few random nodes.
func (m *Memberlist) gossip() {
defer metrics.MeasureSince([]string{"memberlist", "gossip"}, time.Now()) // Get some random live, suspect, or recently dead nodes m.nodeLock.RLock() kNodes := kRandomNodes(m.config.GossipNodes, m.nodes, func(n *nodeState) bool { if n.Name == m.config.Name { return true } switch n.State { case StateAlive, StateSuspect: return false case StateDead: return time.Since(n.StateChange) > m.config.GossipToTheDeadTime default: return true } }) m.nodeLock.RUnlock() // ... for _, node := range kNodes { // Get any pending broadcasts msgs := m.getBroadcasts(compoundOverhead, bytesAvail) if len(msgs) == 0 { return } addr := node.Address() if len(msgs) == 1 { // Send single message as is if err := m.rawSendMsgPacket(node.FullAddress(), &node.Node, msgs[0]); err != nil { m.logger.Printf("[ERR] memberlist: Failed to send gossip to %s: %s", addr, err) } } else { // Otherwise create and send a compound message compound := makeCompoundMessage(msgs) if err := m.rawSendMsgPacket(node.FullAddress(), &node.Node, compound.Bytes()); err != nil { m.logger.Printf("[ERR] memberlist: Failed to send gossip to %s: %s", addr, err) } } }
}
接收端:
// packetListen is a long running goroutine that pulls packets out of the
// transport and hands them off for processing.
func (m *Memberlist) packetListen() {
for { select { case packet := <-m.transport.PacketCh(): m.ingestPacket(packet.Buf, packet.From, packet.Timestamp) case <-m.shutdownCh: return } }
}
func (m *Memberlist) ingestPacket(buf []byte, from net.Addr, timestamp time.Time) {
// ... // See if there's a checksum included to verify the contents of the message if len(buf) >= 5 && messageType(buf[0]) == hasCrcMsg { crc := crc32.ChecksumIEEE(buf[5:]) expected := binary.BigEndian.Uint32(buf[1:5]) if crc != expected { m.logger.Printf("[WARN] memberlist: Got invalid checksum for UDP packet: %x, %x", crc, expected) return } m.handleCommand(buf[5:], from, timestamp) } else { m.handleCommand(buf, from, timestamp) }
}
看了 Memberlist 的实现,不免会有这样的疑问,为何要使用 Gossip 协议,直接在集群内广播不香么?接下来,咱们能够经过 Gossip 协议的优缺点来分析,使用 Gossip 协议的意义。
优势:
缺点:
今天对 Gossip 的协议就简单介绍到这里,若是有同窗对内容感兴趣,能够回复评论,咱们私下多多探讨和交流。
https://en.wikipedia.org/wiki...
https://github.com/hashicorp/...
https://github.com/hashicorp/...
https://zhuanlan.zhihu.com/p/...
https://www.jianshu.com/p/de7...