博客 / 詳情

返回

百萬架構師第三十課:協調服務-zookeeper:瞭解zookeeper的核心原理|JavaGuide

原文鏈接
  1. 瞭解zookeeper及zookeeper的設計猜想
  2. Zookeeper集羣角色
  3. 深入分析ZAB協議
  4. 從源碼層面分析leader選舉的實現過程
  5. 關於zookeeper的數據存儲

回顧內容

  1. zookeeper集羣安裝(myid/zoo.cfg)
  2. zookeeper的數據模型(znode)
  3. 節點的特性

(持久化、臨時節點、有序節點、同級節點必須唯一、臨時節點不能存在子節點)

  1. 節點的status信息
  2. 簡單地瞭解了watcher機制
  3. Zookeeper的應用場景

Zookeeper的由來

JavaGuide_Zookeeper_核心原理_由來.png

分佈式架構下面臨的問題

我們有一個 orderservice 服務

我們對同一個應用去做拷貝,相當於是部署了三個一模一樣的應用實例,組成一個集羣。每個模塊中運行一個 Task 任務。

因為我們是一個集羣,所以我們每一個節點都有權限去執行這個任務。

我們每一個服務都會有一個配置文件,application.properties,配置文件中會配置一些數據庫的信息,連接信息,服務地址信息等等,我們可能有些東西是需要動態變更的,這是一個背景,然後這些數據需要進行調整的話,因為現在是一個集羣,所以現在必須在同一時刻,對這些數據進行一個更新,但是如果你是保存在配置文件裏邊的話,你怎麼保證各個節點的數據保持一致。

我們現在一個server執行一個任務,假如説 ,這個任務在server1上執行,怎麼控制這個任務只在server1上執行。假如説我們有一個定時任務解析文件去入庫,我們怎麼只讓其中一台機子去解析這個文件,其他機子不需要解析。因為如果三台機子都做這個任務的話,那麼意味着數據要做三次。

如果你有一個手段讓一個任務只在一個server上執行,如果其中一個服務掛掉了以後,其他節點怎麼知道它掛掉了,去重新接替任務。

我們如果説存在一個共享資源,集羣對每一個節點都是公平的,所以存在某一個時刻,很多個服務去訪問同一個文件。在訪問的時候怎麼去保證互斥性,就像多線程裏邊,多個線程同時訪問同一塊資源,如果保證線程資源的安全性。

集中式到分佈式架構發展帶來的問題:

  1. 各個數據節點的數據一致
  2. 怎麼保證任務只在一個節點上執行。
  3. 如果某個節點掛了,其他節點如果發現並接替執行任務。
  4. 存在共享資源的場景。互斥性、安全性

總結一下,就是缺少一個分佈式的協調機制。

分佈式協調做得比較好的一個是

  1. 谷歌的chubby
  2. Apache的zookeeper

谷歌的chubby是一個分佈式鎖的東西,

解決分佈式鎖、master選舉相關的服務

雅虎公司內部很多模塊,它需要依賴一個系統,去對裏邊的分佈式系統去做一個協調,然而Google的chubby不開源。 所以雅虎基於chubby的思想開發了zookeeper,捐獻給Apache,所以現在我們下載的zookeeper是從Apache上下載下來的。

這就是我們為什麼要用zookeeper機制的原因 。

JavaGuide_Zookeeper_核心原理_目錄特性結構.png

Zookeeper,是一個文件結構的樹形結構的數據存儲,我們基於zookeeper本身的數據結構和它的節點特性,我們可以利用它去實現一個,互斥的訪問

那麼我們就需要先去註冊一個結點,因為zookeeper節點是一個樹形結構

我們把zookeeper中最小的那個節點有優先權,第一個服務去執行,後邊的兩個就不讓它去執行。我們利用zookeeper起到對我們相關服務節點的協調控制。

如果我們的zookeeper作為我們整個系統的服務協調中心的話,那麼zookeeper就會成為一個瓶頸,而zookeeper目標是提供一個高性能,高可用的,有嚴格訪問順序的控制能力(表示寫操作的順序)分佈式協調服務。

初衷

基於zookeeper的初衷,我們要解決zookeeper的性能問題。

還有它本身的可用性,我們不能讓它本身成為一個單點。如果它是一個單點,如果它掛了,那麼相應的節點無法執行相應的操作。

