目錄
目錄 1
1. 前言 1
2. 名詞 1
3. 什么是分布式一致性? 3
4. Raft選舉 3
4.1. 什么是Leader選舉? 3
4.2. 選舉的實現 4
4.3. Term和Lease比較 4
4.4. 選舉圖示 4
4.5. 選舉總結 7
5. Raft日志復制 8
5.1. 什么是日志復制? 8
5.2. 日志復制的實現 8
5.3. 腦裂時的復制 10
6. 概念對比 12
7. 共性探討 13
7.1. PacificA和HBase和Kafka 13
7.2. Redis&Raft&ZAB&PaxOS 13
8. 總結濃縮 13
8.1. 三種角色 13
8.2. 兩種RPC 13
8.3. 兩個超時時間 14
9. 參考 14
1. 前言
常見的一致性協議主要有:PaxOS、Raft、ZAB、PacificA等。同PaxOS,Raft也不考慮拜占庭將軍問題(Byzantine failures,注:比特幣采用工作量證明PoW和股權證明PoS解決了拜占庭將軍問題)。
2. 名詞
Distributed Consensus |
分布式一致性 |
Distributed Consensus Algorithms |
分布式一致性算法,PaxOS是分布式一致性算法的鼻祖,絕大多數的實現要么基於PaxOS,要么受PaxOS影響(如Zookeeper)。 |
|
就分布式系統中的某個值(在PaxOS中叫決議,即Proposer)達成一致的算法。PaxOS是分布式一致性協議的鼻祖,在Google的三大件未推出之前少為人知,1989年萊斯利·蘭伯特提出。Leslie Lamport是大神的英文名,出生於1941年2月7日紐約。蘭伯特目前就職於微軟研究院,他也是排版系統LaTeX的作者,2008年獲得馮諾依曼獎,2013年獲得圖靈獎,麻省理工學院學士和布蘭戴斯大學博士。最早的實現為Google內部使用的Chubby。 |
Raft |
木筏協議,一種共識算法,旨在替代PaxOS,相比容易理解許多。斯坦福大學的兩個博士Diego Ongaro和John Ousterhout兩個人以易懂為目標設計的一致性算法,2013以論文方式發布。由於易懂,不從論文發布到不到兩年的時間,即出現大量的Raft開源實現。為簡化PaxOS的晦澀,它采用分而治之的辦法,將算法分為三個子問題:選舉(Leader Election)、日志復制(Log Replication)、安全性(Safety)和集群成員動態變更(Cluster Membership Changes)。 |
ZAB |
Zookeeper采用的一致性算法,全稱“ZooKeeper Atomic Broadcast”,即Zookeeper原子廣播協議,實為Zookeeper版本的PaxOS。 |
微軟2008年提出的日志復制協議,與PaxOS和Raft不同,PacificA並不提供Leader選舉,因此需要結合PaxOS或Raft,分布式消息隊列Kafka的ISR算法類似於PacificA。小米開源的KV系統Pegasus基於PacificA實現。 |
|
Viewstamped Replication |
簡稱VR,最初被提出是作為數據庫中的一部分工作,2012年作為單獨的分布式共識算法再次發表。 |
Log Replication |
日志復制 |
Leader Election |
領導選舉 |
Log |
代表一個修改操作 |
Entry |
(日志)條目(代表一個修改操作),Log在Raft中的實體,即一個Entry表示一條Log |
Term |
任期(可簡單理解成租約,但稍有不同),是一個從0開始遞增的整數值,每次發起選舉時增一,最終所有節點的Term值是相同的。等同於ZAB協議中的zxid的高32位,即ZAB中的Epoch。 |
Index |
和Term密切相關,Term針對的是選舉,而Index針對的是一條Log,它的值也是從0開始遞增。同樣最終所有節點的Index也相同,如果兩個節點的Term和Index均相同,則這兩個節點的數據是完全一致的。等同於ZAB協議中的zxid的低32位。 |
Node |
節點 |
Single Node |
單節點 |
Multiple Nodes |
多節點 |
Leader |
領導(唯一接受修改操作的) |
Follower |
跟隨者(投票參與者) |
Candidate |
候選人(唯一具有發起選舉的) |
Vote |
投票 |
Heal |
恢復,比如恢復網絡分區(Heal the network partition) |
Step Down |
下台,當一個Leader發現有更大的Term時,自動降為Follower |
Logic Clock |
邏輯時鍾,由Lampert提出,原因是分布式中無法使用精准的時鍾維護事件的先后順序,方法是為每個消息(或日志)加上一個邏輯時間戳。在ZAB協議中,每個消息均由唯一的zxid標識。zxid是一個64位無符號整數值,被分成兩部分:高32位是Epoch,低32位是一個從0開始的遞增值。類似於Raft的Term,每次選舉新Leader,Epoch值增一,但同時zxid值清0。而Raft也類似,實際也是兩部分組成,Term對應於Epoch,Index對應於zxid的低32位。 |
Quorum |
法定人數 |
Brain Split |
腦裂,同一個網絡的節點被分成了兩個或多個不互通的部分 |
3. 什么是分布式一致性?
多個節點(Multiple Nodes)的數據完全相同,即為分布式一致性。但因為多個節點通過網絡互聯,並不一定時刻可用,而服務不能因為某些節點(特別是少數節點)不可用時,導致整個系統不可用。
Raft將節點分成三種角色:Leader、Follower和Candidate,一個節點可為三種角色中的任意一種,但同一時刻只會為其中一種角色。正常情況下,只有Leader和Follower兩種角色的節點,只有在選舉時才會出現Candidate角色節點,而且可能多個節點同時處於Candidate角色同時競爭選舉。
4. Raft選舉
4.1. 什么是Leader選舉?
Raft協議中,一個節點有三個狀態:Leader、Follower和Candidate,但同一時刻只能處於其中一種狀態。Raft選舉實際是指選舉Leader,選舉是由候選者(Candidate)主動發起,而不是由其它第三者。
並且約束只有Leader才能接受寫和讀請求,只有Candidate才能發起選舉。如果一個Follower和它的Leader失聯(失聯時長超過一個Term),則它自動轉為Candidate,並發起選舉。
發起選舉的目的是Candidate請求(Request)其它所有節點投票給自己,如果Candidate獲得多數節點(a majority of nodes)的投票(Votes),則自動成為Leader,這個過程即叫Leader選舉。
在Raft協議中,正常情況下Leader會周期性(不能大於Term)的向所有節點發送AppendEntries RPC,以維持它的Leader地位。
相應的,如果一個Follower在一個Term內沒有接收到Leader發來的AppendEntries RPC,則它在延遲隨機時間(150ms~300ms)后,即向所有其它節點發起選舉。
采取隨機時間的目的是避免多個Followers同時發起選舉,而同時發起選舉容易導致所有Candidates都未能獲得多數Followers的投票(腦裂,比如各獲得了一半的投票,誰也不占多數,導致選舉無效需重選),因而延遲隨機時間可以提高一次選舉的成功性。
4.2. 選舉的實現
在Raft中,和選舉有關的超時值有兩個:
選舉超時(Election Timeout) |
Follower等待成為Candidate的時間,為隨機時間,隨機范圍為150ms~250ms。如果在超時之前收到了其它的AppendEntries,則重置選舉超時重新計時,否則在選舉超時后,Follower成為Candidate,投票給自己(Votes for itself)並發起新的選舉任期(Term),發送RequestVote給所有其它節點,以請求投票給自己。如果在這個Term時間內仍然沒有獲得投票,則重置選舉超時,以准備重新發起選舉。而一旦獲得多數節點的投票,則成為Leader。成為Leader后,定期性的發送AppendEntries給所有Followers,以維持Leader地位。發送AppendEntries的間隔叫作心跳超時。 |
心跳超時(Heartbeat Timeout) |
Follower會應答每一個AppendEntries,一個選舉任期(Election Term)會一直存在,直到有新的Follower成為Candidate。Leader節點通過定期發送AppendEntries維護自己的Leader地位,定期的間隔時長即為心跳超時時長。在Raft中,心跳方向是:Leader -> Follower。 |
如果一個Follower在一個Term時間內沒有收到AppendEntries(並非要求是來自Leader的AppendEntries,其它Candidates也一樣有效),則它成為Candidate,並在等待選舉超時(Election Timeout)后發起選舉。
要求獲得多數節點的投票,可以保證每個任期(Term)內只有一個Leader。如果兩個Candidates同時發起選舉,則會分割投票,這樣需要再重新發起選舉。Raft設計“選舉超時”,可以降低兩個或多個Candidates同時發起選舉的概率,減少連續重選舉。
Redis的選舉有個借鑒意義,在Redis集群中,不同節點可有不同權重,權重大的優先獲得發起選舉幾率。在Redis中,權重是指復制節點數據的新舊程度,而對Raft來說可考慮機架機房等作為權重。
4.3. Term和Lease比較
Raft中的Term可以簡單看成Lease(租約),但概念上仍然是有區別的。Lease通常是一個節點向一個中心化節點請求,而Term是無中心化的。
如果沒有發生選舉超時,則Term的值不會發生變化,否則至少增一。
4.4. 選舉圖示
每個節點的Term值初始化為1(圓圈中的數字),每個節點選舉超時時間為一個150ms~300ms的隨機值(實際實現可以調整,但這個值是發明者測試得到的優值,詳情請參見兩位斯坦福大學教授的論文):
節點S2最先發起選舉成為Leader,在發起選舉之前,它需要先自增自己的Term值,因此Term值由1變成2,同時其它節點的Term值也需要更新為比自己大的Term值(如果一個Leader遇到一個更大的Term,它需要主動降為Follower):
人工將節點S1和S2停掉,這時兩者不可用。這會導致其它正常的節點S3、節點S4和節點S5在選舉超時時間內收不到來自Leader的心跳(實為AppendEntry),因此會觸發節點S3、節點S4和節點S5發起選舉(究竟哪個節點發起選舉,視哪個節點最先達到選舉超時):
因為節點S2的數據處於未提交狀態,因為它的已提交Index值要小於S3和S5,它沒有最新的數據,不可能成為新的Leader。新的Leader必然在S3和S5中產生。先強制重置S2的選舉超時時長,以讓出優先發起選舉:
雖然S3和S5均有最新的數據,但因為S3的Term小於S5的Term,因此只有S5才能成為Leader,這樣最新的Term值將為5+1為6:
這個時候恢復節點S1,Term值是否會改變?
一個Leader節點,如果遇到比自有更大的Term值,則主動放棄Leader身份,並使用原Term替代自己的Term,然后轉變為Candidate重新開始選舉。
非Leader節點也是一樣的,只要遇到比自己大的Term,都會使用更大的Term替代自己的Term。這樣能保證節點均能正常通信時,Leader節點總是持有最大的Term。
4.5. 選舉總結
能成為Leader的條件:
1) 有最大的Term;
2) 如果Term相同,則有最大的Index;
3) 如果Term相同,Index也相同,就看誰最先發起;
4) 最先發起者也不一定成為Leader,還得看誰最先獲得多數選票。
一個Leader主動放棄Leader條件:
1) 遇到比自己更大的Term,這個時候會轉為Follower。
5. Raft日志復制
5.1. 什么是日志復制?
Leader收到一個修改請求后,先將該請求記錄到本地日志,這條日志在Raft中叫作Enry,此時處於未提交狀態(Uncommitted),然后復制Entry給所有的Followers。當多數Followers節點確認已完成該Entry的寫入后,Leader本地提交該Entry,響應客戶端成功,這個過程叫日志復制(Log Replication)。如果一個Entry不能復制到多數節點,則該Entry狀態一直為未提交,如果發生Leader轉換,有可能被覆蓋。
5.2. 日志復制的實現
正常的復制不需要理解,主要看異常時的復制處理。讓節點S5成為Leader,然后停掉節點S2、S3和S4,然后客戶端再請求寫操作,但此時由於寫操作不能得到多數節點的確認,所以無法提交(不能應用到狀態機中):
先停掉節點S1和S5,再恢復節點S2、S3和S4,並讓節點S3成為Leader,然后客戶端發起一筆寫操作。讓寫操作在三個節點S2、S3和S4中得到確認,這樣該筆寫操作為已提交狀態(Committed),可以看到變成如下狀態了。顯然節點S1和節點S5上存在了兩筆臟數據:
恢復節點S1:
停止節點S3,構造條件讓S1成為新的Leader:
\
5.3. 腦裂時的復制
網絡分裂時,出現兩個Leader:
具有多數節點的一方仍然能夠繼續,但少數一方已不可用,這個時候從節點B讀不到最新的數據:
當網絡恢復后,節點B發現更大的Term,自動從Leader將為Follower,同時回滾未提交的Entries,集群又重新實現一致。
6. 概念對比
Raft |
PaxOS (Multi PaxOS) |
ZAB |
PacificA |
Viewstamped Replication |
獲得多數同意 |
獲得多數同意 |
獲得多數同意 |
需所有都同意 |
獲得多數同意 |
強Leader(領導),總是包含了最全最新的日志,日志無空洞,隨機化簡化Leader選舉(Redis也采用了這個方法,並且加了節點權重)。 |
Proposer(提議者),允許有多個Proposer,並且不一定最包含了最全最新的日志,日志可存在空洞 |
強Leader(領導) |
Primary(主) |
Primary |
Follower(跟隨者) |
Acceptor(接受者) |
Follower(跟隨者,參與投票) |
Secondary(副),接收Primary的心跳,一個Primary對多個Secondary |
Backup |
Candidate(候選者) |
Learner(學習者) |
Looking(競選狀態) |
|
|
|
|
Observing(觀察者,不參與投票,只同步狀態) |
|
|
Entry |
達到一致的叫Value(即決議),達到一致前叫提議 |
類似PaxOS |
|
|
Term(任期) |
Ballot(選票) |
Epoch(紀元),為txid的高32位(注:Redis中也叫Epoch,作用類似) |
View |
|
Index(序號) |
Proposal Number |
txid的低32位 |
Serial Number |
|
7. 共性探討
所有的並不是完全孤立的,而是存在很多共性。
7.1. PacificA和HBase和Kafka
PacificA和HBase均依賴於PaxOS這樣的設施,HBase依賴於Zookeeper,PacificA同樣依賴於類似的配置管理服務。
如同HBase依賴Zookeeper來發現Keys的位置信息,PacificA依賴配置管理服務發現分片的位置。
Kafka其實也類似,早期Kafka直接將位置信息存儲在了Zookeeper中,遭遇了Zookeeper吞吐瓶頸,於是采取和HBase類似的做法,將位置信息作為一個特殊的Partition。
7.2. Redis&Raft&ZAB&PaxOS
Redis同Raft、ZAB和PaxOS並不是一類東西,但核心均有Quorum的朴素思想,即服從多數(多數人理解的即為真理),而且均依賴於一個遞增的值來達到一致性狀態,這個遞增的值在Redis和ZAB中叫Epoch,Raft中叫Term。
8. 總結濃縮
8.1. 三種角色
1) Leader(領導者)
2) Follower(跟隨者)
3) Candidate(候選人)
8.2. 兩種RPC
1) AppendEntry(心跳和寫操作)
2) RequestVote(請求投票)
8.3. 兩個超時時間
中文名 |
英文名 |
主要問題 |
小技巧 |
選舉超時 |
Election Timeout |
選票分裂造成活鎖 |
隨機時間(Redis非強一致,另加了權重) |
心跳超時 |
Heartbeat Timeout |
不能小於選舉超時時長 |
|
9. 參考
1) https://translate.google.cn
2) https://raft.github.io/raft.pdf
4) http://thesecretlivesofdata.com/raft/
6) 分布式共識(Consensus):Viewstamped Replication、Raft以及Paxos
7) https://static.googleusercontent.com/media/research.google.com/en//archive/paxos_made_live.pdf