分布式理論系列(二)一致性算法:2PC 到 3PC 到 Paxos 到 Raft 到 Zab
本文介紹一致性算法: 2PC 到 3PC 到 Paxos 到 Raft 到 Zab
兩類一致性算法(操作原子性與副本一致性)
-
2PC 3PC
協議用於保證屬於多個數據分片上的操作的原子性。這些數據分片可能分布在不同的服務器上,2PC 協議保證多台服務器上的操作要么全部成功,要么全部失敗。 -
Paxos Raft Zab
協議用於保證同一個數據分片的多個副本之間的數據一致性。當這些副本分布到不同的數據中心時,這個需求尤其強烈。
一、2PC(阻塞、數據不一致問題、單點問題)
Two-Phase Commit(兩階段提交) 是計算機網絡尤其是在數據庫領域內,為了使基於分布式系統架構下的所有節點在進行事務處理過程中能夠保持原子性和致性而設計的一種算法。通常,二階段提交協議也被認為是一種一致性協議,用來保證分布式系統數據的一致性。絕大部分的關系型數據庫都是采用二階段提交協議來完成分布式事務處理。
1.1 協議介紹
第一階段:提交事務請求(投票階段)
-
事務詢問
協調者向所有的參與者發送事務內容,詢問是否可以執行事務提交操作,並開始等待各參與者的響應。
-
執行事務
各參與者節點執行事務操作,並將 Undo 和 Redo 信息計入事務日志中。
-
各參與者向協調者反饋事務詢問的響應
如果參與者成功執行了事務操作,那么就反饋給協調者 Yes 響應,表示事務可以執行;如果參與者沒有成功執行事務,那么就反饋給協調者 No 響應,表示事務不可以執行。
第二階段:執行事務提交(執行階段)
(1)執行事務提交
如果所有參與者的反饋都是 Yes 響應,那么
-
發送提交請求
協調者向所有參與者節點發出Commit請求
-
事務提交
參與者接收到Commit請求后,會正式執行事務提交操作,並在完成提交之后釋放在整個事務執行期間占用的事務資源
-
反饋事務提交結果
參與者在完成事務提交之后,向協調者發送ACK信息
-
完成事務
協調者接收到所有參與者反饋的ACK消息后,完成事務
(2)中斷事務
任何一個參與者反饋了 No 響應,或者在等待超時之后,協調者尚無法接收到所有參與者的反饋響應,那么就會中斷事務。
-
發送回滾請求
協調者向所有參與者節點發出Rollback請求
-
事務回滾
參與者接收到rollback請求后,會利用其在階段一中記錄的Undo信息來執行事務回滾操作,並在完成回滾之后釋放整個事務執行期間占用的資源
-
反饋事務回滾結果
參與者在完成事務回滾之后,向協調者發送ACK信息
-
中斷事務
協調者接收到所有參與者反饋的ACK信息后,完成事務中斷
1.2 優缺點
優點:原理簡單、實現方便
缺點:同步阻塞、單點問題、數據不一致、太過保守
(1)同步阻塞
同步阻塞會極大地限制分布式系統的性能。在二階段提交的執行過程中,所有參與該事務操作的邏輯都處於阻塞狀態,各個參與者在等待其他參與者響應的過程中,將無法進行其他任何操作。
(2)單點問題
一旦協調者出現問題,那么整個二階段提交流程將無法運轉,更為嚴重的是,如果是在階段二中出現問題,那么其他參與者將會一直處於鎖定事務資源的狀態中,無法繼續完成事務操作。
(3)數據不一致
在階段二,當協調者向所有參與者發送commit請求之后,發生了局部網絡異常或協調者在尚未發完commit請求之前自身發生了崩潰,導致最終只有部分參與者接收到了commit請求,於是這部分參與者執行事務提交,而沒收到commit請求的參與者則無法進行事務提交,於是整個分布式系統出現了數據不一致性現象。
(4)太過保守
如果參與者在與協調者通信期間出現故障,協調者只能靠超時機制來判斷是否需要中斷事務,這個策略比較保守,需要更為完善的容錯機制,任意一個節點的失敗都會導致整個事務的失敗。
二、3PC(解決2PC的阻塞,但還是可能造成數據不一致)
Three-Phase Commit,三階段提交,分為 CanCommit、PreCommit、doCommit 三個階段。
2.1 協議介紹
為了避免在通知所有參與者提交事務時,其中一個參與者 crash 不一致時,就出現了三階段提交的方式。三階段提交在兩階段提交的基礎上增加了一個 preCommit 的過程,當所有參與者收到 preCommit 后,並不執行動作,直到收到 commit 或超過一定時間后才完成操作。
第一階段: CanCommit
-
事務詢問
協調者向各參與者發送 CanCommit 的請求,詢問是否可以執行事務提交操作,並開始等待各參與者的響應 -
參與者向協調者反饋詢問的響應
參與者收到 CanCommit 請求后,正常情況下,如果自身認為可以順利執行事務,那么會反饋 Yes 響應,並進入預備狀態,否則反饋 No。
第二階段: PreCommit
(1)執行事務預提交
如果協調者接收到各參與者反饋都是 Yes,那么執行事務預提交
-
發送預提交請求
協調者向各參與者發送 preCommit 請求,並進入 prepared 階段 -
事務預提交
參與者接收到 preCommit 請求后,會執行事務操作,並將Undo和Redo信息記錄到事務日記中 -
各參與者向協調者反饋事務執行的響應
如果各參與者都成功執行了事務操作,那么反饋給協調者 Ack 響應,同時等待最終指令,提交 commit 或者終止 abort
(2)中斷事務
如果任何一個參與者向協調者反饋了No響應,或者在等待超時后,協調者無法接收到所有參與者的反饋,那么就會中斷事務。
-
發送中斷請求
協調者向所有參與者發送 abort 請求 -
中斷事務
無論是收到來自協調者的 abort 請求,還是等待超時,參與者都中斷事務
第三階段: doCommit
(1)執行提交
-
發送提交請求
假設協調者正常工作,接收到了所有參與者的ack響應,那么它將從預提交階段進入提交狀態,並向所有參與者發送doCommit請求 -
事務提交
參與者收到doCommit請求后,正式提交事務,並在完成事務提交后釋放占用的資源 -
反饋事務提交結果
參與者完成事務提交后,向協調者發送ACK信息 -
完成事務
協調者接收到所有參與者ack信息,完成事務
(2)中斷事務
假設協調者正常工作,並且有任一參與者反饋No,或者在等待超時后無法接收所有參與者的反饋,都會中斷事務
-
發送中斷請求
協調者向所有參與者節點發送abort請求 -
事務回滾
參與者接收到 abort 請求后,利用 undo 日志執行事務回滾,並在完成事務回滾后釋放占用的資源 -
反饋事務回滾結果
參與者在完成事務回滾之后,向協調者發送 ack 信息 -
中斷事務
協調者接收到所有參與者反饋的 ack 信息后,中斷事務。
階段三可能出現的問題:
協調者出現問題、協調者與參與者之間網絡出現故障。不論出現哪種情況,最終都會導致參與者無法及時接收到來自協調者的 doCommit 或是 abort 請求,針對這種情況,參與者都會在等待超時后,繼續進行事務提交(timeout 后中斷事務)。
2.2 優缺點
優點:降低參與者阻塞范圍,並能夠在出現單點故障后繼續達成一致
缺點:引入 preCommit 階段,在這個階段如果出現網絡分區,協調者無法與參與者正常通信,參與者依然會進行事務提交,造成數據不一致。
三、Paxos(解決單點問題)
Paxos 算法目的是讓整個集群的結點對某個值的變更達成一致。Paxos 算法(強一致性算法)屬於多數派——大多數的決定會成個整個集群的統一決定。任何一個點都可以提出要修改某個數據的提案,是否通過這個提案取決於這個集群中是否有超過半數的結點同意(所以 Paxos 算法需要集群中的結點是單數)
這個算法有兩個大階段,四個小階段(Paxos 有 Proposer 和 Acceptor 兩個角色)
Prepare
proposer 提出一個提案,編號為 N ,此 N 大於這個 proposer 之前提出提案編號。請求 acceptors 的 quorum 接受。Promise
如果 N 大於此 acceptor 之前接受的任何提案編號則接受,否則拒絕Accept
如果達到了多數派, Proposer 會發出 accept 請求,此請求包含提案編號 N,以及提案內容Accepted
如果此 acceptor 在此期間沒有收到任何編號大於 N 的提案,則接受此提案內容,否則忽略
下面詳細解釋一下這四個階段(A 為 Proposer, A、B、C 均為 Acceptor)
第一階段:Prepare 階段
A 把申請修改的請求 Prepare Request 發給所有的結點 A,B,C。注意,Paxos 算法會有一個 Sequence Number(你可以認為是一個提案號,這個數不斷遞增,而且是唯一的,也就是說 A 和 B 不可能有相同的提案號),這個提案號會和修改請求一同發出,任何結點在 “Prepare 階段” 時都會拒絕其值小於當前提案號的請求。所以,結點 A 在向所有結點申請修改請求的時候,需要帶一個提案號,越新的提案,這個提案號就越是是最大的。
如果接收結點收到的提案號 n 大於其它結點發過來的提案號,這個結點會回應 Yes(本結點上最新的被批准提案號),並保證不接收其它 <n 的提案。這樣一來,結點上在 Prepare 階段里總是會對最新的提案做承諾。
優化:在上述 prepare 過程中,如果任何一個結點發現存在一個更高編號的提案,則需要通知提案人,提醒其中斷這次提案。
第二階段:Accept 階段
如果提案者 A 收到了超過半數的結點返回的 Yes,然后他就會向所有的結點發布 Accept Request(同樣,需要帶上提案號 n),如果沒有超過半數的話,那就返回失敗。
當結點們收到了 Accept Request 后,如果對於接收的結點來說,n 是最大的了,那么,它就會通過 request(修改這個值),如果發現自己有一個更大的提案號,那么,結點就會拒絕 request(拒絕修改)。
我們可以看以,這似乎就是一個“兩段提交”的優化。其實,2PC/3PC 都是分布式一致性算法的殘次版本,Google Chubby 的作者 Mike Burrows 說過這個世界上只有一種一致性算法,那就是 Paxos,其它的算法都是殘次品。
我們還可以看到:對於同一個值的在不同結點的修改提案就算是在接收方被亂序收到也是沒有問題的。
四、Raft(解決 Paxos 的實現難度)
Raft 是簡化版的 Paxos。Raft 划分成三個子問題:一是Leader Election;二是 Log Replication;三是 Safety。Raft 定義了三種角色 Leader、Follower、Candidate,最開始大家都是 Follower,當 Follower 監聽不到 Leader,就可以自己成為 Candidate,發起投票。
4.1 Leader 選舉:timeout 限制
每個 Follower 都在 150ms and 300ms 之間隨機超時,之后看誰先 timeout,誰就先成為 Candidate,然后它會先投自己一票,再向其他節點發起投票邀請。如果其他節點在這輪選舉還沒有投過票,那么就給 Candidate 投票,然后重置自己的選舉 timeout。如果得到大多數的投票就成為 Leader,之后定期開始向 Follower 發送心跳。
如果兩個 Follower 同時成為 Candidate 的話,如果最后得到的票數相同,則等待其他 Follower 的選擇 timeout 之后成為 Candidate,繼續開始新一輪的選舉。
4.2 log復制
Leader 把變動的 log 借助心跳同步給 Follower,過半回復之后才成功提交,之后再下一次心跳之后,Follower 也 commit 變動,在自己的 node 上生效。
分裂之后,另一個分區的 Follower 接受不到 Leader 的 timeout,然后會有一個先 timeout,成為 Candidate,最后成為 Leader。於是兩個分區就有了兩個 Leader。
當客戶端有變動時,其中的 Leader 由於無法收到過半的提交,則保持未提交狀態。有的 Leader 的修改,可以得到過半的提交,則可以修改生效。
當分裂恢復之后,Leader 開始對比選舉的 term,發現有更高的 term 存在時,他們會撤銷未提交的修改,然后以最新的為准。
五、ZAB
基本與 raft 相同。在一些名詞的叫法上有些區別,如 ZAB 將某一個 Leader 的周期稱為 epoch,而 raft 則稱之為 term。實現上也有些許不同,如 raft 保證日志連續性,心跳方向為 Leader 至 Follower,ZAB 則相反。
參考:
- 《分布式理論系列》:https://segmentfault.com/a/1190000004474543
- 從 Paxos 到 Zookeeper : 分布式一致性原理與實踐
- 《一致性算法(Paxos、Raft、ZAB)視頻》:https://www.bilibili.com/video/av21667358/
- 《分布式事務中2PC與3PC的區別》:https://blog.csdn.net/yyd19921214/article/details/68953629
每天用心記錄一點點。內容也許不重要,但習慣很重要!