Zookeeper的設計猜想:

  1. 防止單點故障

集羣方案(leader、follower)、還能分擔請求

  1. 每個節點的數據是一致的(必須要有leader)

​ Leader、master; redis-cluster

  1. Leader掛了怎麼辦?數據如何恢復

​ 選舉機制?數據如何恢復

  1. 如何去保證數據一致性?(分佈式事務)

​ 一開始單體架構中,CAP一致性是由一個事務去統一控制和管理的,現在一個請求,分到了不同的服務器,但是我還需要保證在整個集羣是一致的。

​ 每個節點只能保證內部的一致性,我們要保證整體的一致性,zookeeper就引入了改進版的2PC協議去完成2階提交

2PC 二階段提交協議

Two Phase Commitment Protocol

當一個事務涉及多個節點去提交的時候,為了保證事務處理的ACI的特性的話,在2PC中引入了一個概念叫協調者,通過一個協調者來控制各個節點事務的執行邏輯。

JavaGuide_Zookeeper_核心原理_協調者_參與者.png

如果參與者1與參與者2中的數據需要都執行成功,才算一次完整的成功。

在單體架構中直接通過transactional去直接控制事務的一致性。

在分佈式系統中引入一個協調者,協調者先對每一個參與這個事務的參與者提交一次請求,每個參與者收到請求後,會給一個迴應,是不是能夠執行這個事務,給個“是”,表示可以執行這次事務,如果每一個參與者都回應一個是,那麼就表示這個請求是可以被執行成功的。 這時候協調者就對每一個參與者進行一次commit,然後提交完成以後給一個ack的二階提交的響應。

假如某一個參與者第一次迴應了一個“否”,就意味着整體要全部失敗,事務要保證原子性,要麼全部成功,要麼全部失敗。那麼對所有的節點發起的是一個rollback操作。這就是一個所謂的二階段提交的概念。

而zookeeper裏邊是基於二階提交的方式去做數據同步的

結論:

  1. 為什麼用ZAB實現選舉(基於proposal思想的,zookeeper裏邊的協議,解決數據一致性的問題)
  2. 為什麼要做集羣
  3. 為什麼通過2PC做數據一致性

Zookeeper的集羣

我們現在有一個客户端,對zookeeper集羣做出了一個處理

Zookeeper對外有一個集羣,客户端進行連接這個集羣時,會隨機連接某一個節點。

如果當前的請求是讀請求,我們的請求可以落在任意一個節點去讀取數據。

如果是寫請求,那麼這個請求會轉發給leader去處理

我們説過了,zookeeper是基於一個2PC的方式進行的一個事務的提交

如果我們的一個寫請求到了一個follower節點上,它會轉發到leader節點上,然後leader節點會發起一個提議,會把事務發給集羣中的每一個節點,這時候的follower節點要給leader節點一個ack,這個ack表示當前的這個節點是不是能夠執行這個事務,leader一旦發現過半的請求是同意的,我會提交這個事務,然後就返回response。

這就是改進版的2PC的一個事務。然後提交事務以後,數據會同步給observer。

JavaGuide_Zookeeper_核心原理_協調者_Client_Leader_Follower_Observer.png

Leader是整個zookeeper集羣的核心,起到了主導zookeeper集羣的一個作用,比如説我們一個事務請求的一個調度和處理,保證我們集羣中事務處理的一個順序性。

Follower角色主要是用來處理客户端的非事務請求,以及轉發事務請求給leader服務器,參與整個事務的投票過程,叫proposal,我們這條數據要保存到zookeeper集羣裏邊,必須要有過半的節點同意。

Observer是一個觀察者的角色,相當於我能夠了解集羣中相關節點的相關變化並且對狀態進行同步, observer不參與事務請求的投票。

如果我們想要集羣的性能更高,我們肯定想要引入更多的節點,節點越來越多,性能提升了,投票的時候會變慢,不影響整體寫性能的情況下,引入observer,提升整體的性能。

所有節點的數量必須是2N+1,我們必須保證有2N+1個節點,叫基礎節點,zookeeper集羣的工作機制,是必須有半數以上節點能夠正常地工作,並且能夠參與到投票機制,如果投票不能過半的話,我們的投票是沒有結果的。

