分布式五、分布式協(xié)調服務二:深入分析zookeeper的實現(xiàn)原理

Valentine 轉載請標明出處。

Zookeeper的由來

1、各個節(jié)點的數(shù)據(jù)一致性;
2、怎么保證任務只在一個節(jié)點上執(zhí)行;
3、如果service1掛了,其他節(jié)點如何發(fā)現(xiàn)并接替任務;
4、存在共享資源,互斥性、安全性。


示例圖

示例圖

Zookeeper的前世今生

從上面的案例可以看出,分布式系統(tǒng)的很多難題,都是由于缺少協(xié)調機制造成的,在分布式協(xié)調這塊做得比較好的有Google的Chubby以及Apache的Zookeeper。
Google Chubby是一個分布式鎖服務,通過Google Chubby來解決分布式協(xié)作、Master選舉與分布式鎖服務相關的問題。
Zookeeper也是類似,因為當時在雅虎內部的很多系統(tǒng)都需要一個系統(tǒng)來進行分布式協(xié)調,但是谷歌的Chubby是不開源的,所以后來雅虎基于Chubby的思想開發(fā)了Zookeeper,并捐贈給了Apache。
在上面這個架構下Zookeeper可以用來解決task執(zhí)行問題,各個服務先去zookeeper上注冊節(jié)點,然后獲得權限以后再來訪問task。

Zookeeper的設計猜想

Zookeeper主要是解決分布式環(huán)境下的服務協(xié)調問題而產生的,如果我們要去實現(xiàn)一個Zookeeper這樣的中間件,我們需要做什么?
1、防止單點故障
如果要防止Zookeeper這個中間件的單點故障,那就要做集群,而且這個集群如果要滿足高性能要求的話,還的是個高性能高可用的集群。高性能意味著這個集群能夠分擔客戶端的請求流量,高可用意味著集群中的某一個節(jié)點宕機以后,不影響整個集群的數(shù)據(jù)和繼續(xù)提供服務的可能性。
結論:所以這個中間件需要考慮到集群,而且這個集群還需要分攤客戶端的請求流量。
2、接著上面那個結論再來思考,如果要滿足這樣的一個高性能集群,最直觀的想法應該是每個節(jié)點都接收到請求,并且每個節(jié)點的數(shù)據(jù)都必須保持一致。實現(xiàn)各個節(jié)點的數(shù)據(jù)一致性,那就要一個leader節(jié)點負責協(xié)調和數(shù)據(jù)同步操作,如果在這樣一個集群中沒有l(wèi)eader節(jié)點,每個節(jié)點都可以接收到請求,那么這個集群的數(shù)據(jù)同步的復雜度是非常大的。
結論:所以這個集群中涉及到數(shù)據(jù)同步以及會存在leader節(jié)點。
3、繼續(xù)思考,如何在這些節(jié)點中選舉出leader節(jié)點,以及l(fā)eader掛了以后,如何恢復?
結論:所以Zookeeper用了基于paxos理論所衍生出來的zab協(xié)議。
4、leader節(jié)點是如何和其他節(jié)點保證數(shù)據(jù)一致性,并且要求是強一致性的。在分布式系統(tǒng)中,每一個機器節(jié)點雖然都能夠知道自己進行的事務操作過程是成功或失敗。但是卻無法直接獲取其他分布式節(jié)點的操作結果。所以當一個事務操作涉及到跨節(jié)點的時候,就需要用到分布式事務,分布式事務的數(shù)據(jù)一致性協(xié)議有2PC協(xié)議和3PC協(xié)議。
基于這些猜想,基本上知道Zookeeper為什么要用到zab理論來做選舉,為什么要做集群,為什么要用到分布式事務來實現(xiàn)數(shù)據(jù)一致性了。接下來逐步去剖析Zookeeper里面的這些內容。

關于2PC提交

(Two Phase Commitment Protocol)當一個事務操作需要跨越多個分布式節(jié)點的時候,為了保持事務的ACID特性,就需要引入一個“協(xié)調者”(TM(事務管理器))來統(tǒng)一調度所有分布式節(jié)點的執(zhí)行邏輯,這些被調度的分布式節(jié)點被成為AP(應用程序)。TM負責調度AP的行為,并最終決定這些AP是否要把事務真正進行提交,因為整個事務是分為兩個階段提交,所以叫2PC。


2PC提交示例圖

