您当前的位置:首页 > 电脑百科 > 程序开发 > 算法

用 Golang 快速实现 Paxos 分布式共识算法

时间:2020-12-15 11:51:58  来源:  作者:

前文《理解 Paxos》只包含伪代码,帮助了理解但又不够爽,既然现在都讲究 Talk is cheap. Show me the code.这次就把文章中的伪代码用 Go 语言实现出来,希望能帮助各位朋友更直观的感受 Paxos 论文中的细节。

但我们需要对算法做一些简化,有多简单呢?我们不持久化存储任何变量,并且用 chan直接代替 RPC 调用。

代码地址:https://github.com/tangwz/paxos/tree/naive

记得切换到 naive 分支。

 

定义相关结构体

我们定义 Proposer 如下:

type proposer struct {
// server id
id int
// the largest round number the server has seen
round int
// proposal number = (round number, serverID)
number int
// proposal value
value string
acceptors map[int]bool
net network
}

这些结构体成员都很容易理解,其中 acceptors我们主要用来存储 Acceptors 的地址,以及记录我们收到 Acceptor 的成功/失败响应。

Acceptor 的结构体:

type acceptor struct {
// server id
id int
// the number of the proposal this server will accept, or 0 if it has never received a Prepare request
promiseNumber int
// the number of the last proposal the server has accepted, or 0 if it never accepted any.
acceptedNumber int
// the value from the most recent proposal the server has accepted, or if it has never accepted a proposal
acceptedValue string
learners int
net network
}

主要成员解释都有注释,简单来说我们需要记录三个信息:

  • promiseNumber:承诺的提案编号

  • acceptedNumber:接受的提案编号

  • acceptedValue:接受的提案值

 

定义消息结构体

消息结构体定义了 Proposer 和 Acceptor 之间、Acceptor 和 Leaner 之间的通讯协议。最主要的还是 Paxos 的两阶段的四个消息。

  • Phase 1 请求:提案编号

  • Phase 1 响应:如果有被 Accepted 的提案,返回提案编号提案值

  • Phase 2 请求:提案编号提案值

  • Phase 2 响应:Accepted 的提案编号提案值

这样看,我们的消息结构体只需要提案编号和提案值,加上一个消息类型,用来区分是哪个阶段的消息。消息结构体定义在 message.go 文件,具体如下:

// MsgType represents the type of a paxos phase.
type MsgType uint8const (Prepare MsgType = iota
PromiseProposeAccept)type message struct {
tp MsgTypefrom intto intnumber int // proposal number
value string // proposal value
}

 

实现网络

网络上可以做的选择和优化很多,但这里为了保持简单的原则,我们将网络定义成 interface。后面完全可以改成 RPC 或 API 等其它通信方式来实现(没错,我已经实现了一个 Go RPC 的版本了)。

type network interface {
send(m message)recv(timeout time.Duration) (message, bool)
}

接下里我们去实现 network 接口:

type Network struct {
queue map[int]chan message
}func newNetwork(nodes ...int) *Network {
pn := &Network{queue: make(map[int]chan message, 0),
}for _, a := range nodes {
pn.queue[a] = make(chan message, 1024)
}return pn
}func (net *Network) send(m message) {
log.Printf("net: send %+v", m)
net.queue[m.to] <- m}func (net *Network) recvFrom(from int, timeout time.Duration) (message, bool) {
select {
case m := <-net.queue[from]:
log.Printf("net: recv %+v", m)
return m, true
case <-time.After(timeout):return message{}, false
}}

就是用 queue来记录每个节点的chan,key 则是节点的 server id。

发送消息则将 Message发送到目标节点的chan中,接受消息直接从chan中读取数据,并等待对应的超时时间。

不需要做其它网络地址、包相关的东西,所以非常简单。具体在 network.go文件。

 

实现单元测试

这个项目主要使用 go 单元测试来检验正确性,我们主要测试两种场景:

  • TestSingleProposer(单个 Proposer)

  • TestTwoProposers(多个 Proposer)

测试代码通过运行 Paxos 后检查 Chosen 返回的提案值是否符合预期。

 

实现算法流程

按照角色将文件分为 proposer.go, acceptor.go 和 learner.go,每个文件都有一个 run函数来运行程序,run函数执行条件判断,并在对应的阶段执行对应的函数。

按照伪代码描述,我们很容易实现 Phase 1 和 Phase 2,把每个阶段的请求响应都作为一个函数,我们一步步来看。

 

