日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

一致性协议浅析:从逻辑时钟到Raft

發(fā)布時(shí)間:2024/8/23 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一致性协议浅析:从逻辑时钟到Raft 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

春節(jié)在家閑著沒(méi)事看了幾篇論文,把一致性協(xié)議的幾篇論文都過(guò)了一遍。在看這些論文之前,我一直有一些疑惑,比如同樣是有Leader和兩階段提交,Zookeeper的ZAB協(xié)議和Raft有什么不同,Paxos協(xié)議到底要怎樣才能用在實(shí)際工程中,這些問(wèn)題我都在這些論文中找到了答案。接下來(lái),我將嘗試以自己的語(yǔ)言給大家講講這些協(xié)議,使大家能夠理解這些算法。同時(shí),我自己也有些疑問(wèn),我會(huì)在我的闡述中提出,也歡迎大家一起討論。水平有限,文中難免會(huì)有一些紕漏門也歡迎大家指出。

邏輯時(shí)鐘

邏輯時(shí)鐘其實(shí)算不上是一個(gè)一致性協(xié)議,它是Lamport大神在1987年就提出來(lái)的一個(gè)想法,用來(lái)解決分布式系統(tǒng)中,不同的機(jī)器時(shí)鐘不一致可能帶來(lái)的問(wèn)題。在單機(jī)系統(tǒng)中,我們用機(jī)器的時(shí)間來(lái)標(biāo)識(shí)事件,就可以非常清晰地知道兩個(gè)不同事件的發(fā)生次序。但是在分布式系統(tǒng)中,由于每臺(tái)機(jī)器的時(shí)間可能存在誤差,無(wú)法通過(guò)物理時(shí)鐘來(lái)準(zhǔn)確分辨兩個(gè)事件發(fā)生的先后順序。但實(shí)際上,在分布式系統(tǒng)中,只有兩個(gè)發(fā)生關(guān)聯(lián)的事件,我們才會(huì)去關(guān)心兩者的先來(lái)后到關(guān)系。比如說(shuō)兩個(gè)事務(wù),一個(gè)修改了rowa,一個(gè)修改了rowb,他們兩個(gè)誰(shuí)先發(fā)生,誰(shuí)后發(fā)生,其實(shí)我們并不關(guān)心。那所謂邏輯時(shí)鐘,就是用來(lái)定義兩個(gè)關(guān)聯(lián)事件的發(fā)生次序,即‘happens before’。而對(duì)于不關(guān)聯(lián)的事件,邏輯時(shí)鐘并不能決定其先后,所以說(shuō)這種‘happens before’的關(guān)系,是一種偏序關(guān)系。

圖和例子來(lái)自于這篇博客
此圖中,箭頭表示進(jìn)程間通訊,ABC分別代表分布式系統(tǒng)中的三個(gè)進(jìn)程。
邏輯時(shí)鐘的算法其實(shí)很簡(jiǎn)單:每個(gè)事件對(duì)應(yīng)一個(gè)Lamport時(shí)間戳,初始值為0
如果事件在節(jié)點(diǎn)內(nèi)發(fā)生,時(shí)間戳加1
如果事件屬于發(fā)送事件,時(shí)間戳加1并在消息中帶上該時(shí)間戳
如果事件屬于接收事件,時(shí)間戳 = Max(本地時(shí)間戳,消息中的時(shí)間戳) + 1
這樣,所有關(guān)聯(lián)的發(fā)送接收事件,我們都能保證發(fā)送事件的時(shí)間戳小于接收事件。如果兩個(gè)事件之間沒(méi)有關(guān)聯(lián),比如說(shuō)A3和B5,他們的邏輯時(shí)間一樣。正是由于他們沒(méi)有關(guān)系,我們可以隨意約定他們之間的發(fā)生順序。比如說(shuō)我們規(guī)定,當(dāng)Lamport時(shí)間戳一樣時(shí),A進(jìn)程的事件發(fā)生早于B進(jìn)程早于C進(jìn)程,這樣我們可以得出A3 ‘happens before’ B5。而實(shí)際在物理世界中,明顯B5是要早于A3發(fā)生的,但這都沒(méi)有關(guān)系。

