文章很長,建議收藏起來,慢慢讀! 瘋狂創客圈爲小夥伴奉上如下珍貴的學習資源:html
高併發 必讀 的精彩博文 | |
---|---|
nacos 實戰(史上最全) | sentinel (史上最全+入門教程) |
Zookeeper 分佈式鎖 (圖解+秒懂+史上最全) | Webflux(史上最全) |
SpringCloud gateway (史上最全) | TCP/IP(圖解+秒懂+史上最全) |
10分鐘看懂, Java NIO 底層原理 | Feign原理 (圖解) |
更多精彩博文 ..... | 請參見【 瘋狂創客圈 高併發 總目錄 】 |
zookeeper是Apache開源的頂級項目,用於提供一個高性能、高可用,且具備嚴格的順序訪問控制能力(主要是寫操做的嚴格順序性)的分佈式協調服務。可用於在分佈式環境中保證數據的一致性,擁有很是普遍地使用場景。java
主要的使用場景以下:程序員
» 領導者(leader),負責進行投票的發起和決議,更新系統狀態
» 學習者(learner),包括跟隨者(follower)和觀察者(observer),follower用於接受客戶端請求並想客戶端返回結果,在選主過程當中參與投票
» Observer能夠接受客戶端鏈接,將寫請求轉發給leader,但observer不參加投票過程,只同步leader的狀態,observer的目的是爲了擴展系統,提升讀取速度
» 客戶端(client),請求發起方面試
LOOKING:當前Server不知道leader是誰,正在搜尋
LEADING:當前Server即爲選舉出來的leader
FOLLOWING:leader已經選舉出來,當前Server與之同步算法
zk3.3開始引入的角色,觀察最新狀態,並變動。與Follower不一樣只是不參與投票、選舉,只提供非事務服務。sql
• Zookeeper需保證高可用和強一致性;
• 爲了支持更多的客戶端,須要增長更多Server;
• Server增多,投票階段延遲增大,影響性能;
• 權衡伸縮性和高吞吐率,引入Observer
• Observer不參與投票;
• Observers接受客戶端的鏈接,並將寫請求轉發給leader節點;
• 加入更多Observer節點,提升伸縮性,同時不影響吞吐率數據庫
Zookeeper 客戶端會隨機的鏈接到 zookeeper 集羣中的一個節點。編程
注意,ZK集羣會有不少節點, 客戶端在創建鏈接時,會隨機挑選一個節點。設計模式
ZK集羣對客戶端的請求,按照類型(讀、寫兩類)分開處理:服務器
讀請求
客戶端直接從當前節點(其創建鏈接的節點)中讀取數據;
寫請求
這裏涉及到了分佈式事務。 客戶端就會向 Leader 提交事務,Leader 接收到事務提交,會廣播該事務,只要超過半數節點寫入成功,該事務就會被提交。
Zookeeper的核心是原子廣播,這個機制保證了各個Server之間的同步。實現這個機制的協議叫作Zab協議。Zab協議 的全稱是 Zookeeper Atomic Broadcast (Zookeeper原子廣播)。Zookeeper 是經過 Zab 協議來保證分佈式事務的最終一致性。
ZAB與Paxos聯繫&區別
二者設計目標不同,ZAB主要用於構建高可用分佈式系統,Paxos 算法用於構建一致性狀態機器。全部會有細微差異。可是ZAB就是在Paxos保證一致性基礎上設計出高可用的協議。
epoch週期值(比喻:年號)
acceptedEpoch(比喻:接受的年號):follower已經接受leader更改年號的(newepoch)提議。
currentEpoch(比喻:當前的年號):當前的年號
history:當前節點接受到事務提議的log
lastZxid:history中最近接收到的提議zxid(最大的值)
年號如何遞增:
當產生新Leader的時候,就從這個Leader服務器上取出本地log中最大事務Zxid,從裏面讀出epoch+1,做爲一個新epoch,並將低32位置0(保證id絕對自增)
ZXID : 事務的Proposal(提議) 的id,能夠簡單理解爲事務id
ZXID 是一個 64 位的數字,其中低 32 位可看做是計數器,Leader 服務器每產生一個新的事務 Proposal 的時候,都會該計數器進行加 1 操做。
ZXID 的高 32 位表示 Leader 週期 epoch 的編號,每當選舉一個新的 Leader 服務器,就會從該服務器本地的事務日誌中最大 Proposal 的 ZXID 中解析出對應的 epoch 值,而後對其加 1 操做,這個值就做爲新的 epoch 值,並將低 32 位初始化爲 0 來開始生成新的 ZXID。
Zookeeper 集羣的模式包括兩種基本的模式:崩潰恢復 和 消息廣播
當整個集羣啓動過程當中,或者當 Leader 服務器出現網絡中弄斷、崩潰退出或重啓等異常時,Zab協議就會 進入崩潰恢復模式,選舉產生新的Leader。
一但出現崩潰,會致使數據不一致,ZAB的崩潰恢復開始起做用。有以下兩個確保:
針對上兩個要求,若是Leader選舉算法保證新選舉出來的Leader服務器擁有集羣中全部機器最高編號(ZXID最大)的事務Proposal,那麼就能保證新的Leader 必定具備已提交的全部提案,更重要是,若是這麼作,能夠省去Leader服務器檢查Proposal的提交和丟棄工做的這一步。
一旦Leader服務器出現崩潰,或者說網絡緣由致使Leader服務器失去了與過半的Follower的聯繫,那麼就會進入崩潰恢復模式。爲了保證程序的正常運行,整個恢復過程後須要選舉一個新的Leader服務器。所以,ZAB協議須要一個高效可靠的Leader選舉算法,從而確保可以快速的選舉出新的Leader。同時,新的Leader選舉算法不只僅須要讓Leader本身知道其自身已經被選舉爲Leader,同時還須要讓集羣中全部的其餘機器也可以快速的感知選舉產生的新的Leader服務器。
ZAB協議規定了若是一個事務Proposal在一臺機器上被處理成功,那麼應該在全部的機器上都被處理成功,哪怕機器出現崩潰。
當新的Leader出來了,同時,已有過半機器完成同步以後,ZAB協議將退出恢復模式。進入消息廣播模式。這時,若是有一臺遵照Zab協議的服務器加入集羣,由於此時集羣中已經存在一個Leader服務器在廣播消息,那麼該新加入的服務器自動進入恢復模式:找到Leader服務器,而且完成數據同步。同步完成後,做爲新的Follower一塊兒參與到消息廣播流程中。
若是集羣中其餘機器收到客戶端事務請求後,那麼會先轉發Leader服務器,由Leader統一處理。
ZAB協議中涉及的二階段提交和2pc有所不一樣。在ZAB協議的二階段提交過程當中,移除了中斷邏輯,全部Follower服務器要麼正常反饋Leader提出的事務Proposal,要麼就拋棄Leader服務器。ZAB協議中,只要集羣中過半的服務器已經反饋ACK,就開始提交事務了,不須要等待集羣中全部的服務器都反饋響應。這種模型是沒法處理Leader服務器崩潰退出而帶來的數據不一致問題的,所以在ZAB協議中添加了另外一個模式,即採用崩潰恢復模式來解決這個問題。此外,整個消息廣播協議是基於具備FIFO特性的TCP協議來進行網絡通訊的,所以可以很容易保證消息廣播過程當中消息接受與發送的順序性。
在整個消息廣播過程當中,Leader服務器會爲每一個事務請求生成對應的Proposal來進行廣播,而且在廣播事務Proposal以前,Leader服務器會首先爲這個事務分配一個全局單調遞增的惟一ID,咱們稱之爲事務ID(即ZXID)。因爲ZAB協議須要保證每個消息嚴格的因果關係,所以必須將每個事務Proposal按照其ZXID的前後順序來進行排序與處理。
在消息廣播過程當中,Leader服務器會爲每個Follower服務器各自分配一個單獨的隊列,而後將須要廣播的事務Proposal依次放入這些隊列中,而且根據FIFO策略進行消息發送。每個Follower服務器在接受到這個事務Proposal以後,都會首先將其以事務日誌的形式寫入到本地磁盤中去,而且在成功寫入後反饋給Leader服務器一個ACK響應。當Leader服務器接收到超過半數Follower的ACK響應後,就會廣播一個Commit消息給全部Follower服務器以通知其將事務進行提交,同時Leader自身也會完成事務的提交,而每個Follower服務器收到Commit消息以後,也會完成對事務的提交。
崩潰恢復:在正常狀況下運行很是良好,一旦Leader出現崩潰或者因爲網絡緣由致使Leader服務器失去了與過半Follower的聯繫,那麼就會進入崩潰恢復模式。爲了程序的正確運行,整個恢復過程後須要選舉出一個新的Leader,所以須要一個高效可靠的選舉方法快速選舉出一個Leader。
消息廣播:相似一個兩階段提交過程,針對客戶端的事務請求, Leader服務器會爲其生成對應的事務Proposal,並將其發送給集羣中的其他全部機器,再分別收集各自的選票,最後進行事務提交。
啓動過程或Leader出現網絡中斷、崩潰退出與重啓等異常狀況時。
當選舉出新的Leader後,同時集羣中已有過半的機器與該Leader服務器完成了狀態同步以後,ZAB就會退出恢復模式。
ZAB協議中存在着三種狀態,每一個節點都屬於如下三種中的一種:
Looking /election:系統剛啓動時或者Leader崩潰後正處於選舉狀態
Following :Follower節點所處的狀態,Follower與Leader處於數據同步階段;
Leading :Leader所處狀態,當前集羣中有一個Leader爲主進程;
在ZooKeeper的整個生命週期中,每一個節點都會在Looking、Following、Leading狀態間不斷轉換.
啓動之初,ZooKeeper全部節點初始狀態爲Looking,這時集羣會嘗試選舉出一個Leader節點,選舉出的Leader節點切換爲Leading狀態;當節點發現集羣中已經選舉出Leader則該節點會切換到Following狀態,而後和Leader節點保持同步;當Follower節點與Leader失去聯繫時Follower節點則會切換到Looking狀態,開始新一輪選舉;
• 半數經過
– 3臺機器 掛一臺 2>3/2
– 4臺機器 掛2臺 2!>4/2
• A提案說,我要選本身,B你贊成嗎?C你贊成嗎?B說,我贊成選A;C說,我贊成選A。(注意,這裏超過半數了,其實在現實世界選舉已經成功了。
可是計算機世界是很嚴格,另外要理解算法,要繼續模擬下去。)
• 接着B提案說,我要選本身,A你贊成嗎;A說,我已經超半數贊成當選,你的提案無效;C說,A已經超半數贊成當選,B提案無效。
• 接着C提案說,我要選本身,A你贊成嗎;A說,我已經超半數贊成當選,你的提案無效;B說,A已經超半數贊成當選,C的提案無效。
• 選舉已經產生了Leader,後面的都是follower,只能服從Leader的命令。並且這裏還有個小細節,就是其實誰先啓動誰當頭。
分析以上兩種細分場景:
•Leader選舉算法採用了Paxos協議;
•Paxos核心思想:當多數Server寫成功,則任務數據寫成功若是有3個Server,則兩個寫成功便可;若是有4或5個Server,則三個寫成功便可。
•Server數目通常爲奇數(三、五、7)若是有3個Server,則最多容許1個Server掛掉;若是有4個Server,則一樣最多容許1個Server掛掉由此,
咱們看出3臺服務器和4臺服務器的的容災能力是同樣的,因此爲了節省服務器資源,通常咱們採用奇數個數,做爲服務器部署個數。
ZAB 默認採用 TCP 版本的 FastLeaderElection 選舉算法。在選舉投票消息中包含了兩個最基本的信息:所推舉的服務器 SID 和 ZXID,分別表示被推舉服務器的惟一標識(每臺機器不同)和事務 ID。假如投票信息爲 (SID, ZXID)的形式。在第一次投票的時候,因爲還沒法檢測集羣其餘機器的狀態信息,所以每臺機器都將本身做爲被推舉的對象來進行投票。每次對收到的投票,都是一個對投票信息(SID, ZXID)對比的過程,規則以下:
1 邏輯時鐘clock表示第幾輪選舉,重啓時爲0,選舉時加1
2 啓動時處於looking狀態
3 選舉細節
4 從新發出選票
通過第二次投票後,集羣中每臺機器都會再次收到其餘機器的投票,而後開始統計,若是一臺機器收到超過了半數的相同投票,那麼這個投票對應的 myid機器即爲 Leader。
簡單來講,一般哪臺服務器上的數據越新,那麼越有可能成爲 Leader。緣由很簡答,數據越新,也就越可以保證數據的恢復。固然,若是集羣中有幾個服務器具備相同的 ZXID,那麼 myid較大的那臺服務器成爲 Leader。
選舉出Leader節點後ZAB進入原子廣播階段,Leader與Follower使用心跳檢測來感知對方的存在:
1 當Leader節點在超時時間內能正常收到來自Follower的心跳, 那Follower節點會一直與該節點保持鏈接;
2 若超時時間內Leader沒有接收到來自過半Follower節點的心跳檢測或TCP鏈接斷開,那Leader會結束當前週期的領導,切換到Looking狀態,全部Follower節點也會放棄該Leader節點切換到Looking狀態,而後開始新一輪選舉;
節點在一開始都處於選舉階段,只要有一個節點獲得超半數節點的票數,它就能夠當選準 leader。只有到達 Phase 3 準 leader 纔會成爲真正的 leader。這一階段的目的是就是爲了選出一個準 leader,而後進入下一個階段。
協議並無規定詳細的選舉算法,後面咱們會提到實現中使用的 Fast Leader Election。
在這個階段,followers 跟準 leader 進行通訊,同步 followers 最近接收的事務提議。這個一階段的主要目的是發現當前大多數節點接收的最新提議,而且準 leader 生成新的 epoch,讓 followers 接受,更新它們的 accepted Epoch
一個 follower 只會鏈接一個 leader,若是有一個節點 f 認爲另外一個 follower p 是 leader,f 在嘗試鏈接 p 時會被拒絕,f 被拒絕以後,就會進入 Phase 0。
同步階段主要是利用 leader 前一階段得到的最新提議歷史,同步集羣中全部的副本。只有當 quorum 都同步完成,準 leader 纔會成爲真正的 leader。follower 只會接收 zxid 比本身的 lastZxid 大的提議。
到了這個階段,Zookeeper 集羣才能正式對外提供事務服務,而且 leader 能夠進行消息廣播。同時若是有新的節點加入,還須要對新節點進行同步。
值得注意的是,ZAB 提交事務並不像 2PC 同樣須要所有 follower 都 ACK,只須要獲得 quorum (超過半數的節點)的 ACK 就能夠了。
完成Leader選舉以後,在正式開始工做(即接收客戶端的事務請求,而後提出新的提案)以前,Leader服務器會首先確認事務日誌中的全部Proposal都已經被集羣中過半的機器提交了,便是否完成數據同步。
集羣中全部的正常運行的服務器,要麼成爲Leader,要麼成爲Follower並和Leader保持同步。Leader服務器須要確保全部的Follwer服務器可以接收到每一條事務Proposal,而且可以正確地將全部已經提交了的事務Proposal應用到內存數據庫中。Leader服務器會爲每個Followe服務器都準備一個隊列,並將沒有被各Follower服務器同步的事務以Proposal消息形式逐個發送到Follower服務器,並在每個Proposal消息後緊跟着再發送一個Commit消息,以表示這個事務已經被提交。等到Follower服務器將全部還沒有同步的事務Proposal都從Leader服務器上同步過來併成功應用到本地數據庫中後,Leader服務器就會將改Follower服務器加入到真正可用的Follower列表中,並開始以後的其餘流程。
ZAB協議如何處理那些須要被丟棄的事務Proposal的?
在ZAB協議的事務編號ZXID設計中,ZXID是一個64位數字,其中低32位能夠看作一個簡單的單調遞增的計數器,針對客戶端的每個事務請求,Leader服務器在產生一個新的事務Proposal的時候,都會對改計數器進行加一操做;而高32位則表明了leader週期epoch的編號,每當選舉產生一個新的Leader服務器,就會從這個Leader服務器取出本地日誌中最大事務Proposal的ZXID,並從ZXID中解析出對應的epoch值,而後再對其進行加1操做,以後的編號就會做爲新的epoch,並將低32位 置0來開始生成新的ZXID。ZAB協議中的這一經過epoch編號來區分Leader週期變化的策略,可以有效避免不一樣的Leader服務器錯誤的使用相同的ZXID編號提出不同的事務的狀況。
基於這樣的策略,當一個包含了上一個Leader週期中還沒有提交過的事務服務器啓動時,確定沒法成爲leader。由於當前集羣中確定包含一個Quorum集合,該集合中機器必定包含了更高的epoch的事務Proposal。
分佈式事務就是指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位於不一樣的分佈式系統的不一樣節點之上。簡單的說,就是一次大的操做由不一樣的小操做組成,這些小的操做分佈在不一樣的服務器上,且屬於不一樣的應用,分佈式事務須要保證這些小操做要麼所有成功,要麼所有失敗。本質上來講,分佈式事務就是爲了保證不一樣數據庫的數據一致性。
爲了保證事務的順序一致性,zookeeper採用了遞增的事務id號(zxid)來標識事務。全部的提議(proposal)都在被提出的時候加上了zxid。實現中zxid是一個64位的數字,它高32位是epoch(有點相似年代、年號)用來標識 leader關係是否改變,每次一個leader被選出來,它都會有一個新的epoch,標識當前屬於那個leader的統治時期。低32位用於遞增計數。
ZooKeeper狀態的每一次改變, 都對應着一個遞增的Transaction id, 該id稱爲zxid. 因爲zxid的遞增性質, 若是zxid1小於zxid2, 那麼zxid1確定先於zxid2發生.
建立任意節點, 或者更新任意節點的數據, 或者刪除任意節點, 都會致使Zookeeper狀態發生改變, 從而致使zxid的值增長.
Zab協議的核心:定義了事務請求的處理方式
1)全部的事務請求必須由一個全局惟一的服務器來協調處理,這樣的服務器被叫作 Leader服務器。其餘剩餘的服務器則是 Follower服務器。
2)Leader服務器 負責將一個客戶端事務請求,轉換成一個 事務Proposal,並將該 Proposal 分發給集羣中全部的 Follower 服務器,也就是向全部 Follower 節點發送數據廣播請求(或數據複製)
3)分發以後, Leader服務器須要等待全部Follower服務器的反饋(Ack請求),在Zab協議中,只要超過半數的Follower服務器進行了正確的反饋後, 那麼 Leader 就會再次向全部的 Follower服務器發送 Commit 消息,要求其將上一個 事務proposal 進行提交。 (注意:有點像2PC)
廣播模式下只有主節點能夠發送廣播消息,若是某個從節點須要發送廣播信息,也須要經過主節點進行。
消息廣播階段的數據寫入策略,經過事務完成,有如下特色:
1)在zookeeper集羣中,數據副本的傳遞策略就是採用消息廣播模式。zookeeper中農數據副本的同步方式與二段提交類似,可是卻又不一樣。二段提交要求協調者必須等到全部的參與者所有反饋ACK確認消息後,再發送commit消息。要求全部的參與者要麼所有成功,要麼所有失敗。二段提交會產生嚴重的阻塞問題。
2)Zab協議中 Leader 等待 Follower 的ACK反饋消息是指「只要半數以上的Follower成功反饋便可,不須要收到所有Follower反饋」
消息廣播具體步驟以下:
1)客戶端發起一個寫操做請求。
2)Leader 服務器將客戶端的請求轉化爲事務 Proposal 提案,同時爲每一個 Proposal 分配一個全局的ID,即zxid。
3)Leader 服務器爲每一個 Follower 服務器分配一個單獨的隊列,而後將須要廣播的 Proposal 依次放到隊列中取,而且根據 FIFO 策略進行消息發送。
4)Follower 接收到 Proposal 後,會首先將其以事務日誌的方式寫入本地磁盤中,寫入成功後向 Leader 反饋一個 Ack 響應消息。
5)Leader 接收到超過半數以上 Follower 的 Ack 響應消息後,即認爲消息發送成功,能夠發送 commit 消息。
6)Leader 向全部 Follower 廣播 commit 消息,同時自身也會完成事務提交。Follower 接收到 commit 消息後,會將上一條事務提交。
zookeeper 採用 Zab 協議的核心,就是隻要有一臺服務器提交了 Proposal,就要確保全部的服務器最終都能正確提交 Proposal。這也是 CAP/BASE 實現最終一致性的一個體現。
Leader 服務器與每個 Follower 服務器之間都維護了一個單獨的 FIFO 消息隊列進行收發消息,使用隊列消息能夠作到異步解耦。 Leader 和 Follower 之間只須要往隊列中發消息便可。若是使用同步的方式會引發阻塞,性能要降低不少。
一、主節點廣播發送事務提交提議
包括如下步驟:
a.針對客戶端的事務請求,leader服務器會先將該事務寫到本地的log文件中
b.而後,leader服務器會爲此次請求生成對應的事務Proposal而且爲這個事務Proposal分配一個全局遞增的惟一的事務ID,即Zxid
c.leader服務器會爲每個follower服務器都各自分配一個單獨的隊列,將須要廣播的事務Proposal依次放入隊列中,發送給每個follower
二、從節點接收到提議後,回覆確認信息通知主節點
包括如下步驟:
d. 每個follower在收到隊列以後,會從隊列中依次取出事務Proposal,寫入本地的事務日誌中。若是寫成功了,則給leader返回一個ACK消息
三、主節點接收到超過法定數量從節點確認信息後,廣播發送事務提交命令到從節點
e. 當leader服務器接收到半數的follower的ACK相應以後,就會廣播一個Commit消息給全部的follower以通知其進行事務提交,同時leader自身也進行事務提交
固然,在這種簡化了的二階段提交模型下,是沒法處理Leader服務器崩潰退出而帶來的數據不一致問題的,所以在ZAB協議中添加了另外一個模式,即採用崩潰恢復模式來解決這個問題
整個消息廣播協議是基於具備FIFO特性的TCP協議來進行網絡通訊的,所以可以很容易地保證消息廣播過程當中消息接收與發送的順序性
zookeeper 服務實現中很重要的一點:順序性。順序請求,順序響應;主節點事務順序提交,從節點按順序響應事務等等。
客戶端對ZK的更新操做都是永久的,不可回退的,也就是說,一旦客戶端收到一個來自server操做成功的響應,那麼這個變動就永久生效了。爲作到這點,ZK會將每次更新操做以事務日誌的形式寫入磁盤,寫入成功後纔會給予客戶端響應。明白這點以後,你就會明白磁盤的吞吐性能對於ZK的影響了,磁盤寫入速度制約着ZK每一個更新操做的響應。
事務日誌的寫性能確實對ZK性能,尤爲是更新操做的性能影響很大,爲了儘可能減小ZK在讀寫磁盤上的性能損失,能夠考慮使用單獨的磁盤做爲事務日誌的輸出(使用單獨的掛載點用於事務日誌的輸出)
ZK的事務日誌輸出是一個順序寫文件的過程,自己性能是很高的,因此儘可能保證不要和其它隨機寫的應用程序共享一塊磁盤,儘可能避免對磁盤的競爭。
參考文獻: