1. 程式人生 > >《從 PAXOS 到 ZOOKEEPER:分散式一致性原理與實踐》讀書筆記[3]——Zookeeper 技術內幕

《從 PAXOS 到 ZOOKEEPER:分散式一致性原理與實踐》讀書筆記[3]——Zookeeper 技術內幕

1 系統模型

1.1 資料模型

Zookeeper 中,每一個數據節點都被稱為一個 ZNode,所有 ZNode 按層次化結構進行組織,節點路徑標識方式和 Unix 檔案系統路徑相似,由一系列使用 / 進行分割的路徑表示,開發人員可以向這個節點中寫入資料,也可以在節點下面建立子節點

Zookeeper 對於每一個事務請求,都會為其分配一個全域性唯一的事務 ID,用 ZXID 來表示,通常是一個 64 位的數字,低 32 位用來對更新操作遞增計數,如果有新的 leader 產生出來,高 32 位會自增。從這些 ZXID 中可以間接識別出 Zookeeper 處理這些更新操作請求的全域性順序,從而保證事務的全域性順序一致性

1.2 節點特點

四種節點型別:持久節點(PERSISTENT),持久順序節點(PERSISTENT_SEQUENTIAL),臨時節點(EPHEMERAL),臨時順序節點(EPHEMERAL_SEQUENTIAL)

1.3 版本(原子性)

對於樂觀鎖控制事務分成以下三個階段:資料讀取,寫入校驗,資料寫入。version 屬性正是用來實現樂觀鎖機制中的寫入校驗。

寫入校驗:Zookeeper 會從 setDataRequest 請求中獲取當前的版本 version,同時從資料記錄 nodeRecord 中獲取到當前伺服器上該資料的最新版本 currentVersion。如果 version 為 -1,那麼說明客戶端並不要求使用樂觀鎖,可以忽略版本對比;如果 version 不是 -1,那麼就比對 version 和 currentVersion ,如果兩個版本不匹配,那麼就會丟擲異常。

1.4 Watcher

客戶端在向 Zookeeper 伺服器註冊 Watcher 的同時,會將 Watcher 物件儲存在客戶端的 WatcherManager 中,當 Zookeeper 伺服器端觸發 Watcher 事件後,會向客戶端傳送通知,客戶端接收到 Watcher 通知後,主動從 WatcherManager 中取出對應的 Watcher 物件來執行回撥邏輯。

Watcher 特性

  • 一次性:一旦一個 Watcher 被觸發,Zookeeper 都會將其移除。這種設計有效減輕了服務端的壓力
  • 客戶端回撥過程是串行同步執行過程
  • 輕量:Watcher 通知只傳遞是否發生事件,不包含事件具體內容
  • 非同步傳送:Zookeeper 只能最終一致性,無法保證強一致性
  • 註冊 Watcher:getData,exits,getChildren
  • 觸發 Watcher:create,delete,setData

工作機制

Watcher 機制可以概述為以下三個過程:客戶端註冊 Watcher,服務端處理 Watcher 和客戶端回撥 Watcher

客戶端註冊 Watcher

  • 呼叫 getData/getChildren/extis 三個 API 傳入 Watcher 物件
  • 標記請求 request,封裝 Watcher 到 WatcherRegistration
  • 封裝成 Packet 物件,向服務端傳送 request
  • 收到服務端響應後,將 Watcher 註冊到 ZKWatcerManager 中進行管理

服務端處理

  • 服務端接收並存儲 Watcher
  • 封裝 WatchedEvent 物件,查詢並呼叫 process 方法來觸發 Watcher(TCP)

客戶端回撥

  • SendThread 接收事件通知:反序列化、處理 chrootPath、還原 WatchedEvent、回撥 Watcher
  • EventThread 處理事件通知

2 客戶端

Zookeeper 客戶端核心元件:

  • Zookeeper 例項:客戶端的入口
  • ClientWatchManager:客戶端 Watcher 管理器
  • HostProvider:客戶端地址列表管理器
  • ClientCnxn:客戶端核心執行緒,SendThread(客戶端和服務端網路 I/O 通訊),EventThread(對服務端事件進行處理)

3 會話管理

  • 分桶策略:將類似的會話放在同一個區塊中進行管理,以便於 Zookeeper 對會話進行不同區塊的隔離處理以及同一區塊的統一處理
  • 分配原則:每個會話的“下次超時時間點”

4 伺服器

4.1 伺服器角色

Leader

  • 事務請求的唯一排程和處理者,保證叢集事務處理的順序性
  • 叢集內部各服務的排程者

Follower

  • 處理客戶端的非事務請求,轉發事務請求給 Leader 伺服器
  • 參與事務請求 Proposal 的投票
  • 參與 Leader 選舉投票

Observer

  • 處理客戶端的非事務請求,轉發事務請求給 Leader 伺服器
  • 不參與任何形式的投票