邏輯時(shí)鐘貌似目前并沒(méi)有被廣泛的應(yīng)用,除了DynamoDB使用了vector clock來(lái)解決多版本的先后問(wèn)題(如果有其他實(shí)際應(yīng)用的話請(qǐng)指出,可能是我孤陋寡聞了),Google的Spanner 也是采用物理的原子時(shí)鐘來(lái)解決時(shí)鐘問(wèn)題。但是從Larmport大師的邏輯時(shí)鐘算法上,已經(jīng)可以看到一些一致性協(xié)議的影子。

Replicated State Machine

說(shuō)到一致性協(xié)議,我們通常就會(huì)講到復(fù)制狀態(tài)機(jī)。因?yàn)橥ǔN覀儠?huì)用復(fù)制狀態(tài)機(jī)加上一致性協(xié)議算法來(lái)解決分布式系統(tǒng)中的高可用和容錯(cuò)。許多分布式系統(tǒng),都是采用復(fù)制狀態(tài)機(jī)來(lái)進(jìn)行副本之間的數(shù)據(jù)同步,比如HDFS,Chubby和Zookeeper。

所謂復(fù)制狀態(tài)機(jī),就是在分布式系統(tǒng)的每一個(gè)實(shí)例副本中,都維持一個(gè)持久化的日志,然后用一定的一致性協(xié)議算法,保證每個(gè)實(shí)例的這個(gè)log都完全保持一致,這樣,實(shí)例內(nèi)部的狀態(tài)機(jī)按照日志的順序回放日志中的每一條命令,這樣客戶端來(lái)讀時(shí),在每個(gè)副本上都能讀到一樣的數(shù)據(jù)。復(fù)制狀態(tài)機(jī)的核心就是圖中 的Consensus模塊,即今天我們要討論的Paxos,ZAB,Raft等一致性協(xié)議算法。

Paxos

Paxos是Lamport大神在90年代提出的一致性協(xié)議算法,大家一直都覺(jué)得難懂,所以Lamport在2001又發(fā)表了一篇新的論文《Paxos made simple》,在文中他自己說(shuō)Paxos是世界上最簡(jiǎn)單的一致性算法,非常容易懂……但是業(yè)界還是一致認(rèn)為Paxos比較難以理解。在我看過(guò)Lamport大神的論文后,我覺(jué)得,除去復(fù)雜的正確性論證過(guò)程,Paxos協(xié)議本身還是比較好理解的。但是,Paxos協(xié)議還是過(guò)于理論,離具體的工程實(shí)踐還有太遠(yuǎn)的距離。我一開(kāi)始看Paxos協(xié)議的時(shí)候也是一頭霧水,看來(lái)看去發(fā)現(xiàn)Paxos協(xié)議只是為了單次事件答成一致,而且答成一致后的值無(wú)法再被修改,怎么用Paxos去實(shí)現(xiàn)復(fù)制狀態(tài)機(jī)呢?另外,Paxos協(xié)議答成一致的值只有Propose和部分follower知道,這協(xié)議到底怎么用……但是,如果你只是把Paxos協(xié)議當(dāng)做一個(gè)理論去看,而不是考慮實(shí)際工程上會(huì)遇到什么問(wèn)題的話,會(huì)容易理解的多。Lamport的論文中對(duì)StateMachine的應(yīng)用只有一個(gè)大概的想法,并沒(méi)有具體的實(shí)現(xiàn)邏輯,想要直接把Paxos放到復(fù)制狀態(tài)機(jī)里使用是不可能的,得在Paxos上補(bǔ)充很多的東西。這些是為什么Paxos有這么多的變種。

Basic-Paxos

Basic-Paxos即Lamport最初提出的Paxos算法,其實(shí)很簡(jiǎn)單,用三言兩語(yǔ)就可以講完,下面我嘗試著用我自己的語(yǔ)言描述下Paxos協(xié)議,然后會(huì)舉出一個(gè)例子。要理解Paxos,只要記住一點(diǎn)就好了,Paxos只能為一個(gè)值形成共識(shí),一旦Propose被確定,之后值永遠(yuǎn)不會(huì)變,也就是說(shuō)整個(gè)Paxos Group只會(huì)接受一個(gè)提案(或者說(shuō)接受多個(gè)提案,但這些提案的值都一樣)。至于怎么才能接受多個(gè)值來(lái)形成復(fù)制狀態(tài)機(jī),大家可以看下一節(jié)Multi-Paxos.