階段一:提交事務請求(投票)
1、事務詢問
協(xié)調者向所有的參與者發(fā)送事務內容,詢問是否可以執(zhí)行事務提交操作,并開始等待各參與者的響應。
2、執(zhí)行事務
各個參與者節(jié)點執(zhí)行事務操作,并將Undo和Redo信息記錄到事務日志中,盡量把提交過程中所有消耗時間的操作和準備都提前完成確保后面100%提交事務。
3、各個參與者向協(xié)調者反饋事務詢問的響應
如果各個參與這成功執(zhí)行了事務操作,那么就反饋給參與者yes的響應,表示事務可以執(zhí)行,如果參與者沒有成功執(zhí)行事務,就反饋給協(xié)調者no的響應,如果參與者沒有成功執(zhí)行事務,就反饋給協(xié)調者no的響應,表示事務不可以執(zhí)行,上面這個階段有點類似協(xié)調者組織各個參與者對一次事務操作的投票表態(tài)過程,因此2PC協(xié)議的第一個階段稱為“投票階段”,即各參與者投票表明是否需要繼續(xù)執(zhí)行接下去的事務提交操作。
階段二:執(zhí)行事務提交
在這個階段,協(xié)調者會根據(jù)各參與者的反饋情況來決定最終是否可以進行事務提交操作,正常情況下包含兩種可能:執(zhí)行事務、中斷事務。

Zookeeper的集群

在Zookeeper中,客戶端會隨機連接到Zookeeper集群中的一個節(jié)點,如果是讀請求,就直接從當前節(jié)點中讀取數(shù)據(jù),如果是寫請求,那么請求會被轉發(fā)給leader提交事務,然后leader廣播事務,只要有超過半數(shù)節(jié)點寫入成功,那么請求就會被提交(類2PC事務)。


示例圖

所有事務請求必須由一個全局唯一的服務器來協(xié)調處理,這個服務器就是leader服務器,其他的服務器就是follower。leader服務器把客戶端的寫請求轉化成一個proposal(提議)并把這個proposal分發(fā)給集群中的所有follower服務器,之后leader服務器需要等待所有follower服務器的反饋,一旦超過半數(shù)的follower服務器進行了正確的反饋,那么leader就會再次向所有的follower服務器發(fā)送commit消息,要求各個follower節(jié)點對前面的一個proposal進行提交。

集群角色

leader角色

leader服務器是整個Zookeeper集群的核心,主要的工作任務有兩項:
1、事務請求的唯一調度和處理者,保證集群事務處理的順序性;
2、集群內部各服務器的調度者。

follower角色

follower角色的主要職責是:
1、處理客戶端非事務請求,轉發(fā)事務請求給leader服務器;
2、參與事務請求proposal的投票(需要板書以上服務器通過才能通知leader commit數(shù)據(jù),leader發(fā)起的提案要follower投票);
3、參與leader選舉的投票。

observer角色

observer是zookeeper3.3開始引入的一個全新的服務器角色,從字面理解,該角色充當了觀察者的角色。觀察zookeeper集群中的最新狀態(tài)變化并將這些狀態(tài)變化同步到observer服務器上。observer的工作原理與follower角色基本一致,而它和follower角色唯一的不同在于observer不參與任何形式的投票,包括事務請求proposal和leader選舉。簡單來說observer服務器只提供非事務請求服務,通常在于不影響集群事務處理能力的前提下提升集群非事務處理的能力。

集群組成

通常zookeeper是由2n+1臺server組成,每個server都知道彼此的存在,對于2n+1臺server,只要有n+1臺(大多數(shù))server可用,整個系統(tǒng)保持可用。一個zookeeper集群如果要對外提供可用的服務,那么集群中必須要有過半的機器正常工作并且彼此之間能夠正常通信,基于這個特性,如果想搭建一個能夠允許F臺機器down掉的集群,那么就要部署2*F+1臺服務器構成的zookeeper集群。因此3臺機器構成的zookeeper集群,能夠在掛掉一臺依然能正常工作,一個5臺機器集群的服務,能夠對2臺機器掛掉的情況下進行容災,如果一個6臺服務器構成的集群,同樣只能掛掉2臺機器。所以5臺和6臺在容災能力上并沒有明顯優(yōu)勢,反而增加了網絡通信負擔,系統(tǒng)啟動時,集群中的server會選舉一臺server為leader,其他的就作為follower(先不考慮observer)。
之所以要滿足這樣一個燈飾,是因為一個節(jié)點要成為集群中的leader,需要有超過集群中過半數(shù)的節(jié)點支持,這個涉及到leader選舉算法,同時也涉及到事務請求的提交投票。
例子:
3臺服務器,至少2臺正常運行才行(3的半數(shù)為1.5,半數(shù)以上最少為2),正常運行可以允許1臺服務器掛掉。
4臺服務器,至少3臺正常運行才行(4的半數(shù)為2,半數(shù)以上最少為3),正常運行可以允許1臺服務器掛掉。

