├── LICENSE ├── README.md ├── acceptor.go ├── doc.go ├── learner.go ├── message.go ├── network.go ├── paxos_test.go └── proposer.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Evan Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | paxos 2 | ================== 3 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/kkdai/paxos/master/LICENSE) [![GoDoc](https://godoc.org/github.com/kkdai/paxos?status.svg)](https://godoc.org/github.com/kkdai/paxos) 4 | 5 | It is the [Paxos](http://research.microsoft.com/en-us/um/people/lamport/pubs/paxos-simple.pdf) Consensus algorithm Paxos implement in Golang. 6 | 7 | ### Inspired By: 8 | 9 | - [https://github.com/xiang90/paxos](https://github.com/xiang90/paxos) 10 | - [https://github.com/kr/paxos](https://github.com/kr/paxos) 11 | 12 | Project52 13 | --------------- 14 | 15 | It is one of my [project 52](https://github.com/kkdai/project52). 16 | 17 | 18 | License 19 | --------------- 20 | 21 | This package is licensed under MIT license. See LICENSE for details. 22 | 23 | 24 | -------------------------------------------------------------------------------- /acceptor.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import "log" 4 | 5 | //Create a accetor and also assign learning IDs into acceptor. 6 | //Acceptor: Will response request from proposer, promise the first and largest seq number propose. 7 | // After proposer reach the majority promise. Acceptor will pass the proposal value to learner to confirn and choose. 8 | func NewAcceptor(id int, nt nodeNetwork, learners ...int) acceptor { 9 | newAccptor := acceptor{id: id, nt: nt} 10 | newAccptor.learners = learners 11 | return newAccptor 12 | } 13 | 14 | type acceptor struct { 15 | id int 16 | learners []int 17 | acceptMsg message 18 | promiseMsg message 19 | nt nodeNetwork 20 | } 21 | 22 | //Acceptor process detail logic. 23 | func (a *acceptor) run() { 24 | for { 25 | // log.Println("acceptor:", a.id, " wait to recev msg") 26 | m := a.nt.recev() 27 | if m == nil { 28 | continue 29 | } 30 | 31 | // log.Println("acceptor:", a.id, " recev message ", *m) 32 | switch m.typ { 33 | case Prepare: 34 | promiseMsg := a.recevPrepare(*m) 35 | a.nt.send(*promiseMsg) 36 | continue 37 | case Propose: 38 | accepted := a.recevPropose(*m) 39 | if accepted { 40 | for _, lId := range a.learners { 41 | m.from = a.id 42 | m.to = lId 43 | m.typ = Accept 44 | a.nt.send(*m) 45 | } 46 | } 47 | default: 48 | log.Fatalln("Unsupport message in accpetor ID:", a.id) 49 | } 50 | } 51 | log.Println("accetor :", a.id, " leave.") 52 | } 53 | 54 | //After acceptor receive prepare message. 55 | //It will check prepare number and return acceptor if it is bigest one. 56 | func (a *acceptor) recevPrepare(prepare message) *message { 57 | if a.promiseMsg.getProposeSeq() >= prepare.getProposeSeq() { 58 | log.Println("ID:", a.id, "Already accept bigger one") 59 | return nil 60 | } 61 | log.Println("ID:", a.id, " Promise") 62 | prepare.to = prepare.from 63 | prepare.from = a.id 64 | prepare.typ = Promise 65 | a.acceptMsg = prepare 66 | return &prepare 67 | } 68 | 69 | //Recev Propose only check if acceptor already accept bigger propose before. 70 | //Otherwise, will just forward this message out and change its type to "Accept" to learning later. 71 | func (a *acceptor) recevPropose(proposeMsg message) bool { 72 | //Already accept message is identical with previous promise message 73 | log.Println("accept:check propose. ", a.acceptMsg.getProposeSeq(), proposeMsg.getProposeSeq()) 74 | if a.acceptMsg.getProposeSeq() > proposeMsg.getProposeSeq() || a.acceptMsg.getProposeSeq() < proposeMsg.getProposeSeq() { 75 | log.Println("ID:", a.id, " acceptor not take propose:", proposeMsg.val) 76 | return false 77 | } 78 | log.Println("ID:", a.id, " Accept") 79 | return true 80 | } 81 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package Paxos is a implement for consensus protocol algorithm "Paxos". 3 | The interface of this package is experimental and may change. 4 | 5 | There are three role play in this algorithm: "Acceptor", "Proposer" and "Learner". 6 | 7 | */ 8 | package paxos 9 | -------------------------------------------------------------------------------- /learner.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import "log" 4 | 5 | type learner struct { 6 | id int 7 | acceptedMsgs map[int]message 8 | nt nodeNetwork 9 | } 10 | 11 | //Initilize learner and prepare message pool. 12 | func NewLearner(id int, nt nodeNetwork, acceptorIDs ...int) *learner { 13 | newLearner := &learner{id: id, nt: nt} 14 | newLearner.acceptedMsgs = make(map[int]message) 15 | for _, aceptId := range acceptorIDs { 16 | newLearner.acceptedMsgs[aceptId] = message{} 17 | } 18 | return newLearner 19 | } 20 | 21 | //Run learner process and will return learn value if reach majority. 22 | func (l *learner) run() string { 23 | 24 | for { 25 | m := l.nt.recev() 26 | if m == nil { 27 | continue 28 | } 29 | 30 | log.Println("Learner: recev msg:", *m) 31 | l.handleRecevAccept(*m) 32 | learnMsg, isLearn := l.chosen() 33 | if isLearn == false { 34 | continue 35 | } 36 | return learnMsg.getProposeVal() 37 | } 38 | } 39 | 40 | //Check acceptor message and compare with local accpeted proposal to make sure it is most updated. 41 | func (l *learner) handleRecevAccept(acceptMsg message) { 42 | hasAcceptedMsg := l.acceptedMsgs[acceptMsg.from] 43 | if hasAcceptedMsg.getProposeSeq() < acceptMsg.getProposeSeq() { 44 | //get bigger num will replace it to keep it updated. 45 | l.acceptedMsgs[acceptMsg.from] = acceptMsg 46 | } 47 | } 48 | 49 | //Every acceptor might send different proposal ID accept mesage to learner. 50 | //Learner only chosen if the accept count reach majority. 51 | func (l *learner) chosen() (message, bool) { 52 | acceptCount := make(map[int]int) 53 | acceptMsgMap := make(map[int]message) 54 | 55 | //Separate each acceptor message according their proposal ID and count it 56 | for _, msg := range l.acceptedMsgs { 57 | proposalNum := msg.getProposeSeq() 58 | acceptCount[proposalNum]++ 59 | acceptMsgMap[proposalNum] = msg 60 | } 61 | 62 | //Check count if reach majority will return as chosen value. 63 | for chosenNum, chosenMsg := range acceptMsgMap { 64 | if acceptCount[chosenNum] > l.majority() { 65 | return chosenMsg, true 66 | } 67 | } 68 | return message{}, false 69 | } 70 | 71 | //Count for majority, need initilize the count at constructor. 72 | func (l *learner) majority() int { 73 | return len(l.acceptedMsgs)/2 + 1 74 | } 75 | -------------------------------------------------------------------------------- /message.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | type msgType int 4 | 5 | const ( 6 | Prepare msgType = iota + 1 // Send from proposer -> acceptor 7 | Promise // Send from acceptor -> proposer 8 | Propose // Send from proposer -> acceptor 9 | Accept // Send from acceptor -> learner 10 | ) 11 | 12 | type message struct { 13 | from int 14 | to int 15 | typ msgType 16 | seq int 17 | preSeq int 18 | val string 19 | } 20 | 21 | func (m *message) getProposeVal() string { 22 | return m.val 23 | } 24 | 25 | func (m *message) getProposeSeq() int { 26 | return m.seq 27 | } 28 | -------------------------------------------------------------------------------- /network.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import ( 4 | "log" 5 | "time" 6 | ) 7 | 8 | func CreateNetwork(nodes ...int) *network { 9 | nt := network{recvQueue: make(map[int]chan message, 0)} 10 | 11 | for _, node := range nodes { 12 | nt.recvQueue[node] = make(chan message, 1024) 13 | } 14 | 15 | return &nt 16 | } 17 | 18 | type network struct { 19 | recvQueue map[int]chan message 20 | } 21 | 22 | func (n *network) getNodeNetwork(id int) nodeNetwork { 23 | return nodeNetwork{id: id, net: n} 24 | } 25 | 26 | func (n *network) sendTo(m message) { 27 | log.Println("Send msg from:", m.from, " send to", m.to, " val:", m.val, " typ:", m.typ) 28 | n.recvQueue[m.to] <- m 29 | } 30 | 31 | func (n *network) recevFrom(id int) *message { 32 | select { 33 | case retMsg := <-n.recvQueue[id]: 34 | log.Println("Recev msg from:", retMsg.from, " send to", retMsg.to, " val:", retMsg.val, " typ:", retMsg.typ) 35 | return &retMsg 36 | case <-time.After(time.Second): 37 | //log.Println("id:", id, " don't get message.. time out.") 38 | return nil 39 | } 40 | } 41 | 42 | type nodeNetwork struct { 43 | id int 44 | net *network 45 | } 46 | 47 | func (n *nodeNetwork) send(m message) { 48 | n.net.sendTo(m) 49 | } 50 | 51 | func (n *nodeNetwork) recev() *message { 52 | return n.net.recevFrom(n.id) 53 | } 54 | -------------------------------------------------------------------------------- /paxos_test.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestBasicNetwork(t *testing.T) { 10 | log.Println("TestBasicNetowk........................") 11 | nt := CreateNetwork(1, 3, 5, 2, 4) 12 | go func() { 13 | nt.recevFrom(5) 14 | nt.recevFrom(1) 15 | nt.recevFrom(3) 16 | nt.recevFrom(2) 17 | m := nt.recevFrom(4) 18 | if m == nil { 19 | t.Errorf("No message detected.") 20 | } 21 | }() 22 | 23 | m1 := message{from: 3, to: 1, typ: Prepare, seq: 1, preSeq: 0, val: "m1"} 24 | nt.sendTo(m1) 25 | m2 := message{from: 5, to: 3, typ: Accept, seq: 2, preSeq: 1, val: "m2"} 26 | nt.sendTo(m2) 27 | m3 := message{from: 4, to: 2, typ: Promise, seq: 3, preSeq: 2, val: "m3"} 28 | nt.sendTo(m3) 29 | time.Sleep(time.Second) 30 | } 31 | 32 | func TestSingleProser(t *testing.T) { 33 | log.Println("TestProserFunction........................") 34 | //Three acceptor and one proposer 35 | network := CreateNetwork(100, 1, 2, 3, 200) 36 | 37 | //Create acceptors 38 | var acceptors []acceptor 39 | aId := 1 40 | for aId <= 3 { 41 | acctor := NewAcceptor(aId, network.getNodeNetwork(aId), 200) 42 | acceptors = append(acceptors, acctor) 43 | aId++ 44 | } 45 | 46 | //Create proposer 47 | proposer := NewProposer(100, "value1", network.getNodeNetwork(100), 1, 2, 3) 48 | 49 | //Run proposer and acceptors 50 | go proposer.run() 51 | 52 | for index, _ := range acceptors { 53 | go acceptors[index].run() 54 | } 55 | 56 | //Create learner and learner will wait until reach majority. 57 | learner := NewLearner(200, network.getNodeNetwork(200), 1, 2, 3) 58 | learnValue := learner.run() 59 | if learnValue != "value1" { 60 | t.Errorf("Learner learn wrong proposal.") 61 | } 62 | } 63 | 64 | func TestTwoProsers(t *testing.T) { 65 | log.Println("TestProserFunction........................") 66 | //Three acceptor and one proposer 67 | network := CreateNetwork(100, 1, 2, 3, 200, 101) 68 | 69 | //Create acceptors 70 | var acceptors []acceptor 71 | aId := 1 72 | for aId <= 3 { 73 | acctor := NewAcceptor(aId, network.getNodeNetwork(aId), 200) 74 | acceptors = append(acceptors, acctor) 75 | aId++ 76 | } 77 | 78 | //Create proposer 1 79 | proposer1 := NewProposer(100, "ExpectValue", network.getNodeNetwork(100), 1, 2, 3) 80 | //Run proposer and acceptors 81 | go proposer1.run() 82 | 83 | //Need sleep to make sure first proposer reach majority 84 | 85 | //Create proposer 2 86 | proposer2 := NewProposer(101, "WrongValue", network.getNodeNetwork(101), 1, 2, 3) 87 | //Run proposer and acceptors 88 | time.AfterFunc(time.Second, func() { 89 | proposer2.run() 90 | }) 91 | 92 | for index, _ := range acceptors { 93 | go acceptors[index].run() 94 | } 95 | 96 | //Create learner and learner will wait until reach majority. 97 | learner := NewLearner(200, network.getNodeNetwork(200), 1, 2, 3) 98 | learnValue := learner.run() 99 | if learnValue != "ExpectValue" { 100 | t.Errorf("Learner learn wrong proposal. Expect:'ExpectValue', learnValue: %v", learnValue) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /proposer.go: -------------------------------------------------------------------------------- 1 | package paxos 2 | 3 | import "log" 4 | 5 | func NewProposer(id int, val string, nt nodeNetwork, accetors ...int) *proposer { 6 | pro := proposer{id: id, proposeVal: val, seq: 0, nt: nt} 7 | pro.acceptors = make(map[int]message, len(accetors)) 8 | log.Println("proposer has ", len(accetors), " acceptors, val:", pro.proposeVal) 9 | for _, acceptor := range accetors { 10 | pro.acceptors[acceptor] = message{} 11 | } 12 | return &pro 13 | } 14 | 15 | type proposer struct { 16 | id int 17 | seq int 18 | proposeNum int 19 | proposeVal string 20 | acceptors map[int]message 21 | nt nodeNetwork 22 | } 23 | 24 | //Detail process for Proposor. 25 | func (p *proposer) run() { 26 | log.Println("Proposer start run... val:", p.proposeVal) 27 | //Stage1: Proposor send prepare message to acceptor to reach accept from majority. 28 | for !p.majorityReached() { 29 | log.Println("[Proposer:Prepare]") 30 | outMsgs := p.prepare() 31 | log.Println("[Proposer: prepare ", len(outMsgs), "msg") 32 | for _, msg := range outMsgs { 33 | p.nt.send(msg) 34 | log.Println("[Proposer: send", msg) 35 | } 36 | 37 | log.Println("[Proposer: prepare recev..") 38 | m := p.nt.recev() 39 | if m == nil { 40 | log.Println("[Proposer: no msg... ") 41 | continue 42 | } 43 | log.Println("[Proposer: recev", m) 44 | switch m.typ { 45 | case Promise: 46 | log.Println(" proposer recev a promise from ", m.from) 47 | p.checkRecvPromise(*m) 48 | default: 49 | panic("Unsupport message.") 50 | } 51 | } 52 | 53 | log.Println("[Proposer:Propose]") 54 | //Stage2: Proposor send propose value to acceptor to learn. 55 | log.Println("Proposor propose seq:", p.getProposeNum(), " value:", p.proposeVal) 56 | proposeMsgs := p.propose() 57 | for _, msg := range proposeMsgs { 58 | p.nt.send(msg) 59 | } 60 | } 61 | 62 | // After receipt the promise from acceptor and reach majority. 63 | // Proposor will propose value to those accpetors and let them know the consusence alreay ready. 64 | func (p *proposer) propose() []message { 65 | sendMsgCount := 0 66 | var msgList []message 67 | log.Println("proposer: propose msg:", len(p.acceptors)) 68 | for acepId, acepMsg := range p.acceptors { 69 | log.Println("check promise id:", acepMsg.getProposeSeq(), p.getProposeNum()) 70 | if acepMsg.getProposeSeq() == p.getProposeNum() { 71 | msg := message{from: p.id, to: acepId, typ: Propose, seq: p.getProposeNum()} 72 | msg.val = p.proposeVal 73 | log.Println("Propose val:", msg.val) 74 | msgList = append(msgList, msg) 75 | } 76 | sendMsgCount++ 77 | if sendMsgCount > p.majority() { 78 | break 79 | } 80 | } 81 | log.Println(" proposer propose msg list:", msgList) 82 | return msgList 83 | } 84 | 85 | // Stage 1: 86 | // Prepare will prepare message to send to majority of acceptors. 87 | // According to spec, we only send our prepare msg to the "majority" not all acceptors. 88 | func (p *proposer) prepare() []message { 89 | p.seq++ 90 | 91 | sendMsgCount := 0 92 | var msgList []message 93 | log.Println("proposer: prepare major msg:", len(p.acceptors)) 94 | for acepId, _ := range p.acceptors { 95 | msg := message{from: p.id, to: acepId, typ: Prepare, seq: p.getProposeNum(), val: p.proposeVal} 96 | msgList = append(msgList, msg) 97 | sendMsgCount++ 98 | if sendMsgCount > p.majority() { 99 | break 100 | } 101 | } 102 | return msgList 103 | } 104 | 105 | func (p *proposer) checkRecvPromise(promise message) { 106 | previousPromise := p.acceptors[promise.from] 107 | log.Println(" prevMsg:", previousPromise, " promiseMsg:", promise) 108 | log.Println(previousPromise.getProposeSeq(), promise.getProposeSeq()) 109 | if previousPromise.getProposeSeq() < promise.getProposeSeq() { 110 | log.Println("Proposor:", p.id, " get new promise:", promise) 111 | p.acceptors[promise.from] = promise 112 | if promise.getProposeSeq() > p.getProposeNum() { 113 | p.proposeNum = promise.getProposeSeq() 114 | p.proposeVal = promise.getProposeVal() 115 | } 116 | } 117 | } 118 | 119 | func (p *proposer) majority() int { 120 | return len(p.acceptors)/2 + 1 121 | } 122 | 123 | func (p *proposer) getRecevPromiseCount() int { 124 | recvCount := 0 125 | for _, acepMsg := range p.acceptors { 126 | log.Println(" proposer has total ", len(p.acceptors), " acceptor ", acepMsg, " current Num:", p.getProposeNum(), " msgNum:", acepMsg.getProposeSeq()) 127 | if acepMsg.getProposeSeq() == p.getProposeNum() { 128 | log.Println("recv ++", recvCount) 129 | recvCount++ 130 | } 131 | } 132 | log.Println("Current proposer recev promise count=", recvCount) 133 | return recvCount 134 | } 135 | 136 | func (p *proposer) majorityReached() bool { 137 | return p.getRecevPromiseCount() > p.majority() 138 | } 139 | 140 | func (p *proposer) getProposeNum() int { 141 | p.proposeNum = p.seq<<4 | p.id 142 | return p.proposeNum 143 | } 144 | --------------------------------------------------------------------------------