第一轮 Prepare RPCs 请求阶段:

// Phase 1. (a) A proposer selects a proposal number n

// and sends a prepare request with number n to

// a majority of acceptors.

func (p *proposer) prepare message {
p.round++p.number = p.proposalNumbermsg := make(message, p.majority)i := 0
for to := range p.acceptors {
msg[i] = message{tp: Prepare,
from: p.id,
to: to,
number: p.number,
}i++if i == p.majority {
break}}return msg
}// proposal number = (round number, serverID)
func (p *proposer) proposalNumber int {
return p.round<< 16 | p.id
}

Prepare 请求阶段我们将 round+1 然后发送给多数派 Acceptors。

注:这里很多博客和教程都会将 Prepare RPC 发给所有的Acceptors,6.824 的 paxos 实验就将 RPC 发送给所有 Acceptors。这里保持和论文一致,只发送给 a majority of acceptors。

 

第一轮 Prepare RPCs 响应阶段:

接下来在 acceptor.go文件中处理请求:

func (a *acceptor) handlePrepare(args message) (message, bool) {
if a.promiseNumber >= args.number {
return message{}, false
}a.promiseNumber = args.numbermsg := message{tp: Promise,from: a.id,to: args.from,number: a.acceptedNumber,value: a.acceptedValue,}return msg, true
}
  • 如果 args.number大于acceptor.promiseNumber,则承诺将不会接收编号小于args.number的提案(即a.promiseNumber = args.number)。如果之前有提案被 Accepted 的话,响应还应包含 a.acceptedNumber 和 a.acceptedValue。

  • 否则忽略,返回 false

 

第二轮 Accept RPCs 请求阶段:

func (p *proposer) accept message {
msg := make(message, p.majority)
i := 0
for to, ok := range p.acceptors {
if ok {
msg[i] = message{tp: Propose,from: p.id,to: to,number: p.number,value: p.value,}i++}if i == p.majority {
break
}}return msg
}

当 Proposer 收到超过半数 Acceptor 的响应后,Proposer 向多数派的 Acceptor 发起请求并带上提案编号和提案值。

 

第二轮 Accept RPCs 响应阶段:

func (a *acceptor) handleAccept(args message) bool {
number := args.numberif number >= a.promiseNumber {a.acceptedNumber = numbera.acceptedValue = args.valuea.promiseNumber = numberreturn true
}return false
}

Acceptor 收到 Accept请求,在这期间如果 Acceptor 没有对比 a.promiseNumber 更大的编号另行 Promise,则接受该提案。

 

别忘了:Learning a Chosen Value

在 Paxos 中有一个十分容易混淆的概念:Chosen Value 和 Accepted Value,但如果你看过论文,其实已经说得非常直接了。论文的 2.3 节 Learning a Chosen Value 开头就说:

To learn that a value has been chosen, a learner must find out that a proposal has been accepted by a majority of acceptors.

所以 Acceptor 接受提案后,会将接受的提案广播 Leaners,一旦 Leaners 收到超过半数的 Acceptors 的 Accepted 提案,我们就知道这个提案被 Chosen 了。

func (l *learner) chosen (message, bool) {
acceptCounts := make(map[int]int)
acceptMsg := make(map[int]message)
for _, accepted := range l.acceptors {
if accepted.number != 0 {
acceptCounts[accepted.number]++acceptMsg[accepted.number] = accepted}}for n, count := range acceptCounts {
if count >= l.majority {
return acceptMsg[n], true
}}return message{}, false
}

 

运行和测试

代码拉下来后,直接运行:

go test

 

写在后面

为什么不用 mit 6.824 的课程代码?

之前我曾把 mit 6.824 的 Raft 答案推到自己的 Github,直到 2020 开课的时候 mit 的助教发邮件让我将我的代码转为 private,因为这样会导致学习课程的人直接搜到代码,而无法保证作业独立完成。

确实,实验是计算机最不可或缺的环节,用 mit 6.824 2015 的 paxos 代码会导致很多学习者不去自己解决困难,直接上网搜代码,从而导致学习效果不好,违背了 mit 的初衷。

当然,你也可以说现在网上以及很容易搜到 6.824 的各种代码了,但出于之前 mit 助教的邮件,我不会将作业代码直接发出来。