5 Leader 選舉

5.1 啟動時期

  • 1、每個 Server 會發出一個投票,初始情況,伺服器都會將票投給自己,投票的基本元素為:所推舉伺服器的 myid 和 ZXID,形式為(myid,ZXID)
  • 2、接收來自各個伺服器的投票
  • 3、處理投票:優先檢查 ZXID,ZXID 較大的伺服器優先作為 Leader;其次為 myid,myid 較大的伺服器作為 Leader
  • 4、統計投票:判斷是否已經有過半的機器接收到相同的投票資訊
  • 5、改變伺服器狀態

處理投票細則

三臺伺服器組成叢集,當 server1 啟動時,僅有一臺機器,無法進行 Leader 選舉,但是 server2 啟動後,就進入了選舉狀態。對於 server1,它自己的投票是(1,0),接收到的投票是(2,0),根據規則顯然 server1 會更新自己的投票為(2,0)然後將投票重新發出去,而 server2 只需要再一次向叢集中所有機器發出上一次投票資訊即可,這時候 server1 和 server2 都收到相同的投票資訊(2,0),滿足過半要求,則已經選出了 Leader

5.2 執行期間

  • 1、改變狀態
  • 2、每個 server 會發起一個投票
  • 3、接收來自各個伺服器的投票
  • 4、處理投票
  • 5、統計投票
  • 6、改變伺服器狀態

處理投票細則

同樣是三臺伺服器組成的叢集,當前的 Leader 是掛掉的 server2,這時候需要生成投票資訊。由於在執行期間,每個伺服器上的 ZXID 可能不同,我們假定 server1 的 ZXID 是 123,server3 的 ZXID 是 122。在第一輪投票中,機器會投給自己,即分別產生投票(1,123)和(3,122),然後將投票傳送給叢集中的所有機器。按照投票處理規則,明顯 server1 會成為新的 Leader。

5.3 演算法分析

Zookeeper 中提供了三種 Leader 選舉演算法,分別是 LeaderElection(UDP)、FastLeaderElection(UDP)、FastLeaderElection(TCP),可以通過配置檔案中 zoo.cfg 中使用 electionAlg 屬性來指定。

術語解釋

  • SID:伺服器 ID
  • ZXID:事務 ID
  • Vote:投票
  • Quorum:過半機器數

第一次投票

第一次投票的時候,由於還無法檢測到叢集中其他機器的狀態資訊,因此每臺機器是會將自己作為被推舉的物件來進行投票。投票形式為(SID,ZXID)

我們假設 Zookeeper 有 5 臺機器組成,SID 分別是 1-5,ZXID 分別是 9,9,9,8,8,此時 SID 為 2 的機器是 Leader,並且機器 1 和 2 掛掉,於是 SID 為 3、4、5 的機器,投票情況分別是(3,9),(4,8),(5,8)

變更投票

叢集中的機器發出自己的投票後,也會收到來自叢集中其他機器的投票,每次對於收到的投票,都是將 (vote_sid, vote_zxid)和(self_sid, self_zxid)按照以下規則對比的過程

  • 如果 vote_zxid 大於 self_zxid,那就就認可當前收到的投票,並再次將該投票傳送出去
  • 如果 vote_zxid 小於 self_zxid,那麼就堅持自己的投票,不做任何變更
  • 如果 vote_zxid 等於 self_zxid,那麼就對比兩者的 SID,如果 vote_sid 大於 self_sid,那麼就認可當前接收到的投票,並再次將該投票傳送出去
  • 如果 vote_zxid 等於 self_zxid,並且 vote_sid 小於 self_sid,那就堅持自己的投票,不做變更

第一次投票後,每臺機器都把投票發出後,同時也會收到來自另外兩臺機器的投票

  • 對於 server3 來說,它接收到了(4,8)和(5,8)兩個投票,對比後,由於自己的 ZXID 要大於接收到的兩個投票,因此不需要做任何改變
  • 對於 server4 來說,它接收到了(3,9)和(5,8)兩個投票,對比後,由於(3,9)這個投票的 ZXID 大於自己,因此需要變更投票為(3,9),然後繼續將這個投票傳送給另外兩臺機器
  • 對於 server5 來說,它接收都了(3,9)和(4,8)兩個投票,對比後,由於(3,9)這個投票的 ZXID 大於自己,因此需要變更投票為(3,9),然後繼續將這個投票傳送給另外兩臺機器

確定 Leader

在經過第二次投票後,如果一臺機器收到了超過半數的相同的投票,那麼這個投票對應的 SID 機器即為 Leader

在上文變更投票後,server3 為 Leader

簡單來說,通常哪臺伺服器的資料越新,即 ZXID 越大,就越有可能成為 Leader

5.4 演算法實現

伺服器狀態

  • LOOKING:尋找 Leader 狀態
  • FOLLOWING:跟隨者狀態
  • LEADING:領導者狀態
  • OBSERVING:觀察者狀態