Paxos協(xié)議中是沒(méi)有Leader這個(gè)概念的,除去Learner(只是學(xué)習(xí)Propose的結(jié)果,我們可以不去討論這個(gè)角色),只有Proposer和Acceptor。Paxos并且允許多個(gè)Proposer同時(shí)提案。Proposer要提出一個(gè)值讓所有Acceptor答成一個(gè)共識(shí)。首先是Prepare階段,Proposer會(huì)給出一個(gè)ProposeID n(注意,此階段Proposer不會(huì)把值傳給Acceptor)給每個(gè)Acceptor,如果某個(gè)Acceptor發(fā)現(xiàn)自己從來(lái)沒(méi)有接收過(guò)大于等于n的Proposer,則會(huì)回復(fù)Proposer,同時(shí)承諾不再接收ProposeID小于等于n的提議的Prepare。如果這個(gè)Acceptor已經(jīng)承諾過(guò)比n更大的propose,則不會(huì)回復(fù)Proposer。如果Acceptor之前已經(jīng)Accept了(完成了第二個(gè)階段)一個(gè)小于n的Propose,則會(huì)把這個(gè)Propose的值返回給Propose,否則會(huì)返回一個(gè)null值。當(dāng)Proposer收到大于半數(shù)的Acceptor的回復(fù)后,就可以開(kāi)始第二階段accept階段。但是這個(gè)階段Propose能夠提出的值是受限的,只有它收到的回復(fù)中不含有之前Propose的值,他才能自由提出一個(gè)新的value,否則只能是用回復(fù)中Propose最大的值做為提議的值。Proposer用這個(gè)值和ProposeID n對(duì)每個(gè)Acceptor發(fā)起Accept請(qǐng)求。也就是說(shuō)就算Proposer之前已經(jīng)得到過(guò)acceptor的承諾,但是在accept發(fā)起之前,Acceptor可能給了proposeID更高的Propose承諾,導(dǎo)致accept失敗。也就是說(shuō)由于有多個(gè)Proposer的存在,雖然第一階段成功,第二階段仍然可能會(huì)被拒絕掉。
下面我舉一個(gè)例子,這個(gè)例子來(lái)源于這篇博客

假設(shè)有Server1,Server2, Server3三個(gè)服務(wù)器,他們都想通過(guò)Paxos協(xié)議,讓所有人答成一致他們是leader,這些Server都是Proposer角色,他們的提案的值就是他們自己server的名字。他們要獲取Acceptor1~3這三個(gè)成員同意。首先Server2發(fā)起一個(gè)提案【1】,也就是說(shuō)ProposeID為1,接下來(lái)Server1發(fā)起來(lái)一個(gè)提案【2】,Server3發(fā)起一個(gè)提案【3】.

首先是Prepare階段:
假設(shè)這時(shí)Server1發(fā)送的消息先到達(dá)acceptor1和acceptor2,它們都沒(méi)有接收過(guò)請(qǐng)求,所以接收該請(qǐng)求并返回【2,null】給Server1,同時(shí)承諾不再接受編號(hào)小于2的請(qǐng)求;?
??緊接著,Server2的消息到達(dá)acceptor2和acceptor3,acceptor3沒(méi)有接受過(guò)請(qǐng)求,所以返回proposer2 【1,null】,并承諾不再接受編號(hào)小于1的消息。而acceptor2已經(jīng)接受Server1的請(qǐng)求并承諾不再接收編號(hào)小于2的請(qǐng)求,所以acceptor2拒絕Server2的請(qǐng)求;?
??最后,Server3的消息到達(dá)acceptor2和acceptor3,它們都接受過(guò)提議,但編號(hào)3的消息大于acceptor2已接受的2和acceptor3已接受的1,所以他們都接受該提議,并返回Server3 【3,null】;?
??此時(shí),Server2沒(méi)有收到過(guò)半的回復(fù),所以重新取得編號(hào)4,并發(fā)送給acceptor2和acceptor3,此時(shí)編號(hào)4大于它們已接受的提案編號(hào)3,所以接受該提案,并返回Server2 【4,null】。