感兴趣的同学可以到 2015 版本学习:http://nil.csail.mit.edu/6.824/2015/

 

未来计划

  • 实现一个完整的(包含网络和存储的) Paxos

  • 基于 Paxos 实现一个 Paxos KV 存储

  • 实现其它 Paxos 变种

欢迎各位朋友催更……

 

结语

本文代码在 Github 上,如本文有什么遗漏或者不对之处,或者各位朋友有什么新的想法,欢迎提 issue 讨论。

技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。

高可用架构

改变互联网的构建方式



Tags:分布式共识算法   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前文《理解 Paxos》只包含伪代码,帮助了理解但又不够爽,既然现在都讲究 Talk is cheap. Show me the code.这次就把文章中的伪代码用 Go 语言实现出来,希望能帮助各位朋友更直...【详细内容】
2020-12-15  Tags: 分布式共识算法  点击:(116)  评论:(0)  加入收藏
▌简易百科推荐
前言Kafka 中有很多延时操作,比如对于耗时的网络请求(比如 Produce 是等待 ISR 副本复制成功)会被封装成 DelayOperation 进行延迟处理操作,防止阻塞 Kafka请求处理线程。Kafka...【详细内容】
2021-12-27  Java技术那些事    Tags:时间轮   点击:(1)  评论:(0)  加入收藏
博雯 发自 凹非寺量子位 报道 | 公众号 QbitAI在炼丹过程中,为了减少训练所需资源,MLer有时会将大型复杂的大模型“蒸馏”为较小的模型,同时还要保证与压缩前相当的结果。这就...【详细内容】
2021-12-24  量子位    Tags:蒸馏法   点击:(9)  评论:(0)  加入收藏
分稀疏重建和稠密重建两类:稀疏重建:使用RGB相机SLAMOrb-slam,Orb-slam2,orb-slam3:工程地址在: http://webdiis.unizar.es/~raulmur/orbslam/ DSO(Direct Sparse Odometry)因为...【详细内容】
2021-12-23  老师明明可以靠颜值    Tags:算法   点击:(7)  评论:(0)  加入收藏
1. 基本概念希尔排序又叫递减增量排序算法,它是在直接插入排序算法的基础上进行改进而来的,综合来说它的效率肯定是要高于直接插入排序算法的;希尔排序是一种不稳定的排序算法...【详细内容】
2021-12-22  青石野草    Tags:希尔排序   点击:(6)  评论:(0)  加入收藏
ROP是一种技巧,我们对execve函数进行拼凑来进行system /bin/sh。栈迁移的特征是溢出0x10个字符,在本次getshell中,还碰到了如何利用printf函数来进行canary的泄露。ROP+栈迁移...【详细内容】
2021-12-15  星云博创    Tags:栈迁移   点击:(19)  评论:(0)  加入收藏
一、什么是冒泡排序1.1、文字描述冒泡排序是一种简单的排序算法。它重复地走访要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地...【详细内容】
2021-12-15    晓掌柜丶韶华  Tags:排序算法   点击:(16)  评论:(0)  加入收藏
在了解golang的map之前,我们需要了解哈希这个概念。哈希表,又称散列表(Hash table),是根据键(key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算出一个键值的函数,将...【详细内容】
2021-12-07  一棵梧桐木    Tags:哈希表   点击:(13)  评论:(0)  加入收藏
前面文章在谈论分布式唯一ID生成的时候,有提到雪花算法,这一次,我们详细点讲解,只讲它。SnowFlake算法据国家大气研究中心的查尔斯&middot;奈特称,一般的雪花大约由10^19个水分子...【详细内容】
2021-11-17  小心程序猿QAQ    Tags:雪花算法   点击:(24)  评论:(0)  加入收藏
导读:在大数据时代,对复杂数据结构中的各数据项进行有效的排序和查找的能力非常重要,因为很多现代算法都需要用到它。在为数据恰当选择排序和查找策略时,需要根据数据的规模和类型进行判断。尽管不同策略最终得到的结果完...【详细内容】
2021-11-04  华章科技    Tags:排序算法   点击:(37)  评论:(0)  加入收藏
这是我在网上找的资源的一个总结,会先给出一个我看了觉得还行的关于算法的讲解,再配上实现的代码: Original author: Bill_Hoo Original Address: http://blog.sina.com.cn/s/bl...【详细内容】
2021-11-04  有AI野心的电工和码农    Tags: KMP算法   点击:(36)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条