投票資料結構 Vote

  • id:被推舉的 Leader 的 SID 值
  • zxid:被推舉的 Leader 的事務 ID
  • electionEpoch:伺服器的選舉輪次
  • peerEpoch:被推舉的 伺服器的選舉輪次
  • state:當前伺服器的狀態

QuorumCnxManager :網路 IO

訊息佇列

QuorumCnxManager 內部維護了一系列的佇列,用來儲存接收到的,待發送的訊息以及訊息的傳送器,除接收佇列以外,其他佇列都安裝 SID 分組形成佇列集合

建立連線

為了能夠相互投票,Zookeeper 叢集中的所有機器都需要兩兩建立起網路連線。QuorumCnxManager 在啟動時會建立一個 ServerSocket 來監聽 Leader 選舉的通訊埠(預設為 3888 )。開啟監聽後,Zookeeper 能夠不斷地接收到來自其他伺服器的建立連線請求,在接收到其他伺服器的 TCP 連線請求時,會進行處理。

訊息接收與傳送

  • 訊息接收:由訊息接收器RecvWorker負責,由於Zookeeper為每個遠端伺服器都分配一個單獨的RecvWorker,因此,每個RecvWorker只需要不斷地從這個TCP連線中讀取訊息,並將其儲存到recvQueue佇列中。

  • 訊息傳送:由於Zookeeper為每個遠端伺服器都分配一個單獨的SendWorker,因此,每個SendWorker只需要不斷地從對應的訊息傳送佇列中獲取出一個訊息傳送即可,同時將這個訊息放入lastMessageSent中。

5.5 演算法核心

  • 外部投票:特指其他伺服器發來的投票
  • 內部投票:伺服器自身當前的投票
  • 選舉輪次:Zookeeper 伺服器 Leader 選舉的輪次,即 logicalclock
  • PK:指對內部投票和外部投票進行一個對比來確定是否需要變更內部投票
  • 選票管理:sendqueue 選票傳送佇列,用於儲存待發送的選票;recvqueue 選票接收佇列,用於儲存接收到的外部投票;WorkReceiver 選票接收器,從 QuorumCnxManager 中獲取其他伺服器的選舉資訊,並將其轉換成一個選票,然後儲存到 recvqueue 中;WorkerSender 選票傳送器,不斷地從 sendqueue 中獲取等待發送的選票,並將其傳遞到 QuorumCnxManager 中

演算法流程

  • 1、自增選舉輪次。Zookeeper 規定所有有效的投票都必須在同一個輪次中
  • 2、初始化選票。在開始新一輪投票前,每個伺服器都會初始化自身的選票,並且在初始化階段,每臺伺服器都會將自己推舉為 Leader
  • 3、傳送初始化選票。Zookeeper 會將剛剛初始化好的選票放入 sendquue 中,由傳送器 WorkerSender 負責傳送出去
  • 4、接收外部選票。每臺伺服器不斷地從 recvqueue 佇列中獲取外部選票
  • 5、判斷選舉輪次。如果伺服器自身的選舉輪次落後於該外部投票對應的伺服器的選舉輪次,那麼就會立即更新自己的選舉輪次,並清空所有已經收到的投票,然後使用初始化的投票來進行 PK;外部輪次小於內部輪次則不做處理,繼續接收外部選票;內外輪次相等時,則開始進行選票 PK
  • 6、選票 PK。若外部投票中推舉的 Leader 伺服器的選舉輪次大於內部投票,那麼需要變更投票;若選舉輪次一致,外部投票的 ZXID 大,則需要變更投票;若 ZXID 一致,外部投票的 SID 大,則需要變更投票
  • 7、變更投票。PK 後,若確定了外部投票優於內部投票,那麼就變更投票,即使用外部投票的選票資訊來覆蓋內部投票,變更完成後,再次將這個變更後的內部投票傳送出去。
  • 8、選票歸檔。無論是否變更了投票,都會將收到的那份外部投票放入選票集合 recvset 中進行歸檔,按照伺服器對應的 SID 來區分
  • 9、統計投票。為了統計叢集中是否已經有過半的伺服器認可了當前的內部投票,如果有則終止投票,沒有則返回步驟 4
  • 10、更新伺服器狀態。如果 Leader 是伺服器自己的話,更新為 LEADING,否則更新為 FOLLOWING 或者 OBSERVING

演算法流程圖

演算法與底層互動

6 高可用叢集

  • Zookeeper 叢集建議設計部署成奇數臺伺服器:N 與 N+1 容災能力上沒有顯著優勢
  • 三機房部署:假設叢集的機器總數為 N,那麼 N1=(N-1)/2,N2=1~(N-N1)/2,N3=N-N1-N2
  • 水平擴容:整個重啟和逐臺重啟

文章來源:https://gongfukangee.github.io/2018/10/05/Zookeeper-3/