分散式協調服務
一、Zookeeper使用場景
適合讀多寫少的場景
-
統一命名服務
-
統一配置管理
-
分散式叢集管理(註冊中心)
-
分散式鎖
-
負載均衡
二、 Zookeeper內部結構
zookeeper節點
類似於Unix檔案系統
每個子目錄項(路徑) 都被稱作為znode,和檔案系統一樣,我們能夠自由的增加、刪除znode,在一個znode下增加、刪除子znode,唯一的不同在於znode是可以儲存資料的。
ZooKeeper資料模型中的每個znode都維護著一個stat結構,提供後設資料,它由版本號,操作控制列表(ACL),時間戳和資料長度組成。
-
版本號 每當與znode相關聯的資料發生變化時,其對應的版本號也會增加
-
操作控制列表(ACL) ACL基本上是訪問znode的認證機制。它管理所有znode讀取和寫入操作
-
時間戳 時間戳表示建立和修改znode所經過的時間,ZooKeeper從“事務ID"(zxid)標識znode的每個更改
-
資料長度 最多儲存1M資料
節點型別
- 持久化有序
- 持久化無序
- 臨時有序節點
- 臨時無序節點
三、Zookeeper提供的原語服務
- create /path data:建立一個名為/path的znode節點,幷包含data資料(不包含建立失敗)
- delete /path:刪除名為/path的znode
- exists /path:檢查是否存在名為/path的節點
- setData /path data:設定名為/path的znode的資料為data
- getData /path:返回名為/path節點的資料
- getChildren /path:返回/path節點的子節點列表
四、Zookeeper分散式鎖
思路:- 客戶端client_1嘗試獲取鎖,呼叫create()方法在locker節點下建立臨時順序節點node_1
- 客戶端呼叫getChildren("/locker")來獲取locker節點下所有位元組點,同時在這個節點上註冊上子節點變更通知的Watcher
- 如果發現自己在步驟1中建立的節點是所有節點中序號最小的,那麼就認為這個客戶端獲得了鎖
- 在步驟3中發現自己並非是所有子節點中最小的,說明自己還沒有獲取到鎖,就開始等待,直到下次子節點變更通知的時候,再進行子節點的獲取,判斷是否獲取鎖
- 釋放鎖就是刪除自己建立的那個子節點即可
上面的過程有問題:
首先在分散式鎖中,會有很多客戶端來嘗試獲取鎖,他們在判斷自己節點獲取的結果都是自己不是最小節點,在最小節點被刪除時,客戶端會受到大量無效通知——羊群效應
我們的關注點是:每個節點只關注比自己序號小的那個節點的狀態。
改進分散式鎖:
-
客戶端client_1嘗試獲取鎖,呼叫create()方法在locker節點下建立臨時順序節點node_1
-
客戶端呼叫getChildren("/locker")來獲取locker節點下所有位元組點,這裡向比它靠前節點註冊Watcher;
-
如果發現自己在步驟1中建立的節點是所有節點中序號最小的,那麼就認為這個客戶端獲得了鎖
-
在步驟3中發現自己並非是所有子節點中最小的,說明自己還沒有獲取到鎖,就開始等待,直到下次子節點變更通知的時候,再進行子節點的獲取,判斷是否獲取鎖
-
最終會形成Client_1得到了鎖,Client_2監聽了node1,Client_3監聽了node2,形成一個等待佇列,有點類似AQS
-
釋放鎖就是刪除自己建立的那個子節點即可,並且只有相鄰節點會收到通知,判斷自己是不是最小,獲取鎖。
Zookeeper作為分散式鎖非常容易實現,但是增刪節點效率偏低。
五、Zookeeper分散式叢集
1. Zookeeper的角色:
- 領導者(Leader),負責進行投票的發起和決議,更新系統狀態
- 學習者(Learner),包括跟隨者(Follower)和觀察者(observer):
Follower用於接受客戶端請求並想客戶端返回結果,在選主過程中參與投票,Observer可以接受客戶端連線,將寫請求轉發給Leader,但observer不參加投票過程,只同步Leader的狀態,observer的目的是為了擴充套件系統,提高讀取速度
2. Zookeeper下Server的工作狀態
Looking :選舉狀態
Following :Leader已經選舉產生,當前Server與之同步
Leading :主節點所處的狀態
3. Zookeeper工作原理
Zookeeper核心:原子廣播,通過Zab協議實現
ZAB協議兩種模式:訊息廣播和崩潰恢復
訊息廣播:
-
在Client向Follwer發出一個寫的請求
-
Follwer把請求傳送轉發給Leader
-
Leader接收到以後開始發起投票並通知Follwer進行投票, 注:Observer不參與
-
Follwer把投票結果傳送給Leader
-
Leader將結果彙總,廣播通知所有Follower寫入資料,
-
Follower寫入完成後,同時把寫入操作通過ACK訊息通知給Leader,Leader收到半數以上成功ACK(這裡會造成Zookeeper讀資料的時候,資料不一致,半數以上為新資料,半數以下存在老資料,所以在讀資料是需要呼叫sync()同步方法),進行commit,並進行廣播;
注:對於zookeeper來說,它實現了A可用性、P分割槽容錯性、C中的寫入強一致性,喪失的是C中的讀取一致性 -
Follwer把請求結果返回給Client
Zookeeper為保持事務的順序性,採用遞增的zxid標識每個每個提議,zxid是64位數字,高32位epoch標識Leader狀態,就是Leader統治狀態,每次選出新的Leader同時就會產生新的epoch,低32位用來自增。
Zookeeper選主過程:
Zookeeper第一次進行選主過程:
Zookeeper叢集有5個Server
投票過程:server1連入Zookeeper,投票給自己,成為Leader,Server2連入,投票給自己和Server1,發現Server2的zxid較新,Server2當選成為新Leader
Server3連入,重複上面過程,成為Leader,這時有超過半數以上Server投票給Server3,Server3當選成為Leader,Server4,5連入,就算zxid最新,也不會當選成為Leader。
崩潰恢復:
這個過程很複雜,以下是個人理解:
Zookeeper選舉演算法有兩種模式:
- Basic Paxos
- Fast Paxos
預設使用Fast Paxos.
選舉過程:
-
選舉執行緒由當前Server發起選舉的執行緒擔任,主要功能是對投票結果進行統計,並選出推薦的Server
-
選舉階段,所有叢集的節點處於Looking狀態,它們會向其它節點發起投票,投票內容包含(伺服器ID, zxid).
-
該節點同時會收到其它節點的投票,它會將自身zxid和其它zxid比較,選擇最新的zxid,重新發起投票.
-
發起選舉的執行緒統計票數,判斷過半獲得投票的節點,並將它設定為Leader,狀態變為Leading,其它節點狀態變為Following
-
這個時候,如果上一個Leader並不是掛掉,而是因為網路問題,通訊延遲,現在恢復通訊,就會因為兩個Leader產生腦裂情況.
所以進入發現階段:
Leader會收集所有Follwer的Zxid,Leader會選出最大Zxid,基於zxid的epoch加1,,再將這個生成的zxid廣播給所有的Follower,各個Follower收到全新的zxid後,返回ACK給Leader,帶上各自最大的zxid和歷史事務日誌,Leader選出最大的zxid,並更新自身歷史日誌,這種情況就可以避免腦裂 -
Leader將最新的事務日誌,同步給所有Follower,當半數Follower同步成功,這個Leader就開始了自己的統治時代