ZAB協(xié)議

ZAB(Zookeeper Atomic Broadcast)zookeeper原子廣播協(xié)議,協(xié)議是為分布式協(xié)調服務zookeeper專門設計的一種支持崩潰恢復的原子廣播協(xié)議。在zookeeper中,主要依賴ZAB協(xié)議來實現(xiàn)分布式數(shù)據(jù)一致性,基于該協(xié)議,zookeeper實現(xiàn)了一種主備模式的系統(tǒng)架構來保持集群中各個副本之間的數(shù)據(jù)一致性。

ZAB協(xié)議介紹

ZAB協(xié)議包含兩種基本模式,分別是:
1、崩潰恢復;2、原子廣播。
當整個集群在啟動時,或者當leader節(jié)點出現(xiàn)網絡中斷、崩潰等情況,ZAB協(xié)議就會進入恢復模式并選舉產生新的leader,當leader服務器選舉出來后,并且集群中有過半的機器和該leader節(jié)點完成數(shù)據(jù)同步后(同步指的是數(shù)據(jù)同步,用來保證集群中過半的機器能夠和leader服務器的數(shù)據(jù)狀態(tài)保持一致),ZAB協(xié)議就會退出恢復模式。
當集群中已經有過半的follower節(jié)點完成了和leader同步以后,那么整個集群就進入了消息廣播模式。這個時候,在leader節(jié)點正常工作時,啟動一臺新的服務器加入到集群,那這個服務器會直接進入數(shù)據(jù)恢復模式,和leader節(jié)點進行數(shù)據(jù)同步,同步完成后即可正常對外提供非事務請求的處理。

消息廣播的實現(xiàn)原理

如果大家了解分布式事務的2pc、3pc協(xié)議的話,消息廣播的過程實際上是一個簡化版本的二階段提交過程。
1、leader接收到消息請求后,將消息賦予一個全局唯一的64位自增id,叫zxid,通過zxid的大小比較既可以實現(xiàn)因果有序的特征;
2、leader為每個follower準備了一個FIFO隊列(通過TCP協(xié)議來實現(xiàn),以實現(xiàn)了全局有序這一特點)將帶有zxid的消息作為一個proposal分發(fā)給所有的follower;
3、當follower接收到proposal,先把proposal寫到磁盤,寫入成功之后再向leader回復一個ack;
4、當leader接收到合法數(shù)量(超過半數(shù)幾點)的ack后,leader就會向這些follower發(fā)送commit命令,同時會在本地執(zhí)行該消息;
5、當follower收到消息的commit命令以后,會提交該消息,leader的投票過程,不需要observer的ack,observer不參與投票過程,observer只同步leader的數(shù)據(jù)。


消息廣播示例圖

崩潰恢復(數(shù)據(jù)恢復)

ZAB協(xié)議的這個基于原子廣播協(xié)議的消息廣播過程,在正常情況下是沒有任何問題的,但是一旦leader節(jié)點崩潰,或者由于網絡問題導致leader服務器失去了過半的follower節(jié)點的聯(lián)系(可能是leader節(jié)點和follower節(jié)點之間產生了網絡分區(qū),那么此時的leader不再是合法的leader了),那么就會進入到崩潰恢復模式。在ZAB協(xié)議中,為了保證程序的正確運行,整個恢復過程結束后需要選舉出一個新的leader,為了使leader掛了后系統(tǒng)能正常工作,需要解決兩個問題:
1、已經被處理的消息不能丟失
當leader收到合法數(shù)量follower的ack后,就向各個follower廣播commit命令,同時也會在本地執(zhí)行commit并向連接的客戶端返回【成功】。但是如果在各個follower收到commit之前l(fā)eader就掛了,導致剩下的服務器沒有執(zhí)行這條消息。


示例圖