ZAB協議

​ (基於Proposal協議衍生出來的一種算法)

ZAB協議是zookeeper裏邊專門設計的針對崩潰恢復的原子廣播協議,

主要用來實現數據一致性

Zookeeper是一種類似於主備的形式,leader掛了,follower上能夠選舉出一個leader來,代替上一個leader上的服務,通過ZAB協議保證各個節點上數據的一致性,為了保證主備數據的一致性,就需要實現ZAB協議。

  • 崩潰恢復
  • 原子廣播

當我們第一次啓動集羣的時候或者leader集羣崩潰的時候,這時候ZAB協議就會產生作用,ZAB協議會進入恢復模式,它會從宕掉的集羣裏邊重新去選舉一個leader,並且當新的leader選舉出來以後,當集羣中過半的機器和新選舉的leader完成了數據同步以後,整個集羣就進入了一個正常運行的狀態,就進入了一個原子廣播的階段。

消息廣播:(事務提交)

JavaGuide_Zookeeper_核心原理_Zxid消息_原理.png

​ 改進版本的2PC

首先當leader收到一個事務請求的時候會生成一個zxid(64位的自增ID)

  1. 它會對每一個請求分配一個zxid,

通過zxid的大小可以實現數據的因果有序

Leader對每一個follower都會準備一個IFIO隊列,這個是通過TCP協議來實現的,

  1. 它會把帶有zxid的消息作為一個proposal(提案),分發到集羣中的每一個follower節點
  2. 每一個follower節點收到請求以後,它會把propose這個事務寫入到磁盤,返回ack給leader
  3. Leader收到合法數量的請求ack,過半的請求數,同意以後,再發起commit請求

整個事務的過程就是一個消息廣播的過程。

Leader的投票過程和事務的投票過程中不需要observer,但是observer必須保證他的數據和leader的數據是保持一致的

崩潰恢復(對數據層來説)

  1. 當leader失去了過半的follower節點的聯繫
  2. 當leader服務掛了

整個集羣就會進入崩潰恢復階段,因為我們必須保證leader掛了之後整個集羣還可用。

對於數據恢復來説:

  1. 已經被處理的消息不能丟失

JavaGuide_Zookeeper_核心原理_如何保證消息不丟失.png

​ 當leader收到合法數量的follower的ack以後,就會向各個follower廣播消息(commit命令),同時本地自己也會commit這條事務消息。如果follower節點收到commit命令之前,Leader掛了。會導致部分節點收到commit,部分節點沒有收到commit。ZAB協議需要保證已經處理的消息不能丟失。

  1. 被丟棄的消息不能再次出現

JavaGuide_Zookeeper_核心原理_被丟棄的消息不能再出現.png

​ 當leader請求收到事務請求,並且還未發起事務投票之前,leader掛了。

下一個leaser,要跳過這個請求。

我們的leader掛掉以後,我們不僅需要重新選舉leader,還要恢復我們的數據。

ZAB的設計思想

  1. zxid是最大的? 保證已經提交的proposal一定會被提交!!不會出現數據丟失。
  2. epoch的概念,每產生一個新的leader,那麼新的leader的epoch會+1

​ zxid是64位的數據,

低32位表示消息計數器(自增的),高32位(epoch編號)

如果我們新的選舉出來的leader,就意味着它的epoch比老的epoch高,它的每一個事務id 都會帶上epoch編號,和消息計數器。這樣設計以後,當我們重新選舉leader以後,消息會重新從0開始,而epoch是老的加上一,好處是:老的服務器重新啓動以後,它不會再成為leader,就是在這一輪裏邊不會在成為leader,並且老的leader,變成follower,加入到集羣以後,它的zxid一定會小於新的zxid,那麼新的leader,會把他所有沒有提交的事務都會清除。

epoch可以理解為年號,皇帝的年號。

實操

[root@Darian1 bin]# ls /tmp/zookeeper/
myid  version-2  zookeeper_server.pid
[root@Darian1 bin]# ls /tmp/zookeeper/version-2/
acceptedEpoch  currentEpoch  log.1  log.100000001  log.200000001
[root@Darian1 bin]# vim /tmp/zookeeper/version-2/currentEpoch 

2
~  
# ls /tmp/zookeeper/

# ls /tmp/zookeeper/version-2/

