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