简单实现 Gossip 协议

gossip 协议是为实现最终一致性提出的。

实现思路

每个节点都有基本属性,如 id, addr, port
每个节点都有成员列表 members,存储一部分数据 data
通过定时随机节点发送请求,同步成员列表给随机节点,这样就能达到成员列表最终一致性
客户端访问数据,先根据 key 来计算 hash 值,对成员列表取余,确定是哪个节点上,要么返回数据要么转发请求

实现代码

每个节点的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type Node struct {
	Id   ID
	Addr string
	Port int
	// 上次联系时间
	lastContact time.Time
	// 所有的节点列表
	members map[ID]*Node
	// 每个节点都会携带一部分数据
	data map[string]string
}

向随机节点发送同步请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
func (n *Node) sync() {
	// 随机选择一个节点
	targetNode := n.selectRandomNode()
	if targetNode == nil {
		log.Printf("%s, not sync, targetNode is nil", n)
		return
	}

	// 发送请求
	err := n.call(targetNode, n.members)
	if err != nil {
		// 节点超时, 应该加入失败节点列表,然后广播所有节点判断是否应该剔除
		log.Printf("self: %s call fail, targetNode is %v", n, targetNode)
		return
	}
	log.Printf("self: %s sync success, targetNode is %v", n, targetNode)
	targetNode.lastContact = time.Now()
}

随机节点处理请求:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 模拟被调用方的逻辑
func (n *Node) call(targetNode *Node, members map[ID]*Node) error {
	// 更新成员
	for id, m := range members {
		if _, ok := targetNode.members[id]; !ok {
			targetNode.members[id] = m
		}
	}
	// 更新时间
	targetNode.members[n.Id] = n
	targetNode.members[n.Id].lastContact = time.Now()
	return nil
}

示例代码

demo-gossip-protocol

0%