日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

前文《理解 Paxos》只包含偽代碼,幫助了理解但又不夠爽,既然現(xiàn)在都講究 Talk is cheap. Show me the code.這次就把文章中的偽代碼用 Go 語言實(shí)現(xiàn)出來,希望能幫助各位朋友更直觀的感受 Paxos 論文中的細(xì)節(jié)。

但我們需要對算法做一些簡化,有多簡單呢?我們不持久化存儲任何變量,并且用 chan直接代替 RPC 調(diào)用。

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

記得切換到 naive 分支。

 

定義相關(guān)結(jié)構(gòu)體

我們定義 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
}

這些結(jié)構(gòu)體成員都很容易理解,其中 acceptors我們主要用來存儲 Acceptors 的地址,以及記錄我們收到 Acceptor 的成功/失敗響應(yīng)。

Acceptor 的結(jié)構(gòu)體:

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:接受的提案值

 

定義消息結(jié)構(gòu)體

消息結(jié)構(gòu)體定義了 Proposer 和 Acceptor 之間、Acceptor 和 Leaner 之間的通訊協(xié)議。最主要的還是 Paxos 的兩階段的四個消息。

  • Phase 1 請求:提案編號

  • Phase 1 響應(yīng):如果有被 Accepted 的提案,返回提案編號提案值

  • Phase 2 請求:提案編號提案值

  • Phase 2 響應(yīng):Accepted 的提案編號提案值

這樣看,我們的消息結(jié)構(gòu)體只需要提案編號和提案值,加上一個消息類型,用來區(qū)分是哪個階段的消息。消息結(jié)構(gòu)體定義在 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
}

 

實(shí)現(xiàn)網(wǎng)絡(luò)

網(wǎng)絡(luò)上可以做的選擇和優(yōu)化很多,但這里為了保持簡單的原則,我們將網(wǎng)絡(luò)定義成 interface。后面完全可以改成 RPC 或 API 等其它通信方式來實(shí)現(xiàn)(沒錯,我已經(jīng)實(shí)現(xiàn)了一個 Go RPC 的版本了)。

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

接下里我們?nèi)?shí)現(xiàn) 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來記錄每個節(jié)點(diǎn)的chan,key 則是節(jié)點(diǎn)的 server id。

發(fā)送消息則將 Message發(fā)送到目標(biāo)節(jié)點(diǎn)的chan中,接受消息直接從chan中讀取數(shù)據(jù),并等待對應(yīng)的超時時間。

不需要做其它網(wǎng)絡(luò)地址、包相關(guān)的東西,所以非常簡單。具體在 network.go文件。

 

實(shí)現(xiàn)單元測試

這個項(xiàng)目主要使用 go 單元測試來檢驗(yàn)正確性,我們主要測試兩種場景:

  • TestSingleProposer(單個 Proposer)

  • TestTwoProposers(多個 Proposer)

測試代碼通過運(yùn)行 Paxos 后檢查 Chosen 返回的提案值是否符合預(yù)期。

 

實(shí)現(xiàn)算法流程

按照角色將文件分為 proposer.go, acceptor.go 和 learner.go,每個文件都有一個 run函數(shù)來運(yùn)行程序,run函數(shù)執(zhí)行條件判斷,并在對應(yīng)的階段執(zhí)行對應(yīng)的函數(shù)。

按照偽代碼描述,我們很容易實(shí)現(xiàn) Phase 1 和 Phase 2,把每個階段的請求響應(yīng)都作為一個函數(shù),我們一步步來看。

 

第一輪 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 然后發(fā)送給多數(shù)派 Acceptors。

注:這里很多博客和教程都會將 Prepare RPC 發(fā)給所有的Acceptors,6.824 的 paxos 實(shí)驗(yàn)就將 RPC 發(fā)送給所有 Acceptors。這里保持和論文一致,只發(fā)送給 a majority of acceptors。

 

第一輪 Prepare RPCs 響應(yīng)階段:

接下來在 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 的話,響應(yīng)還應(yīng)包含 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
}

當(dāng) Proposer 收到超過半數(shù) Acceptor 的響應(yīng)后,Proposer 向多數(shù)派的 Acceptor 發(fā)起請求并帶上提案編號和提案值。

 

第二輪 Accept RPCs 響應(yīng)階段:

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,但如果你看過論文,其實(shí)已經(jīng)說得非常直接了。論文的 2.3 節(jié) 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 收到超過半數(shù)的 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
}

 

運(yùn)行和測試

代碼拉下來后,直接運(yùn)行:

go test

 

寫在后面

為什么不用 mit 6.824 的課程代碼?

之前我曾把 mit 6.824 的 Raft 答案推到自己的 Github,直到 2020 開課的時候 mit 的助教發(fā)郵件讓我將我的代碼轉(zhuǎn)為 private,因?yàn)檫@樣會導(dǎo)致學(xué)習(xí)課程的人直接搜到代碼,而無法保證作業(yè)獨(dú)立完成。

確實(shí),實(shí)驗(yàn)是計(jì)算機(jī)最不可或缺的環(huán)節(jié),用 mit 6.824 2015 的 paxos 代碼會導(dǎo)致很多學(xué)習(xí)者不去自己解決困難,直接上網(wǎng)搜代碼,從而導(dǎo)致學(xué)習(xí)效果不好,違背了 mit 的初衷。

當(dāng)然,你也可以說現(xiàn)在網(wǎng)上以及很容易搜到 6.824 的各種代碼了,但出于之前 mit 助教的郵件,我不會將作業(yè)代碼直接發(fā)出來。

感興趣的同學(xué)可以到 2015 版本學(xué)習(xí):http://nil.csail.mit.edu/6.824/2015/

 

未來計(jì)劃

  • 實(shí)現(xiàn)一個完整的(包含網(wǎng)絡(luò)和存儲的) Paxos

  • 基于 Paxos 實(shí)現(xiàn)一個 Paxos KV 存儲

  • 實(shí)現(xiàn)其它 Paxos 變種

歡迎各位朋友催更……

 

結(jié)語

本文代碼在 Github 上,如本文有什么遺漏或者不對之處,或者各位朋友有什么新的想法,歡迎提 issue 討論。

技術(shù)原創(chuàng)及架構(gòu)實(shí)踐文章,歡迎通過公眾號菜單「聯(lián)系我們」進(jìn)行投稿。

高可用架構(gòu)

改變互聯(lián)網(wǎng)的構(gòu)建方式

分享到:
標(biāo)簽:分布式 共識 算法
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定