注:內容很長,如有需要,請耐心閱讀。還有分享計劃。
Zookeeper 介紹
ZooKeeper是以Fast Paxos算法為基礎的,Paxos 算法存在活鎖的問題,即當有多個proposer交錯提交時,有可能互相排斥導致沒有一個proposer能提交成功,而Fast Paxos作了一些優化,通過選舉產生一個leader (領導者),只有leader才能提交proposer,具體算法可見Fast Paxos。因此,要想弄懂ZooKeeper首先得對Fast Paxos有所瞭解。
ZooKeeper的基本運轉流程:
1、選舉Leader。
2、同步數據。
3、選舉Leader過程中算法有很多,但要達到的選舉標準是一致的。
4、Leader要具有最高的執行ID,類似root權限。
5、集群中大多數的機器得到響應並接受選出的Leader。
- 工作機制
一致性協議
- CAP
CA - 單點集群,滿足一致性,可用性,通常在可拓展性上不太強大
CP - 滿足一致性,分區容錯性的系統,通常性能不是特別的高
AP - 滿足可用性,分區容錯性,通過對數據一致性要求低一些。
所以,分佈式系統考慮到集群的拓展,只能選擇CP 或者 AP 。
zookeeper 保證CP
服務註冊功能對可用性的要求高於一致性。
但是zk會出現這樣一種情況,當master節點因為網絡故障與其他節點失去聯繫時,剩餘節點回重新進行leader選舉,問題在於選舉leader的時間太長,30~120s且選舉期間整個zk集群都是不可用的。
Eureka 保證AP
在設計時就先保證可用性。Eureka各個節點都是平等的,幾個節點掛掉不會影響正常節點的工作,剩餘的節點依然可以提供註冊和查詢服務。
- Paxos
是能夠基於一大堆完全不可靠的網絡條件下卻能可靠確定地實現共識一致性的算法。
也就是說:它允許一組不一定可靠的處理器(服務器)在某些條件得到滿足情況下就能達成確定的安全的共識,如果條件不能滿足也確保這組處理器(服務器)保持一致。
Paxos 算法適用的幾種情況:一臺機器中多個進程/線程達成數據一致;分佈式文件系統或者分佈式數據庫中多客戶端併發讀寫數據;分佈式存儲中多個副本響應讀寫請求的一致性。
- ZAB協議(Zookeeper)
ZAB 協議全稱:Zookeeper Atomic Broadcast(Zookeeper 原子廣播協議)。
ZAB協議主要包含兩部分內容:
1、崩潰的數據恢復
2、消息的原子廣播
當整個集群正在啟動時,或者當leader節點出現網絡中斷、崩潰等情況時,ZAB協議就會進入恢復模式並選舉產生新的leader。
當leader服務器選舉出來後,並且集群中有過半的機器和該leader節點完成數據同步後,ZAB協議就會退出恢復模式。
當集群中已經有過半的Follower節點完成了和Leader狀態同步以後,那麼整個集群就進入了消息廣播模式。
在Leader節點正常工作時,啟動一臺新的服務器加入到集群,那這個服務器會直接進入數據恢復模式,和Leader節點進行數據同步。同步完成後即可正常對外提供非事務請求的處理。
同步指的是數據同步,用來保證集群中過半的機器能夠和leader服務器的數據狀態保持一致。
⑴消息的原子廣播
是一個簡化版的2PC提交過程。
Leader接收到消息請求後,將消息賦予一個全局唯一的64位自增id,叫: ZXID 。 ZXID由兩部分組成:低32位表示計數器(counter)和高32位的紀元號(epoch)。epoch為當前leader在成為leader的時候生成的,且保證會比前一個leader的epoch大(大1) 。
Leader為每個Follower準備了一個FIFO隊列(通過TCP協議來實現,以保證執行事務有序這一個特點)將帶有ZXID的消息作為一個提案(proposal)分發給所有的 Follower。
當Follower接收到proposal,先把proposal寫到磁盤,寫入成功以後再向Leader回覆一個ack。
當Leader接收到合法數量(超過半數節點)的ack後,Leader就會向這些Follower發送commit命令,同時會在本地執行該消息。
當Follower收到消息的commit命令以後,會提交該消息。
⑵崩潰的數據恢復
在ZAB協議中,為了保證程序的正確運行,整個恢復過程結束後需要選舉出一個新的Leader。並且要保證:不能多數據、不能丟數據。
ZAB協議需要滿足上面兩種情況,就必須要設計一個Leader選舉算法,能夠確保已經被Leader提交的事務proposal能夠提交、同時丟棄已經被跳過的事務proposal。
恢復模式大致可以分為四個階段
1、選舉
當Leader崩潰後,集群進入選舉階段,開始選舉出潛在的新Leader(一般為集群中擁有最大ZXID的節點,因為這樣就可以保證這個新選舉出來的Leader一定具有已經提交的提案,提案被commit之前必須有超過半數的Follower ack,即必須有超過半數節點的服務器的事務日誌上有該提案的proposal,因此只要有合法數量的節點正常工作,就必然有一個節點保存了所有被commit消息的proposal狀態)。
2、發現
進入發現階段,Follower與潛在的新Leader進行溝通,如果發現超過法定人數的Follower同意,則潛在的新Leader將epoch加1,進入新的紀元(epoch)。新的Leader產生。
3、同步
集群間進行數據同步,保證集群中各個節點的事務一致。
4、廣播
集群恢復到廣播模式,開始接受客戶端的寫請求。
當Leader產生某個proposal之後但在發出消息之前宕機,即只有老Leader自己有這個proposal,當老的leader重啟後(此時左右Follower),新的Leader必須保證老的Leader必須丟棄這個proposal.(新的Leader會通知上線後的老Leader截斷其epoch對應的最後一個commit的位置)。
基礎知識
- 集群角色
Zookeeper集群中主要有三種角色:Leader、Follower、Observer。Leader負責接收客戶端的寫請求,並將該請求以事務的方式提交給Follower執行。Follower負責接收讀請求、參與Leader的選舉和過半寫成功策略。Observer也負責接收讀請求,不需要參與Leader的選舉和過半寫成功,該角色設計的主要目標是用來在不影響寫性能的前提下擴展Zookeeper的讀性能。
- 會話
會話指的是客戶端和Zookeeper集群建立的連接,假設與客戶端相連的服務器宕機,在沒有超過sessionTimeout參數設置的前提下能夠重新連接上另一臺服務器,則之前的會話有效。
- 數據節點
這裡的數據節點除了機器節點之外,指的還是Zookeeper中的ZNode,ZNode以文件目錄的形式進行組織。主要有兩種形式:持久節點和臨時節點。持久節點一旦創建除非手動移除否則不會刪除,臨時節點和會話的生命週期有關,會話啟動創建的臨時節點會在會話斷開時自動刪除。Zookeeper還可以為節點設置SEQUENTIAL屬性,被設置了該屬性的節點在創建時會自動在節點名後面加一個數字,該數字是由父節點維護的。
zookeeper數據模型的結構與unix文件系統類似,整體上可以看作是一棵樹,每個節點稱作一個znode
znode的數據模型:
zookeeper的Stat結構體
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 2] create /leo v1.0
Created /leo
[zk: localhost:2181(CONNECTED) 3] ls /
[leo, zookeeper]
[zk: localhost:2181(CONNECTED) 4] get /leo
v1.0
[zk: localhost:2181(CONNECTED) 6] get -s /leo
v1.0
# 引起這個znode創建的zxid,創建節點的事物的zxid
# 每次修改zookeeper狀態都會收到一個zxid形式的時間戳,也就是zookeeper事物id
# 如果zxid1 < zxid2,那麼zxid1在zxid2之前發生
cZxid = 0x6
ctime = Mon Sep 16 14:43:30 CST 2019 #被創建時間
mZxid = 0x6 #znode最後更新的zxid
mtime = Mon Sep 16 14:43:30 CST 2019 #znode最後更新的時間
pZxid = 0x6 #最後更新的子節點zxid
cversion = 0 #znode子節點變化號,znode子節點修改次數
dataVersion = 0 #znode數據變化號
aclVersion = 0 #znode訪問控制列表的變化號
ephemeralOwner = 0x0 #如果是臨時節點,這個是znode擁有者的session id,如果不是臨時節點則是0
dataLength = 4 # znode數據長度 length("v1.0")
numChildren = 0 # znode子節點個數
[zk: localhost:2181(CONNECTED) 7] get -w /leo
v1.0
Zookeeper應用場景
- 數據發佈與訂閱
發佈與訂閱模型,即所謂的配置中心,顧名思義就是發佈者將數據發佈到ZK節點上,供訂閱者動態獲取數據,實現配置信息的集中式管理和動態更新。例如全局的配置信息,服務式服務框架的服務地址列表等就非常適合使用。
應用中用到的一些配置信息放到ZK上進行集中管理。
這類場景通常是這樣:應用在啟動的時候會主動來獲取一次配置,同時,在節點上註冊一個Watcher,這樣一來,以後每次配置有更新的時候,都會實時通知到訂閱的客戶端,從來達到獲取最新配置信息的目的。 分佈式搜索服務中,索引的元信息和服務器集群機器的節點狀態存放在ZK的一些指定節點,供各個客戶端訂閱使用。
- 負載均衡
指軟負載均衡。在分佈式環境中,為了保證高可用性,通常同一個應用或同一個服務的提供方都會部署多份,達到對等服務。而消費者就須要在這些對等的服務器中選擇一個來執行相關的業務邏輯,其中比較典型的是消息中間件中的生產者,消費者負載均衡。
消息中間件中發佈者和訂閱者的負載均衡,linkedin開源的KafkaMQ和阿里開源的metaq都是通過zookeeper來做到生產者、消費者的負載均衡。
以metaq為例: 生產者負載均衡:metaq發送消息的時候,生產者在發送消息的時候必須選擇一臺broker上的一個分區來發送消息,因此metaq在運行過程中,會把所有broker和對應的分區信息全部註冊到ZK指定節點上,默認的策略是一個依次輪詢的過程,生產者在通過ZK獲取分區列表之後,會按照brokerId和partition的順序排列組織成一個有序的分區列表,發送的時候按照從頭到尾循環往復的方式選擇一個分區來發送消息。
消費負載均衡:
在消費過程中,一個消費者會消費一個或多個分區中的消息,但是一個分區只會由一個消費者來消費。MetaQ的消費策略是:
每個分區針對同一個group只掛載一個消費者。 如果同一個group的消費者數目大於分區數目,則多出來的消費者將不參與消費。
* 如果同一個group的消費者數目小於分區數目,則有部分消費者需要額外承擔消費任務。
在某個消費者故障或者重啟等情況下,其他消費者會感知到這一變化(通過 zookeeper watch消費者列表),然後重新進行負載均衡,保證所有的分區都有消費者進行消費。
- 命名服務
是分佈式系統中比較常見的一類場景。
在分佈式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等信息。被命名的實體通常可以是集群中的機器,提供的服務地址,遠程對象等等——這些我們都可以統稱他們為名字(Name)。其中較為常見的就是一些分佈式服務框架中的服務地址列表。通過調用ZK提供的創建節點的API,能夠很容易創建一個全局唯一的path,這個path就可以作為一個名稱。
阿里巴巴集團開源的分佈式服務框架Dubbo中使用ZooKeeper來作為其命名服務,維護全局的服務地址列表。
在Dubbo實現中: 服務提供者在啟動的時候,向ZK上的指定節點:
/dubbo/${serviceName}/providers目錄下寫入自己的URL地址,這個操作就完成了服務的發佈。 服務消費者啟動的時候,訂閱/dubbo/${serviceName}/providers目錄下的提供者URL地址, 並向/dubbo/${serviceName} /consumers目錄下寫入自己的URL地址。
注意,所有向ZK上註冊的地址都是臨時節點,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化。
- 分佈式通知/協調
ZooKeeper中特有watcher註冊與異步通知機制,能夠很好的實現分佈式環境下不同系統之間的通知與協調,實現對數據變更的實時處理。使用方法通常是不同系統都對ZK上同一個znode進行註冊,監聽znode的變化(包括znode本身內容及子節點的),其中一個系統update了znode,那麼另一個系統能夠收到通知,並作出相應處理
另一種心跳檢測機制:檢測系統和被檢測系統之間並不直接關聯起來,而是通過zk上某個節點關聯,大大減少系統耦合。 另一種系統調度模式:某系統有控制檯和推送系統兩部分組成,控制檯的職責是控制推送系統進行相應的推送工作。管理人員在控制檯作的一些操作,實際上是修改了ZK上某些節點的狀態,而ZK就把這些變化通知給他們註冊Watcher的客戶端,即推送系統,於是,作出相應的推送任務。
* 另一種工作彙報模式:一些類似於任務分發系統,子任務啟動後,到zk來註冊一個臨時節點,並且定時將自己的進度進行彙報(將進度寫回這個臨時節點),這樣任務管理者就能夠實時知道任務進度。
總之,使用zookeeper來進行分佈式通知和協調能夠大大降低系統之間的耦合
- 集群管理與Master選舉
集群機器監控:這通常用於那種對集群中機器狀態,機器在線率有較高要求的場景,能夠快速對集群中機器變化作出響應。這樣的場景中,往往有一個監控系統,實時檢測集群機器是否存活。過去的做法通常是:監控系統通過某種手段(比如ping)定時檢測每個機器,或者每個機器自己定時向監控系統彙報“我還活著”。 這種做法可行,但是存在兩個比較明顯的問題:
1. 集群中機器有變動的時候,牽連修改的東西比較多。
2. 有一定的延時。
利用ZooKeeper有兩個特性,就可以實時另一種集群機器存活性監控系統:
1. 客戶端在節點 x 上註冊一個Watcher,那麼如果 x?的子節點變化了,會通知該客戶端。
2. 創建EPHEMERAL類型的節點,一旦客戶端和服務器的會話結束或過期,那麼該節點就會消失。
- 分佈式鎖
分佈式鎖,這個主要得益於ZooKeeper為我們保證了數據的強一致性。鎖服務可以分為兩類,一個是保持獨佔,另一個是控制時序。
所謂保持獨佔,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。通常的做法是把zk上的一個znode看作是一把鎖,通過create znode的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。 控制時序,就是所有視圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。做法和上面基本類似,只是這裡 /distribute_lock 已經預先存在,客戶端在它下面創建臨時有序節點(這個可以通過節點的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL來指定)。Zk的父節點(/distribute_lock)維持一份sequence,保證子節點創建的時序性,從而也形成了每個客戶端的全局時序。
- 分佈式隊列
隊列方面,簡單地講有兩種,一種是常規的先進先出隊列,另一種是要等到隊列成員聚齊之後的才統一按序執行。對於第一種先進先出隊列,和分佈式鎖服務中的控制時序場景基本原理一致,這裡不再贅述。
第二種隊列其實是在FIFO隊列的基礎上作了一個增強。通常可以在 /queue 這個znode下預先建立一個/queue/num 節點,並且賦值為n(或者直接給/queue賦值n),表示隊列大小,之後每次有隊列成員加入後,就判斷下是否已經到達隊列大小,決定是否可以開始執行了。這種用法的典型場景是,分佈式環境中,一個大任務Task A,需要在很多子任務完成(或條件就緒)情況下才能進行。這個時候,凡是其中一個子任務完成(就緒),那麼就去 /taskList 下建立自己的臨時時序節點(CreateMode.EPHEMERAL_SEQUENTIAL),當 /taskList 發現自己下面的子節點滿足指定個數,就可以進行下一步按序進行處理了。
Zookeeper監聽器原理
- 要有一個 main() 線程;
- 在 main() 線程中創建 Zookeeper 客戶端,這時就會創建兩個線程,一個負責 網絡連接通信(connet) , 一個負責監聽(listener) .
- 通過 connet 線程將註冊的監聽事件發送給 Zookeeper.
- 在 Zookeeper 的註冊監聽器列表中將註冊的監聽事件添加到列表中 .
- Zookeeper 監聽到有數據或路徑變化,就會將這個消息發送到 listener 線程 .
- listener 線程內部調用了 process() 方法 .
Zookeeper安裝方法
01 單機
- 下載並解壓zookeeper
# cd /usr/local
# wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.12.tar.gz
# tar -zxvf zookeeper-3.4.12.tar.gz
# cd zookeeper-3.4.12
- 重命名配置文件zoo_sample.cfg
# cp conf/zoo_sample.cfg conf.cfg
- 啟動zookeeper
# bin/zkServer.sh start
- 檢測是否成功啟動,用zookeeper客戶端連接下服務端
檢測是否成功啟動,用zookeeper客戶端連接下服務端
02 集群
三臺機器上,分別建立 zk目錄
機器1 zookeeper 目錄為:zk
機器2 zookeeper 目錄為:zk2
機器3 zookeeper 目錄為:zk3
三臺機器ip分別為
xx.xx.xx.1
xx.xx.xx.2
xx.xx.xx.3
- 主機名到IP地址的映射配置
直接修改/etc/hosts文件,設置主機zoo-1映射到x.x.x.1,設置主機zoo-2映射到x.x.x.2,設置主機zoo-3映射到x.x.x.3
vim /etc/hosts
x.x.x.1 zoo-1
x.x.x.2 zoo-2
x.x.x.3 zoo-3
- 修改zoo.cfg配置
vi zoo.cfg
#增加配置
server.1=zoo-1:2888:3888
server.2=zoo-2:2888:3888
server.3=zoo-3:2888:3888
- 新建myid文件並寫入集群標識
在dataDir目錄中,創建一個名為myid的文件,並寫入機器對應的數字值,比如我們是在zk的機器上,就將該值設置為1,即集群中sever.1=zoo-1:2888:3888中server.後對應的數字。這是zookeeper用來識別是那一臺集群機器的標識。
echo 1 > /opt/zk/zk/data/myid
echo 2 > /opt/zk/zk2/data/myid
echo 3 > /opt/zk/zk3/data/myid
- 查看集群狀態配置結束
分別啟動機器1,機器2,機器3.
集群會自動選舉出一臺服務器作為leader,其餘服務器為follower,可以在每臺服務器上使用命令查看服務器是什麼類型的。
zkServer.sh status
下篇分享:
Zookeeper kafka redis 整合及環境搭建
閱讀更多 天天面試題 的文章