接下來(lái)進(jìn)入Accept階段,
Server3收到半數(shù)以上(2個(gè))的回復(fù),并且返回的value為null,所以,Server3提交了【3,server3】的提案。?
Server1在Prepare階段也收到過(guò)半回復(fù),返回的value為null,所以Server1提交了【2,server1】的提案。?
Server2也收到過(guò)半回復(fù),返回的value為null,所以Server2提交了【4,server2】的提案。?
Acceptor1和acceptor2接收到Server1的提案【2,server1】,acceptor1通過(guò)該請(qǐng)求,acceptor2承諾不再接受編號(hào)小于4的提案,所以拒絕;?
Acceptor2和acceptor3接收到Server2的提案【4,server2】,都通過(guò)該提案;?
Acceptor2和acceptor3接收到Server3的提案【3,server3】,它們都承諾不再接受編號(hào)小于4的提案,所以都拒絕。?
此時(shí),過(guò)半的acceptor(acceptor2和acceptor3)都接受了提案【4,server2】,learner感知到提案的通過(guò),learner開(kāi)始學(xué)習(xí)提案,所以server2成為最終的leader。

Multi-Paxos

剛才我講了,Paxos還過(guò)于理論,無(wú)法直接用到復(fù)制狀態(tài)機(jī)中,總的來(lái)說(shuō),有以下幾個(gè)原因

  • Paxos只能確定一個(gè)值,無(wú)法用做Log的連續(xù)復(fù)制
  • 由于有多個(gè)Proposer,可能會(huì)出現(xiàn)活鎖,如我在上面舉的例子中,Server2的一共提了兩次Propose才最終讓提案通過(guò),極端情況下,次數(shù)可能會(huì)更多
  • 提案的最終結(jié)果可能只有部分Acceptor知曉,沒(méi)法達(dá)到復(fù)制狀態(tài)機(jī)每個(gè)instance都必須有完全一致log的需求。

那么其實(shí)Multi-Paxos,其實(shí)就是為了解決上述三個(gè)問(wèn)題,使Paxos協(xié)議能夠?qū)嶋H使用在狀態(tài)機(jī)中。解決第一個(gè)問(wèn)題其實(shí)很簡(jiǎn)單。為L(zhǎng)og Entry每個(gè)index的值都是用一個(gè)獨(dú)立的Paxos instance。解決第二個(gè)問(wèn)題也很簡(jiǎn)答,讓一個(gè)Paxos group中不要有多個(gè)Proposer,在寫(xiě)入時(shí)先用Paxos協(xié)議選出一個(gè)leader(如我上面的例子),然后之后只由這個(gè)leader做寫(xiě)入,就可以避免活鎖問(wèn)題。并且,有了單一的leader之后,我們還可以省略掉大部分的prepare過(guò)程。只需要在leader當(dāng)選后做一次prepare,所有Acceptor都沒(méi)有接受過(guò)其他Leader的prepare請(qǐng)求,那每次寫(xiě)入,都可以直接進(jìn)行Accept,除非有Acceptor拒絕,這說(shuō)明有新的leader在寫(xiě)入。為了解決第三個(gè)問(wèn)題,Multi-Paxos給每個(gè)Server引入了一個(gè)firstUnchosenIndex,讓leader能夠向向每個(gè)Acceptor同步被選中的值。解決這些問(wèn)題之后Paxos就可以用于實(shí)際工程了。
Paxos到目前已經(jīng)有了很多的補(bǔ)充和變種,實(shí)際上,之后我要討論的ZAB也好,Raft也好,都可以看做是對(duì)Paxos的修改和變種,另外還有一句流傳甚廣的話,“世上只有一種一致性算法,那就是Paxos”。

ZAB

ZAB即Zookeeper Atomic BoardCast,是Zookeeper中使用的一致性協(xié)議。ZAB是Zookeeper的專用協(xié)議,與Zookeeper強(qiáng)綁定,并沒(méi)有抽離成獨(dú)立的庫(kù),因此它的應(yīng)用也不是很廣泛,僅限于Zookeeper。但ZAB協(xié)議的論文中對(duì)ZAB協(xié)議進(jìn)行了詳細(xì)的證明,證明ZAB協(xié)議是能夠嚴(yán)格滿足一致性要求的。

