乐于分享
好东西不私藏

Python源码:解析Gossip协议实现,分布式系统就这样"八卦"起来的!

Python源码:解析Gossip协议实现,分布式系统就这样"八卦"起来的!

分布式系统怎么同步数据?全靠这个"八卦协议"!

各位分布式系统爱好者们好啊。

今天来点硬核的——聊聊分布式系统中大名鼎鼎的Gossip协议。

听到"八卦协议"这个名字,你是不是觉得很有意思。没错,它的设计灵感就是来自于人类社会的八卦传播——一个人告诉两个人,这两个人再各自告诉认识的两个人,以此类推。用不了多久,整个村子都知道你昨天吃了什么。

用Python实现一个简易Gossip协议

import asyncio
import random
import time
from dataclasses import dataclass, field
from typing import Dict, Set

@dataclass
classNode:
    node_id: str
    peers: Set['Node'] = field(default_factory=set)
    data: Dict[str, any] = field(default_factory=dict)
    version: Dict[str, int] = field(default_factory=dict)

defupdate(self, key: str, value: any):
        self.data[key] = value
        self.version[key] = self.version.get(key, 0) + 1
        print(f"[{self.node_id}] 更新 {key}={value} (v{self.version[key]})")

defmerge(self, peer_data: Dict[str, any], peer_version: Dict[str, int]):
        merged = False
for key, value in peer_data.items():
if key notin self.data or self.version[key] < peer_version[key]:
                self.data[key] = value
                self.version[key] = peer_version[key]
                merged = True
                print(f"[{self.node_id}] 从同伴更新 {key}={value}")
return merged

defselect_peer(self) -> 'Node':
ifnot self.peers:
raise ValueError(f"节点 {self.node_id} 没有同伴")
return random.choice(list(self.peers))

asyncdefgossip(self, target: 'Node'):
        print(f"[{self.node_id}] → [{target.node_id}] 开始八卦...")

await asyncio.sleep(0.1)
        merged_from_peer = target.merge(self.data, self.version)

await asyncio.sleep(0.1)
        merged_to_peer = self.merge(target.data, target.version)

if merged_from_peer or merged_to_peer:
            print(f"[{self.node_id}] ↔ [{target.node_id}] 数据同步完成")
else:
            print(f"[{self.node_id}] ↔ [{target.node_id}] 数据已经一致")

asyncdefrun_gossip_protocol():
    nodes = [Node(node_id=f"Node-{i}"for i in range(5)]

for node in nodes:
        other_nodes = [n for n in nodes if n != node]
        node.peers = set(random.sample(other_nodes, min(3, len(other_nodes))))

    print(f"节点连接建立完成")
for node in nodes:
        print(f"  {node.node_id} 连接到: {[n.node_id for n in node.peers]}")

    nodes[0].update("price"100)
    nodes[0].update("inventory"50)

    print("\n--- 开始Gossip传播 ---")

for round_num in range(14):
        print(f"\n=== 第 {round_num} 轮 ===")
        tasks = []

for node in nodes:
if node.peers:
                peer = node.select_peer()
                tasks.append(node.gossip(peer))

await asyncio.gather(*tasks)

        print("\n当前数据状态:")
for node in nodes:
            print(f"  {node.node_id}{node.data}")

    print("\n=== 最终一致状态 ===")
    print(f"所有节点数据一致: {[n.data for n in nodes]}")
return nodes

asyncio.run(run_gossip_protocol())

Gossip的三种传播策略

classGossipStrategies:

    @staticmethod
defpush(node, peer):
        peer.receive(node.data)

    @staticmethod
defpull(node, peer):
        node.receive(peer.data)

    @staticmethod
defpush_pull(node, peer):
        node.receive(peer.data)
        peer.receive(node.data)

Gossip协议的数学魅力——为什么它总能快速传播?

import math

defcalculate_spread_time(n_nodes: int, fanout: int = 3):
    rounds = math.ceil(math.log(n_nodes, fanout))

    print(f"节点数量: {n_nodes}")
    print(f"每轮选择的同伴数: {fanout}")
    print(f"预计传播轮数: O(log_{fanout}({n_nodes})) = {rounds} 轮")
    print(f"对比中心化方式: O(n) = {n_nodes} 轮")
    print(f"加速比: {n_nodes / rounds:.1f}x")

return rounds

calculate_spread_time(1000, fanout=3)
calculate_spread_time(1000000, fanout=3)

反熵与熵传播

classAntiEntropy:

    @staticmethod
defreconcile(node_a, node_b):
        all_keys = set(node_a.data.keys()) | set(node_b.data.keys())

for key in all_keys:
if node_a.version.get(key, 0) > node_b.version.get(key, 0):
                node_b.data[key] = node_a.data[key]
                node_b.version[key] = node_a.version[key]
elif node_b.version.get(key, 0) > node_a.version.get(key, 0):
                node_a.data[key] = node_b.data[key]
                node_a.version[key] = node_b.version[key]

classRumorMongering:

def__init__(self, max_propagate: int = 3):
        self.max_propagate = max_propagate

defshould_continue(self, node, key: str) -> bool:
return node.spread_count.get(key, 0) < self.max_propagate

Gossip协议的优缺点

Gossip协议的优点:
+ 去中心化,没有单点故障
+ 扩展性强,新增节点只需告诉邻居
+ 容错性好,节点挂了不影响其他节点
+ 最终一致性,保证数据最终会同步
+ 实现简单,代码量不大

Gossip协议的缺点:
- 不是强一致性,只是最终一致性
- 消息冗余,同一个消息可能传播多次
- 收敛时间不确定,取决于网络和拓扑
- 不适合实时性要求高的场景

适用场景:

  • 适合:服务发现、集群成员变更、数据复制、状态同步
  • 不适合:银行转账、库存扣减、需要强一致性的场景

混子哥带你读源码——简化的Gossip实现

import random
import time
from enum import Enum
from typing import List, Dict

classNodeState(Enum):
    HANDSHAKE = 0x01
    CONNECTED = 0x02
    DISCONNECTED = 0x03

classGossipMessage:
def__init__(self, node_id: str, state: NodeState, slots: List[int]):
        self.node_id = node_id
        self.state = state
        self.slots = slots

classSimplifiedRedisGossip:

def__init__(self, node_id: str):
        self.node_id = node_id
        self.nodes: Dict[str, GossipMessage] = {}
        self.slots = list(range(16384))

defreceive_gossip(self, messages: List[GossipMessage]):
for msg in messages:
            self.nodes[msg.node_id] = msg

if self.is_suspicious(msg):
                print(f"[{self.node_id}] 节点 {msg.node_id} 长时间未响应,标记为可疑")

if msg.state == NodeState.CONNECTED and msg.slots:
                self.handle_slot_ownership(msg)

defsend_gossip(self) -> List[GossipMessage]:
        target_nodes = random.sample(list(self.nodes.keys()), min(3, len(self.nodes)))
return [
            GossipMessage(
                node_id=self.node_id,
                state=NodeState.CONNECTED,
                slots=self.slots
            )
        ]

defis_suspicious(self, msg: GossipMessage) -> bool:
returnFalse

defhandle_slot_ownership(self, msg: GossipMessage):
pass

node1 = SimplifiedRedisGossip("node-1")
node2 = SimplifiedRedisGossip("node-2")

node1.receive_gossip(node2.send_gossip())
node2.receive_gossip(node1.send_gossip())