文章很長,建議收藏起來,慢慢讀! 瘋狂創客圈爲小夥伴奉上如下珍貴的學習資源:html
高併發 必讀 的精彩博文 | |
---|---|
nacos 實戰(史上最全) | sentinel (史上最全+入門教程) |
Zookeeper 分佈式鎖 (圖解+秒懂+史上最全) | Webflux(史上最全) |
SpringCloud gateway (史上最全) | TCP/IP(圖解+秒懂+史上最全) |
10分鐘看懂, Java NIO 底層原理 | Feign原理 (圖解) |
更多精彩博文 ..... | 請參見【 瘋狂創客圈 高併發 總目錄 】 |
常見的一致性協議 有二階段提交(2PC)、三階段提交(3PC)、Paxos、Raft等算法,在本文將介紹他們中的一部分。java
2PC即Two-Phase Commit,二階段提交。普遍應用在數據庫領域,爲了使得基於分佈式架構的全部節點能夠在進行事務處理時可以保持原子性和一致性。絕大部分關係型數據庫,都是基於2PC完成分佈式的事務處理。
顧名思義,2PC分爲兩個階段處理,程序員
協調者在階段二決定是否最終執行事務提交操做。這一階段包含兩種情形:面試
執行事務提交
全部參與者reply Yes,那麼執行事務提交。算法
事情總會出現意外,當存在某一參與者向協調者發送No響應,或者等待超時。協調者只要沒法收到全部參與者的Yes響應,就會中斷事務。sql
優勢主要體如今實現原理簡單;
缺點比較多:數據庫
針對2PC的缺點,研究者提出了3PC,即Three-Phase Commit。做爲2PC的改進版,3PC將原有的兩階段過程,從新劃分爲CanCommit、PreCommit和do Commit三個階段。
編程
在本階段,協調者會根據上一階段的反饋狀況來決定是否能夠執行事務的PreCommit操做。有如下兩種可能:設計模式
執行事務預提交緩存
中斷事務
加入任意一個參與者向協調者發送No響應,或者等待超時,協調者在沒有獲得全部參與者響應時,便可以中斷事務:
在這個階段,會真正的進行事務提交,一樣存在兩種可能。
執行提交
中斷事務
在該階段,假設正常狀態的協調者接收到任一個參與者發送的No響應,或在超時時間內,仍舊沒收到反饋消息,就會中斷事務:
3PC有效下降了2PC帶來的參與者阻塞範圍,而且可以在出現單點故障後繼續達成一致;
但3PC帶來了新的問題,在參與者收到preCommit消息後,若是網絡出現分區,協調者和參與者沒法進行後續的通訊,這種狀況下,參與者在等待超時後,依舊會執行事務提交,這樣會致使數據的不一致。
像 2PC 和 3PC 都須要引入一個協調者的角色,當協調者 down 掉以後,整個事務都沒法提交,參與者的資源都出於鎖定的狀態,對於系統的影響是災難性的,並且出現網絡分區的狀況,頗有可能會出現數據不一致的狀況。有沒有不須要協調者角色,每一個參與者來協調事務呢,在網絡分區的狀況下,又能最大程度保證一致性的解決方案呢。此時 Paxos 出現了。
Paxos 算法是 Lamport 於 1990 年提出的一種基於消息傳遞的一致性算法。因爲算法難以理解起初並無引發人們的重視,Lamport在八年後從新發表,即使如此Paxos算法仍是沒有獲得重視。2006 年 Google 的三篇論文石破天驚,其中的 chubby 鎖服務使用Paxos 做爲 chubbycell 中的一致性,後來才獲得關注。
Paxos 協議是一個解決分佈式系統中,多個節點之間就某個值(提案)達成一致(決議)的通訊協議。它可以處理在少數節點離線的狀況下,剩餘的多數節點仍然可以達成一致。即每一個節點,既是參與者,也是決策者
因爲 Paxos 和下文提到的 zookeeper 使用的 ZAB 協議過於類似,詳細講解參照下文, ZAB 協議部分
分佈式系統中的節點通訊存在兩種模型:共享內存(Shared memory)和消息傳遞(Messages passing)。
基於消息傳遞通訊模型的分佈式系統,不可避免的會發生如下錯誤:進程可能會慢、被殺死或者重啓,消息可能會延遲、丟失、重複,在基礎Paxos場景中,先不考慮可能出現消息篡改,即拜占庭錯誤的狀況。(網絡環境通常爲自建內網,消息安全相對高)
Paxos算法解決的問題是在一個可能發生上述異常的分佈式系統中如何就某個值達成一致,保證不論發生以上任何異常,都不會破壞決議的一致性。
Paxos 協議的角色 主要有三類節點:
過程:
規定一個提議包含兩個字段:[n, v],其中 n 爲序號(具備惟一性),v 爲提議值。
下圖演示了兩個 Proposer 和三個 Acceptor 的系統中運行該算法的初始過程,每一個 Proposer 都會向全部 Acceptor 發送提議請求。
當 Acceptor 接收到一個提議請求,包含的提議爲 [n1, v1],而且以前還未接收過提議請求,那麼發送一個提議響應,設置當前接收到的提議爲 [n1, v1],而且保證之後不會再接受序號小於 n1 的提議。
以下圖,Acceptor X 在收到 [n=2, v=8] 的提議請求時,因爲以前沒有接收過提議,所以就發送一個 [no previous] 的提議響應,而且設置當前接收到的提議爲 [n=2, v=8],而且保證之後不會再接受序號小於 2 的提議。其它的 Acceptor 相似。
若是 Acceptor 接受到一個提議請求,包含的提議爲 [n2, v2],而且以前已經接收過提議 [n1, v1]。若是 n1 > n2,那麼就丟棄該提議請求;不然,發送提議響應,該提議響應包含以前已經接收過的提議 [n1, v1],設置當前接收到的提議爲 [n2, v2],而且保證之後不會再接受序號小於 n2 的提議。
以下圖,Acceptor Z 收到 Proposer A 發來的 [n=2, v=8] 的提議請求,因爲以前已經接收過 [n=4, v=5] 的提議,而且 n > 2,所以就拋棄該提議請求;Acceptor X 收到 Proposer B 發來的 [n=4, v=5] 的提議請求,由於以前接收到的提議爲 [n=2, v=8],而且 2 <= 4,所以就發送 [n=2, v=8] 的提議響應,設置當前接收到的提議爲 [n=4, v=5],而且保證之後不會再接受序號小於 4 的提議。Acceptor Y 相似。
當一個 Proposer 接收到超過一半 Acceptor 的提議響應時,就能夠發送接受請求。
Proposer A 接受到兩個提議響應以後,就發送 [n=2, v=8] 接受請求。該接受請求會被全部 Acceptor 丟棄,由於此時全部 Acceptor 都保證不接受序號小於 4 的提議。
Proposer B 事後也收到了兩個提議響應,所以也開始發送接受請求。須要注意的是,接受請求的 v 須要取它收到的最大 v 值,也就是 8。所以它發送 [n=4, v=8] 的接受請求。
Acceptor 接收到接受請求時,若是序號大於等於該 Acceptor 承諾的最小序號,那麼就發送通知給全部的 Learner。當 Learner 發現有大多數的 Acceptor 接收了某個提議,那麼該提議的提議值就被 Paxos 選擇出來。
Paxos 是論證了一致性協議的可行性,可是論證的過程聽說晦澀難懂,缺乏必要的實現細節,並且工程實現難度比較高廣爲人知實現只有 zk 的實現 zab 協議。
Paxos協議的出現爲分佈式強一致性提供了很好的理論基礎,可是Paxos協議理解起來較爲困難,實現比較複雜。
而後斯坦福大學RamCloud項目中提出了易實現,易理解的分佈式一致性複製協議 Raft。Java,C++,Go 等都有其對應的實現
以後出現的Raft相對要簡潔不少。
引入主節點,經過競選。
節點類型:Follower、Candidate 和 Leader
Leader 會週期性的發送心跳包給 Follower。每一個 Follower 都設置了一個隨機的競選超時時間,通常爲 150ms~300ms,若是在這個時間內沒有收到 Leader 的心跳包,就會變成 Candidate,進入競選階段。
① 下圖表示一個分佈式系統的最初階段,此時只有 Follower,沒有 Leader。Follower A 等待一個隨機的競選超時時間以後,沒收到 Leader 發來的心跳包,所以進入競選階段。
② 此時 A 發送投票請求給其它全部節點。
③ 其它節點會對請求進行回覆,若是超過一半的節點回復了,那麼該 Candidate 就會變成 Leader。
④ 以後 Leader 會週期性地發送心跳包給 Follower,Follower 接收到心跳包,會從新開始計時。
① 若是有多個 Follower 成爲 Candidate,而且所得到票數相同,那麼就須要從新開始投票,例以下圖中 Candidate B 和 Candidate D 都得到兩票,所以須要從新開始投票。
② 當從新開始投票時,因爲每一個節點設置的隨機競選超時時間不一樣,所以能下一次再次出現多個 Candidate 並得到一樣票數的機率很低。
① 來自客戶端的修改都會被傳入 Leader。注意該修改還未被提交,只是寫入日誌中。
② Leader 會把修改複製到全部 Follower。
③ Leader 會等待大多數的 Follower 也進行了修改,而後纔將修改提交。
④ 此時 Leader 會通知的全部 Follower 讓它們也提交修改,此時全部節點的值達成一致。
Google 的粗粒度鎖服務 Chubby 的設計開發者 Burrows 曾經說過:「全部一致性協議本質上要麼是 Paxos 要麼是其變體」。Paxos 雖然解決了分佈式系統中,多個節點就某個值達成一致性的通訊協議。可是仍是引入了其餘的問題。因爲其每一個節點,均可以提議提案,也能夠批准提案。當有三個及以上的 proposer 在發送 prepare 請求後,很難有一個 proposer 收到半數以上的回覆而不斷地執行第一階段的協議,在這種競爭下,會致使選舉速度變慢。
因此 zookeeper 在 paxos 的基礎上,提出了 ZAB 協議,本質上是,只有一臺機器能提議提案(Proposer),而這臺機器的名稱稱之爲 Leader 角色。其餘參與者扮演 Acceptor 角色。爲了保證 Leader 的健壯性,引入了 Leader 選舉機制。
ZAB協議還解決了這些問題
ZAB 協議相似於兩階段提交,客戶端有一個寫請求過來,例如設置 /my/test
值爲 1,Leader 會生成對應的事務提議(proposal)(當前 zxid爲 0x5000010 提議的 zxid 爲Ox5000011),現將set /my/test 1
(此處爲僞代碼)寫入本地事務日誌,而後set /my/test 1
日誌同步到全部的follower。follower收到事務 proposal ,將 proposal 寫入到事務日誌。若是收到半數以上 follower 的迴應,那麼廣播發起 commit 請求。follower 收到 commit 請求後。會將文件中的 zxid ox5000011 應用到內存中。
上面說的是正常的狀況。有兩種狀況。第一種 Leader 寫入本地事務日誌後,沒有發送同步請求,就 down 了。即便選主以後又做爲 follower 啓動。此時這種仍是會日誌會丟掉(緣由是選出的 leader 無此日誌,沒法進行同步)。第二種 Leader 發出同步請求,可是尚未 commit 就 down 了。此時這個日誌不會丟掉,會同步提交到其餘節點中。
如今 5 臺 zk 機器依次編號 1~5
1.節點 1 發起投票,第一輪投票先投本身,而後進入 Looking 等待的狀態 2.其餘的節點(如節點 2 )收到對方的投票信息。節點 2 在 Looking 狀態,則將本身的投票結果廣播出去(此時走的是上圖中左側的 Looking 分支);若是不在 Looking 狀態,則直接告訴節點 1 當前的 Leader 是誰,就不要瞎折騰選舉了(此時走的是上圖右側的 Leading/following 分支) 3.此時節點 1,收到了節點 2 的選舉結果。若是節點 2 的 zxid 更大,那麼清空投票箱,創建新的投票箱,廣播本身最新的投票結果。在同一次選舉中,若是在收到全部節點的投票結果後,若是投票箱中有一半以上的節點選出了某個節點,那麼證實 leader 已經選出來了,投票也就終止了。不然一直循環
zookeeper 的選舉,優先比較大 zxid,zxid 最大的節點表明擁有最新的數據。若是沒有 zxid,如系統剛剛啓動的時候,則比較機器的編號,優先選擇編號大的
在選出 Leader 以後,zk 就進入狀態同步的過程。其實就是把最新的 zxid 對應的日誌數據,應用到其餘的節點中。此 zxid 包含 follower 中寫入日誌可是未提交的 zxid 。稱之爲服務器提議緩存隊列 committedLog 中的 zxid。
同步會完成三個 zxid 值的初始化。
peerLastZxid
:該 learner 服務器最後處理的 zxid。 minCommittedLog
:leader服務器提議緩存隊列 committedLog 中的最小 zxid。 maxCommittedLog
:leader服務器提議緩存隊列 committedLog 中的最大 zxid。 系統會根據 learner 的peerLastZxid
和 leader 的minCommittedLog
,maxCommittedLog
作出比較後作出不一樣的同步策略
場景:peerLastZxid
介於minCommittedLogZxid
和maxCommittedLogZxid
間
此種場景出如今,上文提到過的,Leader 發出了同步請求,可是尚未 commit 就 down 了。 leader 會發送 Proposal 數據包,以及 commit 指令數據包。新選出的 leader 繼續完成上一任 leader 未完成的工做。
例如此刻Leader提議的緩存隊列爲 0x20001,0x20002,0x20003,0x20004,此處learn的peerLastZxid爲0x20002,Leader會將0x20003和0x20004兩個提議同步給learner
此種場景出如今,上文提到過的,Leader寫入本地事務日誌後,還沒發出同步請求,就down了,而後在同步日誌的時候做爲learner出現。
例如即將要 down 掉的 leader 節點 1,已經處理了 0x20001,0x20002,在處理 0x20003 時還沒發出提議就 down 了。後來節點 2 當選爲新 leader,同步數據的時候,節點 1 又神奇復活。若是新 leader 尚未處理新事務,新 leader 的隊列爲,0x20001, 0x20002,那麼僅讓節點 1 回滾到 0x20002 節點處,0x20003 日誌廢棄,稱之爲僅回滾同步。若是新 leader 已經處理 0x30001 , 0x30002 事務,那麼新 leader 此處隊列爲0x20001,0x20002,0x30001,0x30002,那麼讓節點 1 先回滾,到 0x20002 處,再差別化同步0x30001,0x30002。
peerLastZxid
小於minCommittedLogZxid
或者leader上面沒有緩存隊列。leader直接使用SNAP命令進行全量同步
http://www.javashuo.com/article/p-vjywczat-vh.html
https://blog.csdn.net/weixin_33725272/article/details/87947998