leader對事務消息發(fā)起commit操作,但是該消息在follower1上執(zhí)行了,但是follower2還沒有收到commit就已經掛了,而實際上客戶端已經收到該事務消息處理成功的回執(zhí)了,所以在zab協(xié)議下需要保證所有機器都要執(zhí)行這個事務消息。
2、被丟棄的消息不能再次出現(xiàn)
當leader接收到消息請求生成proposal后就掛了,其他follower并沒有收到此proposal,因此經過恢復模式重新選了leader后,這條消息是被跳過的。此時,之前掛了的leader重新啟動并注冊成了follower,他保留了被跳過消息的proposal狀態(tài),與整個系統(tǒng)的狀態(tài)是不一致的,需要將其刪除。
ZAB協(xié)議需要滿足上面兩種情況,就必須要設計一個leader選舉算法,能夠確保已經被leader提交的事務proposal能夠提交、同時丟棄已經被跳過的事務proposal。
針對這個要求
1、如果leader選舉算法能夠保證新選舉出來的leader服務器擁有集群中所有機器最高編號(ZXID最大)的事務proposal,那么就可以保證這個新選舉出來的leader一定具有已經提交的提案。因為所有提案被commit之前必須有超過半數(shù)的follower ack,即必須有超過半數(shù)節(jié)點的服務器的事務日志上有該提案的proposal,因此只要有合法數(shù)量的節(jié)點正常工作就必然有一個節(jié)點保存了所有被commit消息的proposal狀態(tài)。還有,zxid是64位,高32位是epoch編號,每經過一次leader選舉產生一個新的leader,新的leader會將epoch號+1,低32位是消息計數(shù)器,每接收到一條消息,這個值+1,新leader選舉后這個值重置為0。這樣設計的好處在于老的leader掛了以后重啟,它不會被選舉為leader,因此此時它的zxid肯定小于當前新的leader。當老的leader作為follower接入新的leader后,新的leader會讓它將所有擁有舊的epoch號的未被commit的proposal清除。

關于zxid

zxid,也就是事務id,為了保證事務的順序一致性,zookeeper采用了遞增的事務id(zxid)來標識事務,所有的提議(proposal)都在被提出的時候加上了zxid。實現(xiàn)中zxid是一個64位的數(shù)字,它高32位是epoch(ZAB協(xié)議通過epoch編號來區(qū)分leader周期變化的策略)用來標識leader關系是否改變,每次一個leader被選舉出來,它都會有一個新的epoch=(原來的epoch+1),標識當前屬于哪個leader的統(tǒng)治時期。低32位用于遞增計數(shù)。
epoch:可以理解為當前集群所處的年代或者周期,每個leader就像皇帝,都有自己的年號,所以每次改朝換代,leader變更之后,都會在前一個年代的基礎上加1。這樣就算就得leader崩潰恢復后,也沒有人聽他的了,因為follower只聽從當前l(fā)eader的命令。
epoch的變化可以做一個簡單的實驗:
1、啟動一個zookeeper集群;
2、在/tmp/zookeeper/VERSION-2路徑下會看到一個currentEpoch文件,文件中顯示的是當前的epoch;
3、把leader節(jié)點停機,這個時候在看currentEpoch會有變化,隨著每次選舉新的leader,epoch都會發(fā)生變化。

leader選舉

leader選舉會分兩個過程
啟動的時候leader選舉、leader崩潰的時候的選舉

服務器啟動時的leader選舉

每個節(jié)點啟動的時候狀態(tài)都是LOOKING,處于觀望狀態(tài),接下來就開始進行選主流程。
進行l(wèi)eader選舉,至少需要兩臺機器,我們選取3臺機器的服務器集群為例。在集群初始化階段,當有一臺服務器server1啟動時,它本身是無法進行和完成leader選舉,當?shù)诙シ掌鱯erver2啟動時,這個時候機器可以互相通信,每臺機器都試圖找到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、統(tǒng)計投票,每次投票后,服務器都會統(tǒng)計投票信息,判斷是否已經有過半機器接受相同的投票信息,對于server1、server2而言,都統(tǒng)計出集群中已經有兩臺機器接受了(2,0)的投票信息,此時便認為已經選出了leader。
5、改變服務器狀態(tài),一旦確定了leader,每個服務器就會更新自己的狀態(tài),如果是follower,那么就變更為FOLLOWING,如果是leader就變更為LEADING。

運行過程中的leader選舉

當集群中的leader服務器出現(xiàn)宕機或者不可用的情況時,那么整個集群將無法對外提供服務,而是進入新一輪的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、統(tǒng)計投票,與啟動時過程相同。
5、改變服務器的狀態(tài),與啟動時過程相同。

學習來源https://www.gupaoedu.com/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容