├── .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 | 
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 | 
66 |
67 | 2. Phase2
68 | 
69 |
70 | * **Paxos经典场景**
71 |
72 | 1. 单个Proposer发起提议情况
73 |
74 | 
75 |
76 | 2. 多个Proposer情况
77 |
78 | 
79 |
80 | 3. 死锁情况
81 |
82 | 
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 | 
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 |
--------------------------------------------------------------------------------