持续更新的Zookeeper知识总结
簡介
Zookeeper為分布式應用 提供了高效且可靠的分布式協調服務,提供了諸如統一命名服務、發布訂閱、負載均衡、配置管理和分布式鎖等分布式的基礎服務。
設計目標是將那些復雜且容易出錯的分布式一致性服務封裝起來,構成一個高效可靠的原語集,并以一系列簡單易用的接口提供給用戶使用。
可以保證以下特性:
基本概念/原理
集群角色
通常在分布式系統中,構成一個集群的每一臺機器都有自己的角色,最典型的集群模式就是Master/Slave模式。而在Zookeeper中,引入了Leader、Follower、Observer(可以沒有)三種角色。Zookeeper集群中的所有機器通過一個Leader選舉過程選定一臺被稱為“Leader”的機器,Leader服務器為客戶端提供讀和寫服務。Follower和Observer都能提供讀服務,唯一的區別是,Observer機器不參與Leader選舉過程,也不參與寫操作的“過半寫成功”策略,因此Observer可以在不影響寫性能的情況下提升集群的讀性能。
?
Leader是整個Zookeeper集群工作機制的核心,主要工作有以下兩個:
1)事務請求的唯一調度和處理者,保證集群事務處理的順序性
2)集群內部各服務器的調度者
Follower是Zookeeper集群狀態的跟隨者,其主要工作有以下三個:
1)處理客戶端非事務請求,轉發事務請求給Leader
2)參與事務請求Proposal的投票
3)參與Leader選舉
?
Observer和Follower很像,唯一的區別是不參與任何形式的投票,只提供非事務服務通常用在不影響集群事務處理能力的前提下提升集群的非事務處理能力。
事務
在Zookeeper中,事務是指能夠改變Zookeeper服務器狀態的操作,我們稱之為事務操作。對于每一個事務請求,Zookeeper都會為其分配一個全局唯一的事務iD,用ZXID表示,通常是一個64位的數字。每一個ZXID對應一次更新操作,從ZXID中可以識別出Zookeeper處理這些更新操作請求的全局順序。
會話
Session是指客戶端會話。一個客戶端連接是指客戶端和服務器之間的一個TCP長連接。從第一次連接建立開始,客戶端會話的生命周期也開始了,通過這個連接,客戶端能夠提供心跳檢測與服務器保持有效的會話,也能夠向Zookeeper服務器發送請求并接收響應,同時還能通過該連接接收來自服務器的Watch事件通知。Session的sessionTimeout值用來設置一個客戶端會話的超時時間。當由于服務器壓力太大、網絡故障或者客戶端主動斷開連接等各種原因導致客戶端連接斷開時,只要在sessionTimeout規定的時間內能夠重新連接上集群中任意一臺機器,那么之前創建的會話仍然有效。
數據節點 ZNode
Zookeeper將所有數據存儲在內存中,數據模型是一棵樹,由/進行分割的路徑,就是一個ZNode(比如/foo/path1)。每個ZNode都會保存自己的數據內容,同時保存一系列屬性信息。
ZNode可以分為持久節點和臨時節點。持久節點是指一旦這個ZNode被創建了,除非主動進行ZNode的移除操作,否則這個ZNode將一直保存在Zookeeper上。而臨時節點的生命周期與客戶端會話綁定,一旦客戶端會話失效,那么這個客戶端創建的所有臨時節點都會被移除。另外,Zookeeper還允許用戶為每個節點添加一個SEQUENTIAL。一旦節點被標記上這個屬性,那么在這個節點被創建的時候,Zookeeper會自動在其節點名后面追加上一個整型數字,這個整型數字是一個由父節點維護的自增數字。
數據節點分為持久節點、臨時節點和順序節點,可以生成四種組合:持久、持久順序、臨時、臨時順序。
?
版本
對應每個ZNode,Zookeeper都會為其維護一個叫做Stat的數據結構,Stat中記錄了這個ZNode 的三個數據版本,分別是version(當前ZNode版本)、cversion(當前ZNode子節點的版本)和aversion(當前ZNode的ACL版本)。還有czxid(創建時的事務ID),mzxid(最后一個被更新時的事務ID)等。
version為0表示自創建該節點后,被更新過0次。注意即使變更并沒有使得數據內容的值發生變化,version的值仍然會變更。
版本的作用可以用樂觀鎖原理來解釋,更新數據的時候ZK使用樂觀鎖來保證原子性。
Watcher
Zookeeper允許用戶在指定節點上注冊一些Watcher,并且在一些特定事件觸發的時候,Zookeeper服務器會將事件通知到感興趣的客戶端上去。
Watcher機制包括客戶端線程、客戶端WatchManager和ZK服務器三部分。客戶端在向ZK服務器注冊Watcher的同時,會將Watcher對象存儲在客戶端的WatchManager中。當Zookeeper服務器觸發Watcher事件后,會向客戶端發送通知,客戶端線程從WatchManager中取出對應的Watcher對象來執行回調邏輯。
KeeperState和EventType兩個枚舉分別代表了通知狀態和事件類型。
?
注意,客戶端無法直接從該事件中獲取到對應數據節點的原始數據內容以及變更后的新數據內容,而是需要客戶端再次主動去重新獲取數據。
?
Watcher特性:
1)一次性:無論是服務器還是客戶端,一旦一個Watcher被觸發,ZK都會將其從相應的存儲中移除。因此開發人員必須要進行反復注冊,這樣的設計有效地減輕了服務器的壓力
2)客戶端串行執行:Watcher的回調是一個串行同步的過程,這為我們保證了順序。
3)輕量:WatchedEvent是ZK整個Watcher通知機制的最小通知單元,只包含通知狀態、事件類型和節點路徑。
ACL
Zookeeper使用ACL(訪問控制列表)策略來進行權限控制,避免因誤操作而帶來的數據隨意變更導致的分布式系統異常。
Zookeeper定義了5種權限:
CREATE:創建節點
READ:讀取節點數據和子節點列表
WRITE:更新節點數據的權限
DELETE:刪除子節點
ADMIN:設置節點ACL
?
?
ZAB協議
ZAB:Zookeeper Atomic Broadcast 原子消息廣播協議
核心
1) Zookeeper使用一個單一的主進程來接收并處理客戶端的所有事務請求,并采用ZAB的原子廣播協議,將服務器數據的狀態變更以事務Proposal的形式廣播到所有的副本進程上去。ZAB協議的這個主備模型架構保證了同一時刻集群中只能有一個主進程來廣播服務器的狀態變更,因此能夠很好地處理客戶端大量的并發請求。
2) 在分布式環境中,順序執行的一些狀態變更其前后存在一定的依賴關系,有些狀態變更必須依賴于比它早生成的那些狀態變更。ZAB協議必須能夠保證一個全局的變更序列被順序地應用。
3)所有事務請求必須由一個全局唯一的服務器來協調處理,這樣的服務器被稱為Leader服務器,而余下的服務器則稱為Follower。Leader負責將一個客戶端事務請求轉換成一個Proposal,并將該Proposal分發給集群中所有的Follower服務器。之后Leader需要等待所有Follower的反饋,一旦超過半數的Follower進行了正確的反饋后,那么Leader就會再次向所有的Follower分發Commit消息,要求其將前一個Proposal進行提交。
?
內容介紹
ZAB協議包括兩種基本的模式,分別是崩潰恢復和消息廣播。當整個服務框架在啟動過程中,或是當Leader出現網絡中斷、崩潰退出等異常情況時,ZAB協議就會進入恢復模式,并選舉產生新的Leader。當選舉產生了新的Leader服務器,同時集群中已經有過半的機器與該Leader服務器完成了狀態同步(數據同步)之后,ZAB協議就會退出恢復模式。
?
當集群中已經有過半的Follower完成了與Leader的狀態同步,那么整個服務器框架就可以進入消息廣播模式。當一臺同樣遵循ZAB協議的服務器啟動后加入到集群中時,如果此時集群中已經存在Leader在負責進行消息廣播,那么新加入的服務器就會自覺進入數據恢復模式,找到Leader,并與其進行數據同步,然后一起參與到消息廣播流程中去。
?
Leader在接收到客戶端的事務請求后,會生成對應的Proposal并發起一輪廣播協議;而如果集群中的其他機器接收到客戶端的事務請求,那么這些非Leader服務器會首先將這個事務請求轉發給Leader。
?
當Leader崩潰或機器重啟,或者集群中已經不存在過半的服務器與該Leader保持正常通信時,那么在重新開始新一輪的原子廣播操作之前,所有進程首先會使用崩潰恢復協議來使彼此達到一個一致的狀態,于是整個ZAB流程就會從消息廣播模式進入到崩潰恢復模式。
?
消息廣播
消息廣播使用了一個原子廣播協議,類似于2PC。
針對客戶端的事務請求,Leader會為其生成對應的事務Proposal,并將其發送給集群中其余所有的機器,然后再分別收集各自的選票,最后進行事務提交。
但是與2PC還是略有不同,移除了中斷事務邏輯(rollback),所有的Follower要么正常反饋Leader提出的Proposal,要么就拋棄Leader;并且可以在過半的Follower已經反饋ack時就開始提交事務Proposal了,而不需要等待所有Follower。
?
這樣簡化后的2PC是無法處理Leader崩潰而帶來的數據不一致問題的,因此在ZAB協議中添加了崩潰恢復模式來解決這個問題。
另外,整個消息廣播協議是基于具有FIFO特性的TCP協議來進行網絡通信的,因此能夠很容易地保證消息廣播過程中消息接收與發送的順序性。
?
在整個消息廣播過程中,Leader會為每個事務請求分配一個全局遞增的唯一ID,稱為事務ID(ZXID)。由于ZAB協議需要保證每一個消息嚴格的因果關系,因此必須將每個事務Proposal按照其ZXID的先后順序來進行排序與處理。
?
在消息廣播中,Leader會為每個Follower都各自分配一個單獨的隊列,然后將需要廣播的事務Proposal依次放入到這些隊列中,并且按照FIFO策略進行消息發送。每個Follower在接收到這個事務Proposal之后,都會首先將其以事務日志的形式寫入到本地磁盤中,并且在成功寫入后反饋給Leader一個ack。當Leader接收到過半數的Follower的ack響應后,就會廣播一個Commit消息給所有的Follower以通知其進行事務提交,同時Leader自身也會完成對事務的提交,而每一個Follower在接收到Commit請求后,也會完成事務的提交。
崩潰恢復
ZAB協議需要一個快速可靠的Leader選舉算法,不僅需要讓Leader自己知道自己被選舉為Leader,并且還需要讓集群中的所有其他機器也能夠快速感知到選舉產生的Leader服務器。
?
1)ZAB協議需要保證已經在Leader上提交的事務最終被所有服務器都提交
?
假設一個事務在Leader上被提交了,并且已經得到過半Follower的ack,但是在它將commit消息發送給所有Follower之前,Leader掛了。
?
2)ZAB協議需要確保丟棄那些只在Leader上被提出的事務。
假設Leader提出了一個事務Proposal之后馬上崩潰退出,從而導致集群中的其他服務器都沒有收到這個事務Proposal。于是,當Leader恢復后再次加入集群中的時候,ZAB協議需要確保丟棄該事務。
?
基于以上兩個特殊情況,需要保證Leader選舉算法:能夠確保提交已經被Leader提交的事務Proposal,同時丟棄已經被跳過的事務Proposal。
如果讓Leader選擇算法能夠保證新選舉出來的Leader擁有集群中所有機器最高編號(ZXID最大)的事務Proposal,那么就可以保證這個新選舉出來的Leader一定具有所有已經提交Proposal,并且可以省去Leader服務器檢查Proposal的提交和丟棄工作的這一步操作了。
Leader選舉
術語:SID 服務器ID
ZXID 事務ID,唯一標識一次服務器狀態的變更
Vote 投票 當集群中的機器發現自己無法檢測到Leader時,就會開始嘗試進行投票
Quorum 過半機器數 如果總機器數為n,那么Quorum為n/2+1
?
進入Leader選舉
當Zookeeper集群中的一臺服務器出現以下兩種情況之一時,就會開始進入Leader選舉:
1)服務器 初始化
2)服務器運行時無法和Leader保持連接
?
而當一臺機器進入Leader選舉時,當前集群也可能處于以下兩種狀態:
1)集群中本來存在Leader
2)集群中不存在Leader
?
1)中通常是集群中的某一臺機器啟動比較晚,在它啟動之前,集群已經可以正常工作,即已經存在了一臺Leader服務器。針對這種情況,當該機器試圖去選舉Leader時,會被告知當前服務器的Leader,對于該機器來說,僅僅需要和Leader機器建立起連接,并進行狀態同步即可。
?
第一次投票
通常有兩種情況會導致集群中不存在Leader,一種情況是在整個服務器剛剛初始化情況時,此時尚未產生Leader;另一種情況是Leader宕機。
此時,集群中的所有機器都處于一種試圖選舉出一個Leader的狀態,這種狀態稱為Looking。當一臺機器處于Looking狀態時,那么它就會向集群中的所有其他機器發送消息,這個消息稱為投票。
?
投票可以表示為(SID,ZXID),SID是所推舉的服務器唯一ID,ZXID是事務ID。
在第一次投票時,由于還無法檢測到集群中其他機器的狀態信息,因此每臺機器都是將自己作為被推舉的對象來進行投票。
第二次投票
集群中的每臺機器發出自己的投票后,也會收到來自集群中其他機器的投票。每臺機器都會根據一定的規則,來處理收到的其他機器的投票,并以此來決定是否需要變更自己的投票。
vote_sid:接收到的投票中所推舉的Leader的SID
vote_zxid:接收到的投票中所推舉的Leader的ZXID
self_sid:當前服務器自己的SID
self_zxid:當前服務器自己的ZXID
?
對于每次收到的投票的處理,都是一個對(vote_sid,vote_zxid)和(self_sid,self_zxid)的對比的過程。
規則1:如果vote_zxid大于self_zxid,那么認可當前收到的投票,并再次將該投票發送出去
規則2:如果vote_zxid等于self_zxid,則就對比兩者的SID。如果vote_sid大于self_sid,那么就認可當前接收到的投票,并再次將該投票發送出去
規則3:如果vote_zxid等于self_zxid,并且vote_sid小于self_sid,那么同樣堅持自己的投票,不做變更。
確定Leader
經過第二次投票后,集群中的每臺機器都會再次受到其他機器的投票,然后開始統計投票。如果一臺機器收到超過半數的相同的投票,那么整個投票對應的SID機器即為Leader。
小結
哪臺機器上的數據越新,那么越有可能成為Leader。數據越新,ZXID就越大,也就越能保證數據的恢復。如果幾臺服務器有著相同的ZXID,那么SID較大的服務器成為Leader。
數據同步
完成Leader選舉之后,在接收客戶端事務請求前,Leader會確認事務日志中的所有Proposal是否已經被集群中過半的機器提交了,即是否完成數據同步。
?
正常情況下:Leader會為每一個Follower都準備一個隊列,并將那些沒有被各Follow同步的事務以Proposal消息的形式逐個發送給Follower,并在每一個Proposal消息后面緊接著再發送一個Commit消息,以表示該事務已經被提交。等到Follower將所有尚未同步的事務Proposal都從Leader上同步過來并成功應用到本地數據庫中,Leader就會將該Follower加入到真正的可用Follower列表中。
?
如何處理需要被丟棄的Proposal的?
ZXID是一個64位的數字,低32位是一個單調遞增的計數器,針對客戶端的每一個事務請求,Leader在產生一個新事務Proposal的時候,都會對該計數器進行加一操作;高32位則代表了Leader周期epoch編號,每當選舉產生一個新的Leader,就會從這個Leader上取出其本地日志中最大事務Proposal的ZXID,并從該ZXID中解析出epoch,然后再對其進行加一操作,之后就會以此編號作為新的epoch,并將低32位置0來開始新的ZXID。ZAB協議中這一通過epoch編號來區分Leader周期變化的策略,能夠避免不同的Leader錯誤地使用相同的ZXID編號提出不一樣的事務Proposal的異常情況,這對于識別Leader崩潰恢復前后生成的Proposal非常有幫助。
?
當一個包含了上一個Leader周期中尚未提交過的事務Proposal的服務器啟動時,其肯定無法成為Leader,因為當前集群中一定包含一個Quorum集合(存在過半的處于UP狀態的進程的進程子集),該集合中的機器一定包含了更高epoch的事務Proposal,因此這臺機器的事務Proposal一定不是最高,也就無法成為Leader了。當這臺機器加入到集群后,以Follower的角色連接上Leader后,leader會根據自己服務器上最后被提交的Proposal來與Follower的Proposal進行對比,對比的結果當然是Leader要求Follower進行一個回退操作——回退到一個已經被集群中過半機器提交的最新的事務Proposal。
?
與Paxos算法的聯系與區別
ZAB協議并不是一個Paxos算法的典型實現。
聯系:
?
區別:
Paxos算法中,一個新Leader會進行兩個階段的工作。第一階段被稱為讀階段,Leader與所有其他機器進行通信來收集上一個Leader的Proposal,并將它們提交;第二階段被稱為寫階段,Leader開始提出自己的提案。
ZAB協議中,在Paxos基礎上增加了一個數據同步階段。在同步階段之前,ZAB也存在一個類似于Paxos讀階段的過程,稱為發現階段。在同步階段中,新的Leader會確保過半的Follower已經提交了之前Leader周期中的所有事務Proposal,該階段可以保證Leader在新的周期提出Proposal之前,所有機器都已經完成對之前所有事務Proposal的提交。一旦完成同步階段后,那么ZAB就會執行和Paxos算法類似的寫階段。
?
本質區別是設計目標不一樣,ZAB主要用于構建一個高可用的分布式數據主備系統,Paxos則用于構建一個分布式的一致性狀態機系統。
?
典型應用
發布訂閱(配置管理)
客戶端向服務器注冊自己需要關注的節點,一旦該節點的數據發生變更,那么客戶端就會向響應的客戶端發送Watcher時間通知,客戶端接收到這個事件通知之后,需要主動到服務端獲取最新的數據。
如果將配置信息存放到Zookeeper上進行集中管理,那么通常情況下,應用在啟動的時候都會主動到Zookeeper服務端上進行一次配置信息的獲取,同時,在指定節點上注冊一個Watcher監聽,這樣的話,如果配置信息發生變更,服務端都會實時通知到所有訂閱的客戶端,從而達到獲取最新配置信息的目的。
命名服務(軟負載中心)
被命名的實體可以是集群中的機器、提供的服務地址或遠程對象。
?
比如RPC的服務注冊查找中心,ZK客戶端與ZK服務器保持連接,連接ZK服務器時RPCServer將自己的IP地址、端口號告訴ZKServer,在ZKServer上創建節點。RPCClient也連接ZK服務器,獲取RPCServer的地址(軟件負載均衡),并注冊Watcher。假如RPCServer掛掉,與ZK服務器的連接斷開,那么臨時節點會被刪除,此時會通知RPCClient,RPCClient可以重新選擇RPCServer進行連接。
?
并且ZK還可以創建全局唯一ID,基于ZK提供的創建節點時使用SEQUENTIAL屬性(順序節點)。
集群管理
希望知道當前集群中有哪些集群在工作;
對集群中每天機器的運行時狀態進行數據收集;
對集群中機器進行上下線通知
?
基于ZK提供的臨時節點和Watcher監聽特性,可以實現另一種集群機器存活性監控的系統。
比如云主機管理,每臺機器上線后,啟動機器上的Agent,向ZK的指定節點進行注冊(臨時節點),此時監控中心會接收到子節點變更事件,即上線通知,于是可以對這個新加入的機器開啟相應的后臺管理邏輯。另一方面,監控中心同樣可以獲取到機器下線的通知。
?
除了要對機器的在線狀態進行檢測,還要對機器的運行時狀態進行監控。在運行的過程中,Agent會定時將主機的運行狀態寫入ZK上的主機節點,監控中心提供訂閱這些節點的數據變更通知來間接地獲取主機的運行時信息。
Master選舉
在分布式系統中,Master往往用來協調集群中的其他系統單元,具有對分布式系統狀態變更的決定權。
場景:集群中的所有系統單元都需要對某個業務提供數據,但計算該數據的代價相當大,于是只希望一臺機器或某幾臺機器來執行該任務,集群中的其他機器共享其計算結果。
那么怎么選擇機器呢?Master選舉
一種方案是所有機器都向關系數據庫插入某條數據,主鍵都是一樣的,誰插入成功,誰就是Master。但是無法感知Master掛掉的這種情形。
而ZK的強一致性可以保證在分布式高并發情況下節點的創建一定能夠保證全局唯一性,即ZK將會保證客戶端無法重復創建一個已經存在的數據節點(某個path下的節點名是唯一的)。只有一個客戶端能夠成功創建這個節點,那么這個客戶端所在機器就成為了Master。同時,其他沒有在ZK上成功創建結點的客戶端,都會在該節點的父節點上注冊一個子節點變更的Watcher,用于監控當前的Master機器是否存活,一旦發現當前的Master掛了,那么其余的客戶端將會重新進行Mater選舉。
分布式鎖
可以采用數據庫的鎖,只是數據庫的性能很差。
排他鎖
可以將一個數據節點(比如/exclusive_lock/lock)視為一個鎖,所有客戶端去創建該節點,創建成功的客戶端就獲得了這個鎖。
如果獲得鎖的機器宕機,或者執行完業務邏輯后,都會將該節點刪除。
無論什么情況下刪除了該節點,都會通知所有在該節點(/exclusive_lock)上注冊了監聽子節點變更的Watcher的機器,這些機器在接收到通知后,會再次重新發起分布式鎖的獲取。
共享鎖
同樣是將數據節點視為一個鎖,只是這里的數據節點是一個順序節點,比如/shared_lock/{host_name}-請求類型-序列號。
獲取鎖時所有客戶端都會到/shared_lock這個節點下面創建一個順序節點,讀請求的話,請求類型為R(寫的話為W)。
共享鎖的定義是可以有讀讀,但不能有讀寫、寫寫、寫讀。
1、創建完節點后,獲取/shared_lock節點下的所有子節點,并對該節點注冊了子節點變更的Watcher監聽。
2、確定自己的節點序號在所有子節點中的順序
3、對于讀請求:如果沒有比自己序號小的子節點,或者所有比自己序號小的節點都是讀請求,那么表示自己已經獲得了共享鎖;如果比自己序號小的子節點中有寫請求,那么就需要進入等待。
對于寫請求:如果自己不是序號最小的子節點,那么就需要進入等待
4、接收到Watcher通知后,重復步驟1
?
釋放鎖時就直接將自己的節點刪除即可。
高可用
Zookeeper集群服務器數推薦為奇數。由于過半存活即可用特性,3臺機器掛掉一臺仍可用。而5臺和6臺服務器在掛掉兩臺后仍可用,掛掉三臺都均不可用,因此5臺和6臺服務器在其容災能力上是沒有區別的,因此Zookeeper集群推薦部署為奇數臺服務器。
總結
以上是生活随笔為你收集整理的持续更新的Zookeeper知识总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java的IO总结
- 下一篇: leetcode279 完全平方数