acceptedEpoch cuttentEpoch log.1 log.1000000001 log.3 log.a00000001
 # vim /tmp/zookeeper/version-2/currentEpoch

可以看到epoch

:q!

Epoch

我們去關閉leader

[root@Darian1 bin]# sh zkServer.sh stop

重新看到epoch

可以看到加了一

Epoch加一,可以保證把以前沒有提交的proposal丟棄掉,

[root@Darian1 bin]# ls /tmp/zookeeper/version-2 

查看日誌內容

[root@Darian1 bin]# java -cp :/zookeeper/zookeeper-3.4.10/lib/slf4j-api-1.6.1.jar:/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.jar org.apache.zookeeper.server.LogFormatter /tmp/zookeeper/version-2/log.1

 

路徑:java –cp :/slf4j.jar:/zookeeper.jar  org.apache.zookeeper.server.LogFormatter /tmp/zookeeper/version-2/log.1

avaGuide_Zookeeper_核心原理_Zxid的日誌.png

server.1=192.168.136.128:2888:3888
server.2=192.168.136.129:2888:3888
server.3=192.168.136.130:2888:3888

理解ZAB協議

JavaGuide_Zookeeper_核心原理_理解Zab協議.png

假設leader中有三個事務請求

zxid
P1 01
P2 02
P3 03

它會把這個事務分發給每一個節點去做提交

假設

Follower1 收到了 p1 01
Follower2 收到了 p1 01

其他請求還沒有發起,

這時候leader掛了,

這時候我們假設follower1變成了leader

這時候新的leader發起了一個新的事務請求,

[p2] -10

(代表epoch 0 代表消息數)

這個請求同步到follower上 叫 [p2] -10

數據恢復

整個集羣裏邊所有的參與者follower節點都要確定事務日誌裏邊的Propress已經被過半的節點提交過了。

Leader會為每一個follower節點準備一個FIFO的隊列,把各個follower節點沒有被提交的請求,以事務的方式逐步發給其他節點,但是,如果事務是失效的,過期的,它會被丟棄掉。

Leader選舉

兩種情況會做leader選舉,

一個是集羣啓動,一個是崩潰恢復

Fast Leader 基於fast leader做選舉的

Zxid最大會設置為leader【事務id,事務id越大,那麼表示數據越新】

​ 64位, 000000000000000001(epoch) 0000000000000000000010

Myid(服務器id,sid)【myid越大,在leader選舉機制中權重越大】

epoch【邏輯時鐘】【每一輪投票,epoch都會遞增】

選舉狀態(LOOKING)

  • LEADING
  • FOLLOWING
  • OBSERVING

理論

啓動的時候初始化,每一個節點會選舉自己作為一個leader,把當前節點的這些信息發佈給集羣中的每一個節點。

(myid, zxid, epoch)

每一個節點會收到這些信息,然後去做投票。投票過程中會比較對應的數據記錄結果。

1 檢查zxid zxid大的節點直接為leader
2 myid myid比較大的會作為leader
3 投票完了以後 會去統計票數,根據投票結果,確定leader選舉的結果。
4 統計投票 每次投票後,服務器都會統計投票信息,判斷是否已經有過半機器接收到相同的投票信息,此時便認為已經選出了Leader

如果運行過程中的選舉的話,leader掛掉了,它可以重新去選舉leader,第一個它會去變更狀態,把剩下的所有節點都變成一個LOOKING狀態去重新做一個監控,監控其他的節點去重新做一個選舉。 接下來步驟跟初始化的都一樣了………

zookeeper 的代碼入口搜索 QuorumPeerMain

FastLeaderElection 類中 的 lookForLeader投票方法的實現機制

JavaGuide_Zookeeper_核心原理_Leader_Looking_日誌.png

投票流程

JavaGuide_Zookeeper_核心原理_投票的過程.png

  1. 判斷epoch
  2. Zxid
  3. 再判斷myid

高性能和高可用的集羣

熱備的集羣

來源於: https://javaguide.net

公眾號:不止極客

user avatar fulng 頭像 yaoyaolx_wiki 頭像 91cyz 頭像 tengteng_5c7902af4b01e 頭像 lianhuatongzina 頭像 mysteryjack 頭像 yexiaobai_616e2b70869ae 頭像
7 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.