ZAB隨著Zookeeper誕生于2007年,此時(shí)Raft協(xié)議還沒(méi)有發(fā)明,根據(jù)ZAB的論文,之所以Zookeeper沒(méi)有直接使用Paxos而是自己造輪子,是因?yàn)樗麄冋J(rèn)為Paxos并不能滿足他們的要求。比如Paxos允許多個(gè)proposer,可能會(huì)造成客戶端提交的多個(gè)命令沒(méi)法按照FIFO次序執(zhí)行。同時(shí)在恢復(fù)過(guò)程中,有一些follower的數(shù)據(jù)不全。這些斷論都是基于最原始的Paxos協(xié)議的,實(shí)際上后來(lái)一些Paxos的變種,比如Multi-Paxos已經(jīng)解決了這些問(wèn)題。當(dāng)然我們只能站在歷史的角度去看待這個(gè)問(wèn)題,由于當(dāng)時(shí)的Paxos并不能很好的解決這些問(wèn)題,因此Zookeeper的開(kāi)發(fā)者創(chuàng)造了一個(gè)新的一致性協(xié)議ZAB。

ZAB其實(shí)和后來(lái)的Raft非常像,有選主過(guò)程,有恢復(fù)過(guò)程,寫(xiě)入也是兩階段提交,先從leader發(fā)起一輪投票,獲得超過(guò)半數(shù)同意后,再發(fā)起一次commit。ZAB中每個(gè)主的epoch number其實(shí)就相當(dāng)于我接下來(lái)要講的Raft中的term。只不過(guò)ZAB中把這個(gè)epoch number和transition number組成了一個(gè)zxid存在了每個(gè)entry中。

ZAB在做log復(fù)制時(shí),兩階段提交時(shí),一個(gè)階段是投票階段,只要收到過(guò)半數(shù)的同意票就可以,這個(gè)階段并不會(huì)真正把數(shù)據(jù)傳輸給follower,實(shí)際作用是保證當(dāng)時(shí)有超過(guò)半數(shù)的機(jī)器是沒(méi)有掛掉,或者在同一個(gè)網(wǎng)絡(luò)分區(qū)里的。第二個(gè)階段commit,才會(huì)把數(shù)據(jù)傳輸給每個(gè)follower,每個(gè)follower(包括leader)再把數(shù)據(jù)追加到log里,這次寫(xiě)操作就算完成。如果第一個(gè)階段投票成功,第二個(gè)階段有follower掛掉,也沒(méi)有關(guān)系,重啟后leader也會(huì)保證follower數(shù)據(jù)和leader對(duì)其。如果commit階段leader掛掉,如果這次寫(xiě)操作已經(jīng)在至少一個(gè)follower上commit了,那這個(gè)follower一定會(huì)被選為leader,因?yàn)樗膠xid是最大的,那么他選為leader后,會(huì)讓所有follower都commit這條消息。如果leader掛時(shí)沒(méi)有follower commit這條消息,那么這個(gè)寫(xiě)入就當(dāng)做沒(méi)寫(xiě)完。

由于只有在commit的時(shí)候才需要追加寫(xiě)日志,因此ZAB的log,只需要append-only的能力,就可以了。

另外,ZAB支持在從replica里做stale read,如果要做強(qiáng)一致的讀,可以用sync read,原理也是先發(fā)起一次虛擬的寫(xiě)操作,到不做任何寫(xiě)入,等這個(gè)操作完成后,本地也commit了這次sync操作,再在本地replica上讀,能夠保證讀到sync這個(gè)時(shí)間點(diǎn)前所有的正確數(shù)據(jù),而Raft所有的讀和寫(xiě)都是經(jīng)過(guò)主節(jié)點(diǎn)的

Raft

