一. Zookeeper設計思想:
類似于前面我們簡單說了Zookeeper可能解決的問題,例如類似于實現分布式鎖,控制任務執(zhí)行但是這里也會發(fā)現會有很多其他隱形問題.Zookeeper設計時就是為了解決了這些問題.
1. 防止單點故障
要防止 zookeeper 這個中間件的單點故障葡兑,那么肯定要做集群。而且這個集群如果要滿足高性能要求的話猩谊,還得是一個高性能高可用的集群。
- 高性能意味著這個集群能夠分擔客戶端的請求流量
- 高可用意味著集群中的某一個節(jié)點宕機以后祭刚,不影響整個集群的數據和繼續(xù)提供服務的可能性牌捷。
簡單說就是橫向擴容,縱向熱備.
需求1 :.這個中間件需要考慮到集群,而且這個集群還需要分攤客戶端的請求流量
2.如果要滿足高性能
要保證高性能,就需要保證我們任意請求打到任意結點上都會有相同的反饋結果.進一步說明我們需要保證每個節(jié)點都能接收到請求,并且每個節(jié)點的數據都必須要保持一致涡驮。要實現各個節(jié)點的數據一致性暗甥,就需要 一個 leader 節(jié)點負責協調和數據同步操作。這個我想大家都知道捉捅,如果在這樣一個集群中沒有 leader 節(jié)點撤防,每個節(jié)點都可以接收所有請求,那么這個集群的數據同步的復雜度是非常大棒口。
需求2:所以這個集群中涉及到數據同步以及會存在leader 節(jié)點,用來保證數據一致性
3.如果要滿足高可用
既如何在眾多節(jié)點中選舉出 leader 節(jié)點寄月,以及l(fā)eader 宕機以后以后,如何恢復呢,如何正常保證使用呢?
需求3 :結論:所以 zookeeper 用了基于 paxos 理論所衍生出來的 ZAB 協議
4. leader 節(jié)點如何和其他節(jié)點保證數據一致性无牵,并且要求是強一致的漾肮。
在分布式系統中,每一個機器節(jié)點雖然都能夠明確知道自己進行的事務操作過程是成功和失敗合敦,但是卻無法直接獲取其他分布式節(jié)點的操作結果初橘。
所以當一個事務操作涉及到跨節(jié)點的時候,就需要用到分布式事務,分布式事務的數據一致性協議有 2PC 協議和3PC 協議
二 . 保證Zookeeper解決問題的相關功能設計基礎
1.關于 2PC 提交(Two Phase Commitment Protocol)
當一個事務操作需要跨越多個分布式節(jié)點的時候保檐,為了保持事務處理的 ACID特性耕蝉,就需要引入一個“協調者”(TM)來統一調度所有分布式節(jié)點的執(zhí)行邏輯,這些被調度的分布式節(jié)點被稱為 AP夜只。TM 負責調度 AP 的行為垒在,并最終決定這些 AP 是否要把事務真正進行提交;因為整個事務是分為兩個階段提交扔亥,所以叫 2pc
階段一:提交事務請求(投票)
- 事務詢問
協調者向所有的參與者發(fā)送事務內容场躯,詢問是否可以執(zhí)行事
務提交操作,并開始等待各參與者的響應 - 執(zhí)行事務
各個參與者節(jié)點執(zhí)行事務操作旅挤,并將 Undo 和 Redo 信息記
錄到事務日志中踢关,盡量把提交過程中所有消耗時間的操作和
準備都提前完成確保后面 100%成功提交事務 - 各個參與者向協調者反饋事務詢問的響應
如果各個參與者成功執(zhí)行了事務操作,那么就反饋給參與者
yes 的響應粘茄,表示事務可以執(zhí)行签舞;如果參與者沒有成功執(zhí)行
事務,就反饋給協調者 no 的響應柒瓣,表示事務不可以執(zhí)行儒搭,
上面這個階段有點類似協調者組織各個參與者對一次事務
操作的投票表態(tài)過程,因此 2pc 協議的第一個階段稱為“投
票階段”芙贫,即各參與者投票表名是否需要繼續(xù)執(zhí)行接下去的
事務提交操作搂鲫。
階段二:執(zhí)行事務提交
在這個階段,協調者會根據各參與者的反饋情況來決定最終是
否可以進行事務提交操作磺平,正常情況下包含兩種可能:執(zhí)行事務魂仍、
中斷事務
2. zookeeper 的集群
在 zookeeper 中,客戶端會連接到 zookeeper 集群中的一個節(jié)點
- 如果是讀請求褪秀,就直接從當前節(jié)點中讀取數據
- 如果是寫請求蓄诽,那么請求會被轉發(fā)給 leader 提交事務薛训,然后 leader 會廣播事務媒吗,只要有超過半數節(jié)點寫入成功,那么寫請求就會被提交(類 2PC 事務),其實這里也是為什么參與投票的結點數必須是2N+1最佳的原因之一.
所有事務請求必須由一個全局唯一的服務器來協調處理乙埃,這個服務器就是 Leader 服務器闸英,其他的服務器就是follower。
leader 服務器把客戶端的寫請求轉化成一個事務 Proposal(提議)介袜,并把這個 Proposal 分發(fā)給集群中的所有 Follower 服務器甫何。之后 Leader 服務器需要等待所有Follower 服務器的反饋,一旦超過半數的 Follower 服務器進行了正確的反饋ACK遇伞,那么 Leader 就會再次向所有的Follower 服務器發(fā)送 Commit 消息辙喂,要求各個 follower 節(jié)點對前面的一個 Proposal 進行提交;
集群重的角色
-
Leader 角色
Leader 服務器是整個 zookeeper 集群的核心,主要的工作任務有兩項- 事物請求的唯一調度和處理者,保證集群事物處理的順序性**
- 集群內部各服務器的調度者
-
Follower 角色
Follower 角色的主要職責是- 處理客戶端非事物請求巍耗、轉發(fā)事物請求給 leader 服務器
- 參與事物請求 Proposal 的投票(需要半數以上服務器
通過才能通知 leader commit 數據; Leader 發(fā)起的提案秋麸,
要求 Follower 投票) - 參與 Leader 選舉的投票
Observer 角色
Observer 是 zookeeper3.3 開始引入的一個全新的服務器
角色,從字面來理解炬太,該角色充當了觀察者的角色灸蟆。
觀察 zookeeper 集群中的最新狀態(tài)變化并將這些狀態(tài)變化
同步到 observer 服務器上。
Observer 的工作原理與follower 角色基本一致
而它和 follower 角色唯一的不同在于 observer 不參與任何形式的投票
亲族,包括事物請求Proposal的投票和leader選舉的投票炒考。(看源碼就知道了 坐標FastLeaderElection.java)
簡單來說,observer服務器只提供非事物請求服務霎迫,通常在于不影響集群事物
處理能力的前提下提升集群非事物處理的能力
為什么會有observer這種服務器存在?
由于選舉需要進行網絡IO,我們的leader必須經過全部IO結束才能決定是否提交事務或者進行選舉,這將會拉低效率,故出現了Observer這種單處理不選舉的服務器類型.
集群組成
通常 zookeeper 是由 2n+1 臺 server 組成斋枢,每個 server 都知道彼此的存在。
對于 2n+1 臺 server知给,只要有 n+1 臺(大多數)server 可用杏慰,整個系統保持可用。我們已經了解到炼鞠,一個 zookeeper 集群如果要對外提供可用的服務缘滥,那么集群中必須要有過半的機器正常工作并且彼此之間能夠正常通信,基于這個特性谒主,如果向搭建一個能夠允許 F 臺機器down 掉的集群朝扼,那么就要部署 2*F+1 臺服務器構成的zookeeper 集群。因此 3 臺機器構成的 zookeeper 集群霎肯,能夠在掛掉一臺機器后依然正常工作擎颖。一個 5 臺機器集群的服務,能夠對 2 臺機器怪調的情況下進行容災观游。如果一臺由 6 臺服務構成的集群搂捧,同樣只能掛掉 2 臺機器。因此懂缕,5 臺和 6 臺在容災能力上并沒有明顯優(yōu)勢允跑,反而增加了網絡通信負擔。系統啟動時搪柑,集群中的 server 會選舉出一臺server 為 Leader聋丝,其它的就作為 follower(這里先不考慮observer 角色)。
之所以要滿足這樣一個等式工碾,是因為一個節(jié)點要成為集群中的 leader弱睦,需要有超過及群眾過半數的節(jié)點支持,這個涉及到 leader 選舉算法渊额。同時也涉及到事務請求的提交投票.
3.ZAB 協議
ZAB(Zookeeper Atomic Broadcast)原子廣播 協議是為分布式協調服務 ZooKeeper 專門設計的一種况木。
在 ZooKeeper 中垒拢,主要依賴 ZAB 協議來實現分布式數據一致性,基于該協議火惊,ZooKeeper 實現了一種的系統架構來保持集群中各個副本之間的數據一致性.
zab 協議介紹 ZAB 協議包含兩種基本模式子库,分別是
- 崩潰恢復
- 原子廣播
當整個集群在啟動時,或者當 leader 節(jié)點出現網絡中斷矗晃、崩潰等情況時仑嗅,ZAB 協議就會進入恢復模式并選舉產生新的 Leader,當 leader 服務器選舉出來后张症,并且集群中有過半的機器和該 leader 節(jié)點完成數據同步后(同步指的是數據同步仓技,用來保證集群中過半的機器能夠和 leader 服務器的數據狀態(tài)保持一致),ZAB 協議就會退出恢復模式俗他。當集群中已經有過半的 Follower 節(jié)點完成了和 Leader 狀態(tài)同步以后脖捻,那么整個集群就進入了消息廣播模式。這個時候兆衅,在 Leader 節(jié)點正常工作時地沮,啟動一臺新的服務器加入到集群,那這個服務器會直接進入數據恢復模式羡亩,和leader 節(jié)點進行數據同步摩疑。同步完成后即可正常對外提供非事務請求的處理。
崩潰恢復(數據恢復)
ZAB 協議的這個基于原子廣播協議的消息廣播過程畏铆,在正常情況下是沒有任何問題的雷袋,但是一旦 Leader 節(jié)點崩潰,或者由于網絡問題導致 Leader 服務器失去了過半的Follower 節(jié)點的聯系(leader 失去與過半 follower 節(jié)點聯系辞居,可能是 leader 節(jié)點和 follower 節(jié)點之間產生了網絡分區(qū)楷怒,那么此時的 leader 不再是合法的 leader 了),那么就會進入到崩潰恢復模式瓦灶。在ZAB 協議中鸠删,為了保證程序的正確運行,整個恢復過程結束后需要選舉出一個新的Leader.
為了使 leader 掛了后系統能正常工作贼陶,需要解決以下兩個問題
-
1. 已經被處理的消息不能丟失
當 leader 收到合法數量 follower 的 ACKs 后刃泡,就向各個 follower 廣播 COMMIT 命令,同時也會在本地執(zhí)行 COMMIT 并向連接的客戶端返回「成功」每界。但是如果在各個 follower 在收到 COMMIT 命令前 leader 就掛了捅僵,導致剩下的部分服務器并沒有執(zhí)行都這條消息家卖。 -
2. 被丟棄的消息不能再次出現
當 leader 接收到消息請求生成 proposal 還沒轉發(fā)到followe就掛了,其他 follower 并沒有收到此 proposal叁征,因此經過恢復模式重新選了 leader 后纳账,這條消息是被跳過的。 此時捺疼,之前掛了的 leader 重新啟動并注冊成了 follower疏虫,他保留了被跳過消息的 proposal 狀態(tài),與整個系統的狀態(tài)是不一致的啤呼,需要將其刪除**卧秘。
消息廣播的實現原理
這個與分布式事務的 2pc 和 3pc 協議有關,消息廣播的過程實際上是一個簡化版本的二階段提交過程.
- leader 接收到事務型請求(增刪改)后官扣,將消息賦予
一個全局唯一的64 位自增 id翅敌,叫:zxid,通過 zxid 的大小
比較既可以實現因果有序這個特征 -
leader 為每個 follower 準備了一個 FIFO 隊列(通過 TCP
協議來實現惕蹄,以實現了全局有序這一個特點)將帶有 zxid
的消息作為一個提案(proposal)分發(fā)給所有的 follower -
當 follower 接收到 proposal蚯涮,先把 proposal 寫到磁盤,
寫入成功以后再向 leader 回復一個 ack - 當 leader 接收到合法數量(超過半數節(jié)點)的 ACK 后卖陵,
leader 就會向這些 follower 發(fā)送 commit 命令遭顶,同時會
在本地執(zhí)行該消息 -
當 follower 收到消息的 commit 命令以后,會提交該消
息
leader 的投票過程泪蔫,不需要 Observer 的 ack液肌,也就是Observer 不需要參與投票過程,但是 Observer 必須要同步 Leader 的數據從而在處理請求的時候保證數據的一致性
ZAB 協議需要滿足上面兩種情況鸥滨,就必須要設計一個leader 選舉算法:
能夠確保已經被 leader 提交的事務Proposal能夠提交嗦哆、同時丟棄已經被跳過的事務Proposal。
針對這個要求
- 如果 leader 選舉算法能夠保證新選舉出來的 Leader 服務器擁有集群中所有機器最高編號(ZXID 最大)的事務Proposal婿滓,那么就可以保證這個新選舉出來的 Leader 一定具有已經提交的提案老速。
因為所有提案被 COMMIT 之前必須有超過半數的 follower ACK,即必須有超過半數節(jié)點的服務器的事務日志上有該提案的 proposal凸主,因此橘券,只要有合法數量的節(jié)點正常工作,就必然有一個節(jié)點保存了所有被 COMMIT 消息的 proposal 狀態(tài)另外一個
zxid 是 64 位
- 高 32 位是 epoch 編號卿吐,每經過一次 Leader 選舉產生一個新的 leader旁舰,新的 leader 會將epoch 號+1
- 低 32 位是消息計數器,每接收到一條消息這個值+1嗡官,新 leader 選舉后這個值重置為 0.
這樣設計的好處在于老的 leader 掛了以后重啟箭窜,它不會被選舉為 leader,因此此時它的 zxid 肯定小于當前新的 leader衍腥。當老的leader 作為 follower 接入新的 leader 后磺樱,新的leader 會 讓它將所有的擁有舊的 epoch 號的未被 COMMIT 的proposal 清除
關于 ZXID
zxid纳猫,也就是事務 id
為了保證事務的順序一致性,zookeeper 采用了遞增的事務 id 號(zxid)來標識事務竹捉。
所有的提議(proposal)都在被提出的時候加上了 zxid芜辕。
實際上 zxid 是一個 64 位的數字,它高 32 位是 epoch用來標識 leader 關系是否改變(ZAB 協議通過 epoch 編號來區(qū)分 Leader 周期變化的策略)块差,每次一個 新leader 被選出來侵续,它都會有一個新的epoch=(原來的 epoch+1),標識當前屬于那個 leader 的統治時期憨闰。低 32 位用于遞增消息計數.
epoch 的變化大家可以做一個簡單的宕機實驗
- 啟動一個 zookeeper 集群询兴。
- 在 /tmp/zookeeper/VERSION-2 路 徑 下 會 看 到 一 個
currentEpoch 文件。文件中顯示的是當前的 epoch- 把 leader 節(jié)點停機起趾,這個時候在看 currentEpoch 會有
變化诗舰。 隨著每次選舉新的 leader,epoch 都會發(fā)生變化
4. leader 選舉
Leader 選舉會分兩個類型
- 啟動的時候的 leader 選舉
- leader 崩潰的時候的的選舉
ZK服務器結點狀態(tài)分為四種
- LOOKING (選舉狀態(tài),觀望狀態(tài))
- LEADING
- FOLLOWING
- OBSERVING
服務器啟動時的 leader 選舉
每個節(jié)點啟動的時候狀態(tài)都是 LOOKING训裆,處于觀望狀態(tài)眶根,接下來就開始進行選leader流程
進行 Leader 選舉,至少需要兩臺機器
在集群初始化階段边琉,當有一臺服務器 Server1 啟動時属百,它本身是無法進行和完成 Leader 選舉,當第二臺服務器 Server2 啟動時变姨,這個時候兩臺機器可以相互通信族扰,每臺機器都試圖找到 Leader,于是進入 Leader 選舉過程定欧。
選舉過程如下
(1) 每個 Server 發(fā)出一個投票渔呵。由于是初始情況,Server1和 Server2 都會將自己作為 Leader 服務器來進行投票砍鸠,每次投票會包含所推舉的服務器的 myid 和 ZXID扩氢、epoch,使用(myid, ZXID,epoch)來表示爷辱,此時 Server1的投票為(1, 0)录豺,Server2 的投票為(2, 0),然后各自將這個投票發(fā)給集群中其他機器饭弓。
(2) 接受來自各個服務器的投票双饥。集群的每個服務器收到投票后劈猪,首先判斷該投票的有效性烤蜕,如檢查是否是本輪投票(epoch)素邪、是否來自LOOKING狀態(tài)的服務器值桩。
(3) 處理投票。針對每一個投票裹唆,服務器都需要將別人的投票和自己的投票進行 PK拔妥,
PK 規(guī)則如下
i. 優(yōu)先檢查 ZXID荆萤。ZXID 比較大的服務器優(yōu)先作為Leader
ii. 如果 ZXID 相同舍咖,那么就比較 myid矩父。myid 較大的服務器作為 Leader 服務器。對于 Server1 而言排霉,它的投票是(1, 0)窍株,接收 Server2的投票為(2, 0),首先會比較兩者的 ZXID攻柠,均為 0球订,再比較 myid,此時 Server2 的 myid 最大瑰钮,于是更新自己的投票為(2, 0)冒滩,然后重新投票,對于 Server2 而言浪谴,它不需要更新自己的投票开睡,只是再次向集群中所有機器發(fā)出上一次投票信息即可。
(4) 統計投票苟耻。每次投票后篇恒,服務器都會統計投票信息,判斷是否已經有過半機器接受到相同的投票信息凶杖,對于 Server1胁艰、Server2 而言,都統計出集群中已經有兩
臺機器接受了(2, 0)的投票信息智蝠,此時便認為已經選出了 Leader腾么。
(5) 改變服務器狀態(tài)。一旦確定了 Leader杈湾,每個服務器就會更新自己的狀態(tài)哮翘,如果是 Follower,那么就變更為FOLLOWING毛秘,如果是 Leader饭寺,就變更為 LEADING。
運行過程中的 leader 選舉
當集群中的 leader 服務器出現宕機或者不可用的情況時叫挟,那么整個集群將無法對外提供服務艰匙,而是進入新一輪的Leader 選舉,服務器運行期間的 Leader 選舉和啟動時期的 Leader 選舉基本過程是一致的抹恳。
(1) 變更狀態(tài)员凝。Leader 掛后,余下的非 Observer 服務器都會將自己的服務器狀態(tài)變更為 LOOKING奋献,然后開始進入 Leader 選舉過程健霹。
(2) 每個 Server 會發(fā)出一個投票旺上。在運行期間,每個服務器上的 ZXID 可能不同糖埋,此時假定 Server1 的 ZXID 為123宣吱,Server3的ZXID為122;在第一輪投票中瞳别,Server1和 Server3 都會投自己征候,產生投票(1, 123),(3, 122)祟敛,然后各自將投票發(fā)送給集群中所有機器疤坝。接收來自各個服務器的投票。與啟動時過程相同馆铁。
(3) 處理投票跑揉。與啟動時過程相同,此時埠巨,Server1 將會成
為 Leader历谍。
(4) 統計投票。與啟動時過程相同乖订。
(5) 改變服務器的狀態(tài)扮饶。與啟動時過程相同
Leader 選舉源碼分析
啟動主線程,QuorumPeer 重寫了 Thread.start 方法調用
調用QUORUMPEER的START方法
loaddatabase 主要是從本地文件中恢復數據乍构,以及獲取最新的 zxid
初始化LEADERELECTION
配置選舉算法甜无,選舉算法有3 種,可以通過在 zo o.cfg 里面進行配置哥遮,默認是 fast 選舉
繼續(xù)看FastLeaderElection 的初始化動作岂丘,主要初始化了業(yè)務層的發(fā)送隊列和接收隊列
接下來調用fle.start() , 也就是會調用 FastLeaderElectionstart() 方法,該方法主要是對發(fā)送線程和接收線程的初始化 眠饮,左邊是 FastLeaderElection 的 start 奥帘,右邊是messager .start()
wsThread 和 wrThread 的初始化動作在FastLeaderElection的 starter 方法里面進行,這里面有兩個內部類仪召,一個是 WorkerSender 寨蹋,一個是 WorkerReceiver負責發(fā)送投票信息和接收投票信息
前面部分主要是做JMX 監(jiān)控注冊
重要的代碼在這個while 循環(huán)里
調用setCurrentVote(makeLEStrategy(). lookForLeader());最終根據策略應該運行 Fast LeaderElection 中的選舉算法
LOOKFORLEADER開始選舉
消息如何廣播,看SENDNOTIFICATIONS
WORKERSENDER
FastLeaderElection 選舉過程
其實在這個投票過程中就涉及到幾個類
FastLeaderElection: FastLeaderElection 實現了 Election 接口 召娜,實現 各服務器之間基于 TCP 協議進行選舉
Notification:內部類运褪, Notification 表示收到的選舉投票信息(其他服務器發(fā)來的選舉投票信息),其包含了被選舉者的 id 、 zxid 秸讹、選舉周期等信息
ToSend: ToSend 表示發(fā)送給其他服務器的選舉投票信息檀咙,也包含了被選舉者的
id 、 zxid 璃诀、選舉周期等信息
Messenger: Messenger 包含了 WorkerReceiver 和WorkerSender 兩個內部類
- WorkerReceiver實現了 Runnable 接口弧可,是選票接收器。其會不斷地從 QuorumCnxManager 中獲取其他服務器發(fā)來的選舉消息文虏,并將其轉換成一個選票侣诺,然后保存到recvqueue 中
- WorkerSender也實現了 Runnable 接口殖演,為選票發(fā)送器氧秘,其會不斷地從 sendqueue 中獲取待發(fā)送的選票,并將其傳遞到底層 QuorumCnxManager 中