├── .github └── workflows │ └── main.yml ├── README.md ├── acceptor.go ├── go.mod ├── images ├── live_lock.png ├── many_proposer.png ├── mutil_paxos.png ├── paxos.png ├── phase1.png ├── phase2.png └── single_proposer.png ├── lamport-paxos-vision9527.pdf ├── learner.go ├── messager.go ├── proposer.go └── test_test.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the main branch 7 | on: 8 | push: 9 | branches: [ '**' ] 10 | pull_request: 11 | branches: [ '**' ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | # This workflow contains a single job called "build" 16 | build: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | 20 | # Steps represent a sequence of tasks that will be executed as part of the job 21 | steps: 22 | - name: Set up Go 1.14 23 | uses: actions/setup-go@v1 24 | with: 25 | go-version: 1.14 26 | id: go 27 | - name: Check out code into the Go module directory 28 | uses: actions/checkout@v2 29 | - name: Test 30 | run: go test -v 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paxos For Studying 2 | 3 | bilibili分享地址:https://www.bilibili.com/video/BV1C5411L7qT 4 | 5 | ## 一、Paxos Overview 6 | 7 | * Paxos( [The Part-Time Parliament](http://lamport.azurewebsites.net/pubs/lamport-paxos.pdf) )共识算法由[Leslie Lamport](http://www.lamport.org/)在1989年首次发布,后来由于大多数人不太能接受他的幽默风趣的介绍方法(其实用比喻的方式介绍长篇的理论,确实让人比较难理解),于是在2001年重新写一篇名叫 [Paxos Made Simple](http://lamport.azurewebsites.net/pubs/paxos-simple.pdf) 论文,相当于原始Paxos算法的简化版,主要讲述两阶段共识协议部分。这篇文章与原始文章在讲述Paxos算法上最大的不同就是用的都是计算机术语,看起来也轻松很多 8 | 9 | * Paxos算法是分布式系统中的一个共识算法家族,也是第一个有完整数学证明的共识算法 10 | 11 | * "世界上只有两种分布式共识算法,一种是Paxos算法,另一种是类Paxos算法" 12 | 13 | * 现在比较流行的zab和raft算法也是基于Paxos算法设计的 14 | 15 | ## 二、Basic Paxos 16 | 17 | * **Basic Paxos 是在一轮决策中对一个或多个被提议(propose)的值,最终选出一个值达成共识** 18 | ![paxos](./images/paxos.png) 19 |
20 | 21 | * **Basic Paxos 可以解决的问题** 22 | 1. 选主 23 | 2. 资源互斥访问 24 | 3. 复制日志的一致性 25 | 4. 其它 26 |
27 | 28 | * **容错模型** 29 | 1. 异步网络,网络是不可靠的,消息可能丢失、重复、延迟、网络分区,但是不包括拜占庭错误,即消息不会被篡改和伪造 30 | 2. 只要大多数(majority)服务器还运行,决议就能继续进行 31 |
32 | 33 | * [FLP](https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf)**定理** 34 | 1. Agreement:所有server必须对同一个值达成共识 35 | 2. Validity:达成共识的值必须是提议的有效值 36 | 3. Termination:最终会对一个值达成共识(Paxos不一定) 37 |
38 | 39 | * [Safety & Liveness](https://lrita.github.io/images/posts/distribution/safety-and-liveness-properties-a-survey.pdf) 40 | 41 | 1. Safety(不会有坏事发生): 42 | - 只有一个值达成共识 43 | - 一个server不会知道某个值达成共识,除非它真的已经达成共识 44 | 45 | 2. Liveness(好事一定会发生): 46 | - 最终一定会达成共识(在多个proposer的情况下不能保证,即basic paxos是不能保证终止的,为了保证终止就需要选出一个leader,每次的提议只通过一个leader来发起) 47 | - 如果共识达成最终所有服务器都能知道 48 |
49 | 50 | * **Paxos 角色构成** 51 | 52 | 1. Proposer 53 | - 处理客户端请求,主动发起提议 54 | 2. Acceptor 55 | - 被动接收来自Proposer的提议消息,并返回投票结果,通知learner 56 | 3. Learner 57 | - 被动接收来自Acceptor的消息 58 | 4. 实际中一个节点经常扮演三个角色 59 |
60 | 61 | * **Paxos两阶段协议** 62 | 63 | 1. Phase1 64 | 65 | ![phase1](./images/phase1.png) 66 | 67 | 2. Phase2 68 | ![phase2](./images/phase2.png) 69 | 70 | * **Paxos经典场景** 71 | 72 | 1. 单个Proposer发起提议情况 73 | 74 | ![single_proposer](./images/single_proposer.png) 75 | 76 | 2. 多个Proposer情况 77 | 78 | ![many_proposer](./images/many_proposer.png) 79 | 80 | 3. 死锁情况 81 | 82 | ![live_lock](./images/live_lock.png) 83 | 84 | ## 三、Multi Paxos 85 | 86 | * The Preliminary Protocol(初级协议) -> The Basic Protocol(基本协议) -> The Complete Synod Protocol(完整神会协议) -> The Multi-Decree Parliament(多法令议会协议) 87 | 88 | * 当需要决定多个值时就需要连续执行多次Paxos算法,一般执行一次Paxos算法的过程称作A Paxos Run 或者 A Paxos Instance 89 | 90 | * Multi-Paxos是由The Complete Synod Protocol衍生而来,The Complete Synod Protocol通过选主解决了进展性问题(同时也是满足一致性的) 91 | 92 | * 在Paxos算法中,选主只是为了解决进展性问题,不会影响一致性,即使出现脑裂Paxos算法也是安全的,只会影响进展性 93 | 94 | * 两阶段协议效率太低,可以有优化的空间。在单个Leader的情况下,如果前一次已经accept成功,接下来不再需要prepare阶段,直接进行accept 95 | 96 | * 一般的Multi-Paxos可以简单总结为(选主 + 两阶段的优化),但是选主也不是必须的(舍弃一定的进展性) 97 | 98 | * 多个instance可以并发的进行 99 | 100 | * Multi Paxos通常是指一类共识算法,不是精确的指定某个共识算法 101 | 102 | ![mutil-paxos](./images/mutil_paxos.png) 103 | 104 | ## 四、Implementing State Machine 105 | 106 | 1. Basic Paxos -> Multi Paxos -> Replicated State Machine 107 | 108 | 2. 本人另一篇关于[Raft](https://github.com/vision9527/raft-demo)算法的分享,可以帮助大家更好的理解共识算法的实际应用和实现。 109 | 110 | ## 五、Basic Paxos Implement With Go 111 | 112 | * 运行测试用例:go test -v 113 | 114 | ## 六、The Part-Time Parliament 115 | 116 | * [The The Part-Time Parliament 个人阅读笔记](https://github.com/vision9527/paxos/blob/main/lamport-paxos-vision9527.pdf) 117 | 118 | ## 七、Reference 119 | 120 | 1. [The Part-Time Parliament](http://lamport.azurewebsites.net/pubs/lamport-paxos.pdf) 121 | 122 | 2. [Paxos Made Simple](http://lamport.azurewebsites.net/pubs/paxos-simple.pdf) 123 | 124 | 3. [FLP定理](https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf) 125 | 126 | 4. [Safety & Liveness](https://lrita.github.io/images/posts/distribution/safety-and-liveness-properties-a-survey.pdf) 127 | 128 | 5. [Understanding Paxos](https://www.cs.rutgers.edu/~pxk/417/notes/paxos.html) 129 | 130 | 6. [Google TechTalks About Paxos](https://www.youtube.com/watch?v=d7nAGI_NZPk) 131 | 132 | 7. [Paxos lecture (Raft user study)](https://www.youtube.com/watch?v=JEpsBg0AO6o) 133 | 134 | 8. [raft-demo](https://github.com/vision9527/raft-demo) 135 | 136 | 9. [Paxos Made Live - Chubby](https://www.cs.utexas.edu/users/lorenzo/corsi/cs380d/papers/paper2-1.pdf) 137 | -------------------------------------------------------------------------------- /acceptor.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import ( 4 | "log" 5 | "math/rand" 6 | "net" 7 | "net/rpc" 8 | "sync" 9 | ) 10 | 11 | type Acceptor struct { 12 | mu sync.Mutex 13 | localAddr string // 本地tcp地址 14 | learnerPeers []string // learner的tcp地址 15 | promiseID float32 // 收到的最高proposeID 16 | acceptedID float32 // 接受的proposeID 17 | acceptedValue interface{} // 接受的值 18 | listener net.Listener 19 | isunreliable bool // 用于模拟不可靠网络 20 | } 21 | 22 | func (a *Acceptor) getLearnerPeers() []string { 23 | a.mu.Lock() 24 | peers := a.learnerPeers 25 | a.mu.Unlock() 26 | return peers 27 | } 28 | 29 | func (a *Acceptor) getAddr() string { 30 | a.mu.Lock() 31 | addr := a.localAddr 32 | a.mu.Unlock() 33 | return addr 34 | } 35 | 36 | func (a *Acceptor) RecievePrepare(arg *PrepareMsg, reply *PromiseMsg) error { 37 | // logPrint("[acceptor %s RecievePrepare:%v ]", a.localAddr, arg) 38 | reply.ProposeID = arg.ProposeID 39 | reply.AcceptorAddr = a.getAddr() 40 | if arg.ProposeID > a.promiseID { 41 | a.promiseID = arg.ProposeID 42 | reply.Success = true 43 | if a.acceptedID > 0 && a.acceptedValue != nil { 44 | reply.AccepedID = a.acceptedID 45 | reply.AccepedValue = a.acceptedValue 46 | } 47 | } 48 | 49 | // PASS 持久化promise的数据 50 | return nil 51 | } 52 | 53 | func (a *Acceptor) RecieveAccept(arg *AcceptMsg, reply *AcceptedMsg) error { 54 | // logPrint("[acceptor %s RecieveAccept:%v ]", a.localAddr, arg) 55 | reply.ProposeID = arg.ProposeID 56 | if arg.ProposeID == a.promiseID { 57 | reply.Success = true 58 | reply.AcceptorAddr = a.getAddr() 59 | a.promiseID = arg.ProposeID 60 | a.acceptedID = arg.ProposeID 61 | a.acceptedValue = arg.Value 62 | for _, learnerPeer := range a.getLearnerPeers() { 63 | callRpc(learnerPeer, "Learner", "RecieveAccepted", reply, &EmptyMsg{}) 64 | } 65 | } 66 | // PASS 持久化accepted的数据 67 | return nil 68 | } 69 | 70 | func (a *Acceptor) startRpc() { 71 | rpcx := rpc.NewServer() 72 | rpcx.Register(a) 73 | l, e := net.Listen("tcp", a.localAddr) 74 | a.listener = l 75 | if e != nil { 76 | log.Fatal("listen error:", e) 77 | } 78 | go func() { 79 | for { 80 | conn, err := l.Accept() 81 | if err != nil { 82 | continue 83 | } 84 | if a.isunreliable && rand.Int63()%1000 < 300 { 85 | conn.Close() 86 | continue 87 | } 88 | go rpcx.ServeConn(conn) 89 | } 90 | }() 91 | } 92 | 93 | func (a *Acceptor) clean() { 94 | a.promiseID = 0 95 | a.acceptedID = 0 96 | a.acceptedValue = nil 97 | } 98 | 99 | func (a *Acceptor) close() { 100 | a.listener.Close() 101 | } 102 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vision9527/paxos 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /images/live_lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/images/live_lock.png -------------------------------------------------------------------------------- /images/many_proposer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/images/many_proposer.png -------------------------------------------------------------------------------- /images/mutil_paxos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/images/mutil_paxos.png -------------------------------------------------------------------------------- /images/paxos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/images/paxos.png -------------------------------------------------------------------------------- /images/phase1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/images/phase1.png -------------------------------------------------------------------------------- /images/phase2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/images/phase2.png -------------------------------------------------------------------------------- /images/single_proposer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/images/single_proposer.png -------------------------------------------------------------------------------- /lamport-paxos-vision9527.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vision9527/paxos/32b142295e3f28c62f4d9369721fea2ddca560a2/lamport-paxos-vision9527.pdf -------------------------------------------------------------------------------- /learner.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import ( 4 | "log" 5 | "net" 6 | "net/rpc" 7 | "sync" 8 | ) 9 | 10 | type Learner struct { 11 | mu sync.Mutex 12 | localAddr string 13 | acceptorCount map[float32]int 14 | acceptedValue map[float32]interface{} 15 | decidedValue interface{} 16 | quorumSize int 17 | listener net.Listener 18 | } 19 | 20 | func (le *Learner) getQuorumSize() int { 21 | le.mu.Lock() 22 | size := le.quorumSize 23 | le.mu.Unlock() 24 | return size 25 | } 26 | 27 | func (le *Learner) RecieveAccepted(arg *AcceptedMsg, reply *EmptyMsg) error { 28 | // PASS 29 | return nil 30 | } 31 | 32 | func (le *Learner) startRpc() { 33 | rpcx := rpc.NewServer() 34 | rpcx.Register(le) 35 | l, e := net.Listen("tcp", le.localAddr) 36 | le.listener = l 37 | if e != nil { 38 | log.Fatal("listen error:", e) 39 | } 40 | go func() { 41 | for { 42 | conn, err := l.Accept() 43 | if err != nil { 44 | return 45 | } 46 | go rpcx.ServeConn(conn) 47 | } 48 | }() 49 | } 50 | 51 | func (le *Learner) clean() { 52 | le.acceptedValue = nil 53 | } 54 | 55 | func (le *Learner) close() { 56 | le.listener.Close() 57 | } 58 | -------------------------------------------------------------------------------- /messager.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import ( 4 | "fmt" 5 | "net/rpc" 6 | "strconv" 7 | ) 8 | 9 | type PrepareMsg struct { 10 | ProposeID float32 11 | } 12 | 13 | type PromiseMsg struct { 14 | AcceptorAddr string 15 | ProposeID float32 16 | Success bool 17 | AccepedID float32 18 | AccepedValue interface{} 19 | } 20 | type AcceptMsg struct { 21 | ProposeID float32 22 | AcceptorAddr string 23 | Value interface{} 24 | } 25 | type AcceptedMsg struct { 26 | ProposeID float32 27 | AcceptorAddr string 28 | Success bool 29 | } 30 | 31 | type EmptyMsg struct{} 32 | 33 | func callRpc(peerAddr, roleService, method string, arg interface{}, reply interface{}) error { 34 | c, err := rpc.Dial("tcp", peerAddr) 35 | if err != nil { 36 | return err 37 | } 38 | defer c.Close() 39 | 40 | err = c.Call(roleService+"."+method, arg, reply) 41 | if err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | func generateNumber(me int, number float32) float32 { 48 | var strNum string 49 | if number == 0 { 50 | strNum = fmt.Sprintf("1.%d", me) 51 | n, err := strconv.ParseFloat(strNum, 32) 52 | if err != nil { 53 | panic("error parse num") 54 | } 55 | return float32(n) 56 | } 57 | i := int(number) + 1 // 暂时不考虑float的精度问题 58 | strNum = fmt.Sprintf("%d.%d", i, me) 59 | n, err := strconv.ParseFloat(strNum, 32) 60 | if err != nil { 61 | panic("error parse num") 62 | } 63 | return float32(n) 64 | } 65 | 66 | var logLevel = 1 67 | 68 | func logPrint(format string, a ...interface{}) { 69 | if logLevel == 1 { 70 | fmt.Printf(format+"\n", a...) 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /proposer.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import ( 4 | "sync" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type Proposer struct { 10 | mu sync.Mutex 11 | me int // proposer的编号,用于生成proposerID 12 | acceptorPeers []string // acceptor的地址 13 | proposerID float32 // 提案号 14 | currentValue interface{} // 本轮的提案值 15 | highestAcceptedID float32 // 收到acceptor中最高接受过的proposerID 16 | highestAcceptedValue interface{} // 收到acceptor中最高接受过的proposerID的value 17 | decidedValue interface{} // 最终达成共识的值 18 | } 19 | 20 | func (p *Proposer) close() {} 21 | 22 | func (p *Proposer) clean() { 23 | p.proposerID = 0 24 | p.currentValue = nil 25 | p.highestAcceptedID = 0 26 | p.highestAcceptedValue = nil 27 | p.decidedValue = nil 28 | } 29 | 30 | func (p *Proposer) propose(value interface{}) interface{} { 31 | p.currentValue = value 32 | p.runTwoPhase() 33 | return nil 34 | } 35 | 36 | func (p *Proposer) getMe() int { 37 | p.mu.Lock() 38 | me := p.me 39 | p.mu.Unlock() 40 | return me 41 | } 42 | 43 | func (p *Proposer) getQuorumSize() int64 { 44 | p.mu.Lock() 45 | size := int64(len(p.acceptorPeers)/2 + 1) 46 | p.mu.Unlock() 47 | return size 48 | } 49 | 50 | func (p *Proposer) getAcceptorPeers() []string { 51 | p.mu.Lock() 52 | peers := p.acceptorPeers 53 | p.mu.Unlock() 54 | return peers 55 | } 56 | 57 | func (p *Proposer) runTwoPhase() { 58 | peers := p.getAcceptorPeers() 59 | 60 | for p.decidedValue == nil { 61 | // phase 1 62 | prepareMsgReq := p.prepare() 63 | var promiseSuccessNum int64 64 | var promiseFailedNum int64 65 | promiseSuccessChan := make(chan struct{}) 66 | promiseFailedChan := make(chan struct{}) 67 | 68 | logPrint("[proposer:%d] phase 1, prepareMsg:%v", p.getMe(), prepareMsgReq) 69 | for _, peerAddr := range peers { 70 | go func(peerAddr string, prepareMsgReq PrepareMsg) { 71 | defer func() { 72 | if atomic.LoadInt64(&promiseSuccessNum) >= p.getQuorumSize() { 73 | promiseSuccessChan <- struct{}{} 74 | return 75 | } 76 | if atomic.LoadInt64(&promiseFailedNum) >= p.getQuorumSize() { 77 | promiseFailedChan <- struct{}{} 78 | return 79 | } 80 | }() 81 | 82 | promiseMsgResp, err := p.sendPrepare(peerAddr, &prepareMsgReq) // 这里用同步的方式处理,就不考虑响应消息重复接收了 83 | if err != nil || prepareMsgReq.ProposeID != promiseMsgResp.ProposeID || 84 | promiseMsgResp.AcceptorAddr != peerAddr { 85 | atomic.AddInt64(&promiseFailedNum, 1) 86 | return 87 | } 88 | 89 | if promiseMsgResp.Success { 90 | atomic.AddInt64(&promiseSuccessNum, 1) 91 | } else { 92 | atomic.AddInt64(&promiseFailedNum, 1) 93 | } 94 | 95 | if promiseMsgResp.AccepedID > 0 { 96 | p.setAccepted(promiseMsgResp) 97 | } 98 | }(peerAddr, prepareMsgReq) 99 | } 100 | 101 | select { 102 | case <-time.After(200 * time.Millisecond): 103 | continue 104 | case <-promiseFailedChan: 105 | continue 106 | case <-promiseSuccessChan: 107 | // prepare success 108 | } 109 | 110 | // phase 2 111 | var acceptSuccessNum int64 112 | var acceptFailedNum int64 113 | acceptSuccesChan := make(chan struct{}) 114 | acceptFailedChan := make(chan struct{}) 115 | 116 | acceptMsgReq := p.accept() 117 | logPrint("[proposer:%d] phase 2, acceptMsg:%v", p.getMe(), acceptMsgReq) 118 | 119 | for _, peerAddr := range peers { 120 | go func(peerAddr string, acceptMsgReq AcceptMsg) { 121 | defer func() { 122 | if atomic.LoadInt64(&acceptSuccessNum) >= p.getQuorumSize() { 123 | acceptSuccesChan <- struct{}{} 124 | return 125 | } 126 | if atomic.LoadInt64(&acceptFailedNum) >= p.getQuorumSize() { 127 | acceptFailedChan <- struct{}{} 128 | return 129 | } 130 | }() 131 | 132 | acceptedMsgResp, err := p.sendAccept(peerAddr, &acceptMsgReq) // 这里用同步的方式处理,就不考虑响应消息重复接收了 133 | if err != nil || acceptMsgReq.ProposeID != acceptedMsgResp.ProposeID || 134 | peerAddr != acceptedMsgResp.AcceptorAddr { 135 | atomic.AddInt64(&acceptFailedNum, 1) 136 | return 137 | } 138 | if acceptedMsgResp.Success { 139 | atomic.AddInt64(&acceptSuccessNum, 1) 140 | } else { 141 | atomic.AddInt64(&acceptFailedNum, 1) 142 | } 143 | }(peerAddr, acceptMsgReq) 144 | } 145 | select { 146 | case <-time.After(200 * time.Millisecond): 147 | continue 148 | case <-acceptFailedChan: 149 | continue 150 | case <-acceptSuccesChan: 151 | p.decidedValue = acceptMsgReq.Value 152 | logPrint("[proposer:%d] reach consensuse Value: %v", p.getMe(), acceptMsgReq.Value) 153 | return 154 | } 155 | } 156 | } 157 | 158 | func (p *Proposer) prepare() PrepareMsg { 159 | p.mu.Lock() 160 | proposerID := generateNumber(p.me, p.proposerID) 161 | // PASS 持久化当前最高序号 162 | msg := PrepareMsg{ 163 | ProposeID: proposerID, 164 | } 165 | p.proposerID = proposerID 166 | p.mu.Unlock() 167 | return msg 168 | } 169 | 170 | func (p *Proposer) accept() AcceptMsg { 171 | p.mu.Lock() 172 | msg := AcceptMsg{ 173 | ProposeID: p.proposerID, 174 | Value: p.currentValue, 175 | } 176 | if p.highestAcceptedValue != nil { 177 | msg.Value = p.highestAcceptedValue 178 | } 179 | p.mu.Unlock() 180 | return msg 181 | } 182 | 183 | func (p *Proposer) setAccepted(promiseMsgResp *PromiseMsg) { 184 | p.mu.Lock() 185 | if promiseMsgResp.AccepedID > p.highestAcceptedID { 186 | p.highestAcceptedID = promiseMsgResp.AccepedID 187 | p.highestAcceptedValue = promiseMsgResp.AccepedValue 188 | } 189 | p.mu.Unlock() 190 | } 191 | 192 | func (p *Proposer) sendPrepare(peerAddr string, msg *PrepareMsg) (*PromiseMsg, error) { 193 | reply := new(PromiseMsg) 194 | err := callRpc(peerAddr, "Acceptor", "RecievePrepare", msg, reply) 195 | return reply, err 196 | } 197 | 198 | func (p *Proposer) sendAccept(peerAddr string, msg *AcceptMsg) (*AcceptedMsg, error) { 199 | reply := new(AcceptedMsg) 200 | err := callRpc(peerAddr, "Acceptor", "RecieveAccept", msg, reply) 201 | return reply, err 202 | } 203 | -------------------------------------------------------------------------------- /test_test.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | // 初始化集群 10 | func makeCluster(proposerNum, acceptorNum, learnNum int) (p []*Proposer, a []*Acceptor, l []*Learner) { 11 | acceptorPort := 4100 12 | learnerPort := 4200 13 | 14 | learnPeers := make([]string, learnNum) 15 | for i := 0; i < learnNum; i++ { 16 | learnerPort++ 17 | learner := &Learner{ 18 | localAddr: formatAddr(learnerPort), 19 | } 20 | l = append(l, learner) 21 | learnPeers[i] = learner.localAddr 22 | l[i].startRpc() 23 | } 24 | 25 | acceptorPeers := make([]string, acceptorNum) 26 | for i := 0; i < acceptorNum; i++ { 27 | acceptorPort++ 28 | acceptor := &Acceptor{ 29 | localAddr: formatAddr(acceptorPort), 30 | learnerPeers: learnPeers, 31 | } 32 | a = append(a, acceptor) 33 | acceptorPeers[i] = acceptor.localAddr 34 | a[i].startRpc() 35 | } 36 | 37 | for i := 0; i < proposerNum; i++ { 38 | proposer := &Proposer{ 39 | acceptorPeers: acceptorPeers, 40 | me: i + 1, 41 | } 42 | p = append(p, proposer) 43 | } 44 | return 45 | } 46 | 47 | func formatAddr(port int) string { 48 | return fmt.Sprintf("127.0.0.1:%d", port) 49 | } 50 | 51 | // 清除数据 52 | func clean(p []*Proposer, a []*Acceptor, l []*Learner) { 53 | for _, i := range p { 54 | i.clean() 55 | } 56 | for _, i := range a { 57 | i.clean() 58 | } 59 | for _, i := range l { 60 | i.clean() 61 | } 62 | } 63 | 64 | // 关闭端口 65 | func close(p []*Proposer, a []*Acceptor, l []*Learner) { 66 | for _, i := range p { 67 | i.close() 68 | } 69 | for _, i := range a { 70 | i.close() 71 | } 72 | for _, i := range l { 73 | i.close() 74 | } 75 | } 76 | 77 | // 检测单个proposer 78 | func checkOne(t *testing.T, p []*Proposer, value interface{}) { 79 | for _, i := range p { 80 | if i.decidedValue != value { 81 | t.Fatalf("wrong decided value, want value:%v decided value:%v", value, i.decidedValue) 82 | } 83 | } 84 | } 85 | 86 | // 检测多个proposer 87 | func checkMany(t *testing.T, p []*Proposer) { 88 | var value interface{} 89 | for _, i := range p { 90 | if i.decidedValue == nil { 91 | t.Fatalf("wrong decided value, decided value: nil") 92 | } 93 | if value != nil && value != i.decidedValue { 94 | t.Fatalf("wrong decided value, previous decided value:%v current decided value:%v", value, i.decidedValue) 95 | } 96 | if value == nil { 97 | value = i.decidedValue 98 | } 99 | } 100 | if value == nil { 101 | t.Fatalf("wrong decided value, should have one") 102 | } 103 | } 104 | 105 | func TestBasicPaxos(t *testing.T) { 106 | pNum := 1 107 | aNum := 3 108 | lNum := 2 109 | logPrint("TestBasicPaxos proposer num:%d, acceptor num:%d, learner num:%d begin", pNum, aNum, lNum) 110 | p, a, l := makeCluster(pNum, aNum, lNum) 111 | defer close(p, a, l) 112 | clean(p, a, l) 113 | 114 | p[0].propose(100) 115 | time.Sleep(1 * time.Second) 116 | checkOne(t, p, 100) 117 | clean(p, a, l) 118 | logPrint("TestBasicPaxos proposer num:%d, acceptor num:%d, learner num:%d end", pNum, aNum, lNum) 119 | } 120 | 121 | func TestSingleProposer(t *testing.T) { 122 | pNum := 1 123 | aNum := 3 124 | lNum := 2 125 | logPrint("TestSingleProposer proposer num:%d, acceptor num:%d, learner num:%d begin", pNum, aNum, lNum) 126 | p, a, l := makeCluster(pNum, aNum, lNum) 127 | defer close(p, a, l) 128 | clean(p, a, l) 129 | 130 | p[0].propose(100) 131 | time.Sleep(1 * time.Second) 132 | checkOne(t, p, 100) 133 | clean(p, a, l) 134 | 135 | p[0].propose(200) 136 | 137 | time.Sleep(1 * time.Second) 138 | checkOne(t, p, 200) 139 | clean(p, a, l) 140 | 141 | p[0].propose(300) 142 | time.Sleep(1 * time.Second) 143 | checkOne(t, p, 300) 144 | clean(p, a, l) 145 | logPrint("TestSingleProposer proposer num:%d, acceptor num:%d, learner num:%d end", pNum, aNum, lNum) 146 | } 147 | 148 | func TestManyProposer(t *testing.T) { 149 | pNum := 3 150 | aNum := 3 151 | lNum := 2 152 | logPrint("TestManyProposer proposer num:%d, acceptor num:%d, learner num:%d begin", pNum, aNum, lNum) 153 | p, a, l := makeCluster(pNum, aNum, lNum) 154 | defer close(p, a, l) 155 | 156 | for i, proposer := range p { 157 | proposer.propose(100 + i) 158 | } 159 | time.Sleep(1 * time.Second) 160 | checkMany(t, p) 161 | clean(p, a, l) 162 | 163 | for i, proposer := range p { 164 | proposer.propose(200 + i) 165 | } 166 | time.Sleep(1 * time.Second) 167 | checkMany(t, p) 168 | clean(p, a, l) 169 | 170 | for i, proposer := range p { 171 | proposer.propose(300 + i) 172 | } 173 | time.Sleep(1 * time.Second) 174 | checkMany(t, p) 175 | clean(p, a, l) 176 | logPrint("TestManyProposer proposer num:%d, acceptor num:%d, learner num:%d end", pNum, aNum, lNum) 177 | } 178 | 179 | func TestManyProposerUnreliable(t *testing.T) { 180 | pNum := 3 181 | aNum := 3 182 | lNum := 2 183 | logPrint("TestManyProposerUnreliable proposer num:%d, acceptor num:%d, learner num:%d begin", pNum, aNum, lNum) 184 | p, a, l := makeCluster(pNum, aNum, lNum) 185 | for _, acceptor := range a { 186 | acceptor.isunreliable = true 187 | } 188 | defer close(p, a, l) 189 | 190 | // instance 1 -> log index 1 191 | for i, proposer := range p { 192 | proposer.propose(100 + i) 193 | } 194 | time.Sleep(1 * time.Second) 195 | checkMany(t, p) 196 | clean(p, a, l) 197 | 198 | // instance 2 -> log index 2 199 | for i, proposer := range p { 200 | proposer.propose(200 + i) 201 | } 202 | time.Sleep(1 * time.Second) 203 | checkMany(t, p) 204 | clean(p, a, l) 205 | 206 | // instance 3 -> log index 3 207 | for i, proposer := range p { 208 | proposer.propose(300 + i) 209 | } 210 | time.Sleep(1 * time.Second) 211 | checkMany(t, p) 212 | clean(p, a, l) 213 | logPrint("TestManyProposerUnreliable proposer num:%d, acceptor num:%d, learner num:%d end", pNum, aNum, lNum) 214 | } 215 | --------------------------------------------------------------------------------