Raft是斯坦福大學(xué)在2014年提出的一種新的一致性協(xié)議。作者表示之所以要設(shè)計(jì)一種全新的一致性協(xié)議,是因?yàn)镻axos實(shí)在太難理解,而且Paxos只是一個(gè)理論,離實(shí)際的工程實(shí)現(xiàn)還有很遠(yuǎn)的路。因此作者狠狠地吐槽了Paxos一把:

  • Paxos協(xié)議中,是不需要Leader的,每個(gè)Proposer都可以提出一個(gè)propose。相比Raft這種一開(kāi)始設(shè)計(jì)時(shí)就把選主和協(xié)議達(dá)成一致分開(kāi)相比,Paxos等于是把選主和propose階段雜糅在了一起,造成Paxos比較難以理解。
  • 最原始的Paxos協(xié)議只是對(duì)單一的一次事件答成一致,一旦這個(gè)值被確定,就無(wú)法被更改,而在我們的現(xiàn)實(shí)生活中,包括我們數(shù)據(jù)庫(kù)的一致性,都需要連續(xù)地對(duì)log entry的值答成一致,所以單單理解Paxos協(xié)議本身是不夠的,我們還需要對(duì)Paxos協(xié)議進(jìn)行改進(jìn)和補(bǔ)充,才能真正把Paxos協(xié)議應(yīng)用到工程中。而對(duì)Paxos協(xié)議的補(bǔ)充本身又非常復(fù)雜,而且雖然Paxos協(xié)議被Lamport證明過(guò),而添加了這些補(bǔ)充后,這些基于Paxos的改進(jìn)算法,如Multi-Paxos,又是未經(jīng)證明的。
  • 第三個(gè)槽點(diǎn)是Paxos協(xié)議只提供了一個(gè)非常粗略的描述,導(dǎo)致后續(xù)每一個(gè)對(duì)Paxos的改進(jìn),以及使用Paxos的工程,如Google的Chubby,都是自己實(shí)現(xiàn)了一套工程來(lái)解決Paxos中的一些具體問(wèn)題。而像Chubby的實(shí)現(xiàn)細(xì)節(jié)其實(shí)并沒(méi)有公開(kāi)。也就是說(shuō)要想在自己的工程中使用Paxos,基本上每個(gè)人都需要自己定制和實(shí)現(xiàn)一套適合自己的Paxos協(xié)議。
  • 因此,Raft的作者在設(shè)計(jì)Raft的時(shí)候,有一個(gè)非常明確的目標(biāo),就是讓這個(gè)協(xié)議能夠更好的理解,在設(shè)計(jì)Raft的過(guò)程中,如果遇到有多種方案可以選擇的,就選擇更加容易理解的那個(gè)。作者舉了一個(gè)例子。在Raft的選主階段,本來(lái)可以給每個(gè)server附上一個(gè)id,大家都去投id最大的那個(gè)server做leader,會(huì)更快地達(dá)成一致(類似ZAB協(xié)議),但這個(gè)方案又增加了一個(gè)serverid的概念,同時(shí)在高id的server掛掉時(shí),低id的server要想成為主必須有一個(gè)等待時(shí)間,影響可用性。因此Raft的選主使用了一個(gè)非常簡(jiǎn)單的方案:每個(gè)server都隨機(jī)sleep一段時(shí)間,最早醒過(guò)來(lái)的server來(lái)發(fā)起一次投票,獲取了大多數(shù)投票即可為主。在通常的網(wǎng)絡(luò)環(huán)境下,最早發(fā)起投票的server也會(huì)最早收到其他server的贊成票,因此基本上只需要一輪投票就可以決出leader。整個(gè)選主過(guò)程非常簡(jiǎn)單明了。

    除了選主,整個(gè)Raft協(xié)議的設(shè)計(jì)都非常簡(jiǎn)單。leader和follower之間的交互(如果不考慮snapshot和改變成員數(shù)量)一共只有2個(gè)RPC call。其中一個(gè)還是選主時(shí)才需要的RequestVote。也就是說(shuō)所有的數(shù)據(jù)交互,都只由AppendEntries 這一個(gè)RPC完成。
    理解Raft算法,首先要理解Term這個(gè)概念。每個(gè)leader都有自己的Term,而且這個(gè)term會(huì)帶到log的每個(gè)entry中去,來(lái)代表這個(gè)entry是哪個(gè)leader term時(shí)期寫(xiě)入的。另外Term相當(dāng)于一個(gè)lease。如果在規(guī)定的時(shí)間內(nèi)leader沒(méi)有發(fā)送心跳(心跳也是AppendEntries這個(gè)RPC call),Follower就會(huì)認(rèn)為leader已經(jīng)掛掉,會(huì)把自己收到過(guò)的最高的Term加上1做為新的term去發(fā)起一輪選舉。如果參選人的term還沒(méi)自己的高的話,follower會(huì)投反對(duì)票,保證選出來(lái)的新leader的term是最高的。如果在time out周期內(nèi)沒(méi)人獲得足夠的選票(這是有可能的),則follower會(huì)在term上再加上1去做新的投票請(qǐng)求,直到選出leader為止。最初的raft是用c語(yǔ)言實(shí)現(xiàn)的,這個(gè)timeout時(shí)間可以設(shè)置的非常短,通常在幾十ms,因此在raft協(xié)議中,leader掛掉之后基本在幾十ms就能夠被檢測(cè)發(fā)現(xiàn),故障恢復(fù)時(shí)間可以做到非常短。而像用Java實(shí)現(xiàn)的Raft庫(kù),如Ratis,考慮到GC時(shí)間,我估計(jì)這個(gè)超時(shí)時(shí)間沒(méi)法設(shè)置這么短。

    在Leader做寫(xiě)入時(shí)也是一個(gè)兩階段提交的過(guò)程。首先leader會(huì)把在自己的log中找到第一個(gè)空位index寫(xiě)入,并通過(guò)AppendEntries這個(gè)RPC把這個(gè)entry的值發(fā)給每個(gè)follower,如果收到半數(shù)以上的follower(包括自己)回復(fù)true,則再下一個(gè)AppendEntries中,leader會(huì)把committedIndex加1,代表寫(xiě)入的這個(gè)entry已經(jīng)被提交。如在下圖中,leader將x=4寫(xiě)入index=8的這個(gè)entry中,并把他發(fā)送給了所有follower,在收到第一臺(tái)(自己),第三臺(tái),第五臺(tái)(圖中沒(méi)有畫(huà)index=8的entry,但因?yàn)檫@臺(tái)服務(wù)器之前所有的entry都和leader保持了一致,因此它一定會(huì)投同意),那么leader就獲得了多數(shù)票,再下一個(gè)rpc中,會(huì)將Committed index往前挪一位,代表index<=8的所有entry都已經(jīng)提交。至于第二臺(tái)和第四臺(tái)服務(wù)器,log內(nèi)容已經(jīng)明顯落后,這要么是因?yàn)榍皫状蝦pc沒(méi)有成功。leader會(huì)無(wú)限重試直到這些follower和leader的日志追平。另外一個(gè)可能是這兩臺(tái)服務(wù)器重啟過(guò),處于恢復(fù)狀態(tài)。那么這兩臺(tái)服務(wù)器在收到寫(xiě)入index=8的RPC時(shí),follower也會(huì)把上一個(gè)entry的term和index發(fā)給他們。也就是說(shuō)prevLogIndex=7,prevLogTerm=3這個(gè)信息會(huì)發(fā)給第二臺(tái)服務(wù)器,那么對(duì)于第二臺(tái)服務(wù)器,index=7的entry是空的,也就是log和leader不一致,他會(huì)返回一個(gè)false給leader,leader會(huì)不停地從后往前遍歷,直到找到一個(gè)entry與第二臺(tái)服務(wù)器一致的,從這個(gè)點(diǎn)開(kāi)始重新把leader的log內(nèi)容發(fā)送給該follower,即可完成恢復(fù)。raft協(xié)議保證了所有成員的replicated log中每個(gè)index位置,如果他們的term一致,內(nèi)容也一定一致。如果不一致,leader一定會(huì)把這個(gè)index的內(nèi)容改寫(xiě)成和leader一致。

    其實(shí)經(jīng)過(guò)剛才我的一些描述,基本上就已經(jīng)把Raft的選主,寫(xiě)入流程和恢復(fù)基本上都講完了。從這里,我們可以看出Raft一些非常有意思的地方。

    第一個(gè)有意思的地方是Raft的log的entry是可能被修改的,比如一個(gè)follower接收了一個(gè)leader的prepare請(qǐng)求,把值寫(xiě)入了一個(gè)index,而這個(gè)leader掛掉,新選出的leader可能會(huì)重新使用這個(gè)index,那么這個(gè)follower的相應(yīng)index的內(nèi)容,會(huì)被改寫(xiě)成新的內(nèi)容。這樣就造成了兩個(gè)問(wèn)題,首先第一個(gè),raft的log無(wú)法在append-only的文件或者文件系統(tǒng)上去實(shí)現(xiàn),而像ZAB,Paxos協(xié)議,log只會(huì)追加,只要求文件系統(tǒng)有append的能力即可,不需要隨機(jī)訪問(wèn)修改能力。
    第二個(gè)有意思的地方是,為了簡(jiǎn)單,Raft中只維護(hù)了一個(gè)Committed index,也就是任何小于等于這個(gè)committedIndex的entry,都是被認(rèn)為是commit過(guò)的。這樣就會(huì)造成在寫(xiě)入過(guò)程中,在leader獲得大多數(shù)選票之前掛掉(或者leader在寫(xiě)完自己的log之后還沒(méi)來(lái)得及通知到任何follower就掛掉),重啟后如果這個(gè)server繼續(xù)被選為leader,這個(gè)值仍然會(huì)被commit永久生效。因?yàn)閘eader的log中有這個(gè)值,leader一定會(huì)保證所有的follower的log都和自己保持一致。而后續(xù)的寫(xiě)入在增長(zhǎng)committedIndex后,這個(gè)值也默認(rèn)被commit了。

    舉例來(lái)說(shuō),現(xiàn)在有5臺(tái)服務(wù)器,其中S2為leader,但是當(dāng)他在為index=1的entry執(zhí)行寫(xiě)入時(shí),先寫(xiě)到了自己的log中,還沒(méi)來(lái)得及通知其他server append entry就宕機(jī)了。?

    當(dāng)S2重啟后,任然有可能被重新當(dāng)選leader,當(dāng)S2重新當(dāng)選leader后,仍然會(huì)把index=1的這個(gè)entry復(fù)制給每臺(tái)服務(wù)器(但是不會(huì)往前移動(dòng)Committed index)

    此時(shí)S2又發(fā)生一次寫(xiě)入,這次寫(xiě)入完成后,會(huì)把Committed index移動(dòng)到2的位置,因此index=1的entry也被認(rèn)為已經(jīng)commit了。

    這個(gè)行為有點(diǎn)奇怪,因?yàn)檫@樣等于raft會(huì)讓一個(gè)沒(méi)有獲得大多數(shù)人同意的值最終commit。這個(gè)行為取決于leader,如果上面的例子中S2重啟后沒(méi)有被選為leader,index=1的entry內(nèi)容會(huì)被新leader的內(nèi)容覆蓋,從而不會(huì)提交未經(jīng)過(guò)表決的內(nèi)容。

    雖然說(shuō)這個(gè)行為是有點(diǎn)奇怪,但是不會(huì)造成任何問(wèn)題,因?yàn)閘eader和follower還是會(huì)保持一致,而且在寫(xiě)入過(guò)程中l(wèi)eader掛掉,對(duì)客戶端來(lái)說(shuō)是本來(lái)就是一個(gè)未決語(yǔ)義,raft論文中也指出,如果用戶想要exactly once的語(yǔ)義,可以在寫(xiě)入的時(shí)候加入一個(gè)類似uuid的東西,在寫(xiě)入之前l(fā)eader查下這個(gè)uuid是否已經(jīng)寫(xiě)入。那么在一定程度上,可以保證exactly once的語(yǔ)義。

    Raft的論文中也比較了ZAB的算法,文中說(shuō)ZAB協(xié)議的一個(gè)缺點(diǎn)是在恢復(fù)階段需要leader和follower來(lái)回交換數(shù)據(jù),這里我沒(méi)太明白,據(jù)我理解,ZAB在重新選主的過(guò)程中,會(huì)選擇Zxid最大的那個(gè)從成為主,而其他follower會(huì)從leader這里補(bǔ)全數(shù)據(jù),并不會(huì)出現(xiàn)leader從follower節(jié)點(diǎn)補(bǔ)數(shù)據(jù)這一說(shuō)。

    后話

    目前,經(jīng)過(guò)改進(jìn)的Paxos協(xié)議已經(jīng)用在了許多分布式產(chǎn)品中,比如說(shuō)Chubby,PaxosStore,阿里云的X-DB,以及螞蟻的OceanBase,都選用了Paxos協(xié)議,但他們都多多少少做了一些補(bǔ)充和改進(jìn)。而像Raft協(xié)議,普遍認(rèn)為Raft協(xié)議只能順序commit entry,性能沒(méi)有Paxos好,但是TiKV中使用了Raft,其公開(kāi)的文章宣傳對(duì)Raft做了非常多的優(yōu)化,使Raft的性能變的非常可觀。阿里的另外一個(gè)數(shù)據(jù)庫(kù)PolarDB,也是使用了改進(jìn)版的Parallel-Raft,使Raft實(shí)現(xiàn)了并行提交的能力。相信未來(lái)會(huì)有更多的基于Paxos/Raft的產(chǎn)品會(huì)面世,同時(shí)也對(duì)Raft/Paxos也會(huì)有更多的改進(jìn)。

    ?


    原文鏈接
    本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。

    總結(jié)

    以上是生活随笔為你收集整理的一致性协议浅析:从逻辑时钟到Raft的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。