Kali-linux Arpspoof工具
特點
一個leader
多個follower
組成的叢集,只要半數伺服器有效,則叢集有效。
- 順序一致性: 從同一客戶端發起的事務請求,最終將會嚴格地按照順序被應用到
ZooKeeper
中去。 - 原子性: 所有事務請求的處理結果在整個叢集中所有機器上的應用情況是一致的,也就是說,要麼整個叢集中所有的機器都成功應用了某一個事務,要麼都沒有應用。
- 全域性資料一致: 無論客戶端連到哪一個
ZooKeeper
伺服器上,其看到的服務端資料模型都是一致的。 - 可靠性: 一旦一次更改請求被應用,更改的結果就會被持久化,直到被下一次更改覆蓋。
- 實時性:在一段時間內,客戶端可以讀取到最新的資料(伺服器間同步很快)
體系結構
事實上ZooKeeper
底層只提供了兩個功能:
- 管理(儲存,讀取)使用者程式提交的資料
- 為使用者程式提交資料節點監聽服務
由若干伺服器構成,每臺伺服器記憶體中維護相同的類似於檔案系統的樹形結構。其中一臺通過ZAB
原子廣播選舉主控伺服器,其他的作為從屬伺服器。
客戶端可以通過TCP
協議連線任意一臺伺服器,使用這個TCP
連線來發送請求、獲取結果、獲取監聽事件以及傳送心跳包。如果這個連線異常斷開了,客戶端可以連線到另外的機器上。但是:
- 每一臺從伺服器都可以直接響應讀請求
- 如果是更新或者寫資料操作,則必須由主控伺服器進行協調更新(客戶端如果連線的是從伺服器,則請求會被轉發至主控伺服器上,由其完成)
因此,ZooKeeper
任意一臺伺服器都可以響應讀操作,這是吞吐量高的主要原因(資料儲存在記憶體中,因此它也是低延遲的)。並且,在讀多於寫的應用程式中效能更高。
但潛在的問題是,即使主控伺服器已經更新了記憶體資料,但是ZAB
協議還未能將其廣播到從屬伺服器時,客戶端可能會讀到過期的資料。所以在API
中提供了sync
操作:接收到sync
命令的從屬伺服器從主控伺服器同步狀態資訊,保證兩者完全一致。因此只要在讀操作前呼叫sync
,可以保證客戶端讀到最新狀態的資料。
伺服器在響應請求時,會給客戶端分配一個漸增的zxid
格式的時間戳(包括了節點建立時間cZxid
、節點修改時間mZxid
、子節點修改或者建立時間pZxid
epoch
,用來標識leader
是否發生改變,低32
位用來遞增計數。客戶端會在請求中攜帶該資訊,如果發生了伺服器切換,當伺服器發現自身的zxid
比客戶端發來的zxid
要低,表明伺服器資料過期,需要同步。
ZooKeeper
的容錯是通過重放日誌(Replay log
)和模糊快照(Fuzzy Snapshot
)實現的:
- 重放日誌:將更新操作體現在記憶體資料之前先寫入外存日誌中避免資料丟失
- 模糊快照:週期性對記憶體資料進行資料快照,並不對記憶體資料加鎖,而是用深度遍歷的方式將記憶體中的屬性結構轉入外存快照資料中。因而快照資料可能與記憶體中並不一致,是模糊的。
ZooKeeper
可以保證資料更新操作是冪等的,只要保證操作順序一致,即使多次執行統一操作對最終結果沒有影響。載入模糊快照,利用重放日誌重新執行一遍,系統會恢復到最新狀態。
資料結構
ACL
ZooKeeper
採用ACL
(Access Controll List
,訪問控制)策略進行許可權控制,共有五種許可權:
create
:建立子節點read
:獲取節點資料和子節點列表write
:更新節點資料delete
:刪除子節點admin
:設定節點ACL
許可權
Znode
類似於傳統的檔案系統模式,由樹形的層級目錄結構組成,節點稱之為Znode
。每個ZNode
預設可以儲存1MB
的資料。節點既可以是檔案,也可以是目錄。它有兩種型別:持久節點和臨時節點。
對應於每個Znode
,Zookeeper
都會為其維護一個叫作Stat
的資料結構,Stat
中記錄了這個Znode
的三個資料版本,分別是dataVersion
(每次對節點進行set
操作,資料版本值加)、cVersion
(當子節點有變化時,值加1)和aclVersion
(當前Znode
的訪問控制版本號)。
dataVersion
實際上是樂觀鎖。
持久節點不論客戶端會話情況,一直存在,只有當客戶端顯式呼叫刪除操作才會消失。臨時節點會在會話結束後,自動由ZooKeeper
刪除。
在
ZooKeeper
中,一個客戶端連線是指客戶端和伺服器之間的一個TCP
長連線。通過這個連線,客戶端能夠通過心跳檢測與伺服器保持有效的會話,也能夠向Zookeeper
伺服器傳送請求並接受響應,同時還能夠通過該連線接收來自伺服器的Watch
事件通知。 會話的sessionTimeout
值用來設定一個客戶端會話的超時時間。當由於伺服器壓力太大、網路故障或是客戶端主動斷開連線等各種原因導致客戶端連線斷開時,只要在sessionTimeout
規定的時間內能夠重新連線上叢集中任意一臺伺服器,那麼之前建立的會話仍然有效。在為客戶端建立會話之前,服務端首先會為每個客戶端都分配一個
sessionID
。由於sessionID
是Zookeeper
會話的一個重要標識,許多與會話相關的執行機制都是基於這個sessionID
的,因此,無論是哪臺伺服器為客戶端分配的sessionID
,都務必保證全域性唯一。
API
getData
、exists
和getChildren
可以設定觀察標識,如果觀察標識watch
為true
,則當節點內容發生變化時,ZooKeeper
會主動通知客戶端程序,但是並不會將變化內容資料推送過來。
應用場景
領導者選舉
當主從結構中主節點發生故障,需要由某臺備機成為新的主控機,這個過程被稱為領導者選舉。
ZooKeeper
將l
節點設定為領導專用節點,儲存領導者的地址資訊以及其他輔助資訊。當程序p
嘗試讀取l
,並設定觀察標識。
- 如果讀取成功,說明已有領導者,並且從讀取結果中獲得領導者相關資訊。
- 如果讀取失敗,說明無領導者。
p
嘗試建立l
節點,如果建立成功,其他節點因為設定了觀察標識,ZooKeeper
會通知其他節點領導者發生了故障。 - 如果
l
節點失效了,ZooKeeper
會通知其他非領導節點,其他節點再按此步驟進行選舉競爭。
配置管理
配置檔案儲存在ZooKeeper
的某個節點c
中,分散式系統中的客戶端程序啟動時從該節點讀取配置資訊並設定觀察標記。若配置檔案內容被改變,客戶端會接收到變化通知。
組成員管理
利用臨時節點進行組成員管理,建立一個g
節點代表某個群組,某個組成員加入在g
下建立臨時子節點。負責組成員管理的監控程序可以使用getChildren
獲得組下所有成員資訊並設定觀察標識,當後續有新節點加入或者退出時會獲得通知訊息。
任務分配
監控程序建立任務佇列管理節點tasks
,所有新進入系統的任務都可以在tasks
節點下建立子節點。當有新任務task-j
時,ZooKeeper
通知監控程序,監控程序將其分配給機器i
,然後在machines
目錄下對應的m-i
節點建立子節點task-j
。m-i
節點對應的伺服器會監聽節點的變化,發現有新增節點時說明有新分配的任務,讀出並執行後,將其刪除,同時刪除tasks
節點下的task-j
節點。
鎖管理(實現分散式鎖)
首先設立節點l
作為鎖標記,每個程序在節點下創立一個臨時自增節點,這樣各個程序按照建立子節點的先後順序在名字上進行編號,如果當前程序發現自己編號是l
節點下最小的編號節點,那麼它就獲得了鎖。
由於建立的是臨時節點,客戶端如果發生宕機,過了一定時間
ZooKeeper
沒有收到客戶端的心跳包會判斷會話失效,將臨時節點刪除從而釋放鎖。同時,
ZooKeeper
提供的API
中,讀取子節點列表(判斷自己是否是序號最小的節點)與設定監聽器的操作是原子執行的,從而可以保證不會丟失事件。
寫鎖(排他鎖):判斷l
下的節點是否存在編號比自己小1
的子節點,並設定觀察標識。如果不存在,可以直接獲得鎖,如果存在則進行等待。
讀鎖(併發鎖):判斷l
下的節點是否存在編號比自己小的子節點持有寫鎖,如果沒有寫鎖可以直接獲得鎖;否則監聽離自己最近的寫鎖並進行等待。
雙向路障同步
路障同步是指多個併發程序都要到達某個同步點後再繼續向後推進。
利用某個節點b
代表路障,每個節點通過在b
下建立子節點表明自己已經到達了同步點,離開時通過刪除自己節點表明自己準備離開同步點。因此,在開始時,可以判斷b
下的節點是否到達一定數量,從而判斷同步條件是否滿足,可以開始執行。如果需要對結束時進行同步,那麼判斷b
下的節點個數是否已經到達0
。
其他還包括命名服務,負載均衡等。
叢集角色
ZooKeeper
中沒有選擇傳統的Master/Slave
概念,而是引入了Leader
、Follower
和Observer
三種角色。
ZooKeeper
叢集中的所有機器通過一個Leader
選舉過程來選定一臺稱為“Leader”
的機器,Leader
既可以為客戶端提供寫服務又能提供讀服務。除了 Leader
外,Follower
和Observer
都只能提供讀服務。Follower
和Observer
唯一的區別在於Observer
機器不參與Leader
的選舉過程,也不參與寫操作的“過半寫成功”策略,因此Observer
機器可以在不影響寫效能的情況下提升叢集的讀效能。
半數機制
我們知道在Zookeeper
中Leader
選舉演算法採用了Zab
協議。Zab
核心思想是當多數Server
寫成功,則任務資料寫成功。
- 如果有3個
Server
,則最多允許1個Server
掛掉。 - 如果有4個
Server
,則同樣最多允許1個Server
掛掉
因此選奇數個Server
即可。
ZAB
協議
ZooKeeper
基於paxos
演算法,但是也擁有自己的核心演算法ZAB
(原子訊息廣播協議),包括訊息廣播和崩潰恢復兩個方面。
訊息廣播:
- 在廣播事務之前
Leader
伺服器會先給這個事務分配一個全域性單調遞增的唯一ID
,也就是zxid
,每一個事務必須按照zxid
的先後順序進行處理。 Leader
通過先進先出佇列將帶有 zxid 的訊息作為一個提案(proposal
)分發給所有follower
。Follower
伺服器在接收到proposal
之後,都會將其以事務日誌的形式寫入到本地磁碟中,成功寫入後反饋給Leader
一個ACK
。- 當
Leader
收到半數ACK
響應之後,就會廣播一個Commit
訊息給所有Follower
,通知它們進行提交,同時Leader
也會完成自身的提交。 - 當
follower
收到COMMIT
時,會執行該訊息
因此follower
要麼回ACK
給Leader
,要麼拋棄Leader
,這樣可能會導致某一時刻leader
和follwer
狀態不一致。因此ZAB
協議還有恢復模式。
崩潰恢復:
- 情況一:
leader
收到合法數量的ACK
後,開始廣播COMMIT
命令,但是在所有follower
都收到之前出現宕機,於是剩下的伺服器沒有執行這條命令。此時:- 選舉擁有
zxid
最大的節點作為leader
(此節點必為儲存了所有COMMIT
訊息的) - 新的
leader
將自己事務日誌中未COMMIT
的訊息處理 - 新的
leader
將自身有follwer
沒有的proposal
傳送給follower
,並將COMMIT
命令傳送給所有follower
,保證所有follower
都處理了訊息。最終實現已經處理的訊息不會丟
- 選舉擁有
- 情況二:
leader
還沒傳送COMMIT
就已經宕機了,當leader
重新上線成為follower
之後,會丟棄被跳過的proposal
用以保持與整個系統一致。
leader
選舉
伺服器啟動時的選舉
- 每臺伺服器都會發出投票,投給自己
- 每臺伺服器也會受到其他伺服器的投票請求,首先比較
zxid
,如果對方zxid
較大,則會認可把投票傳送出去。如果zxid
相同,則比較myid
。 - 如果伺服器收到了超過半數機器相同的投票資訊,那麼它將成為
leader
。 - 一旦確定了
leader
,每個伺服器就會更新自己的狀態,如果是follower
,那麼就變更為FOLLOWING
,如果是leader
,就變更為LEADING
。
對於後面啟動的較晚的機器,此時叢集已經正常工作,因此只需要和Leader
機器簡歷連線,進行狀態同步。
執行期間的選舉
leader
宕機後,follower
會變更伺服器狀態為LOOKING
,進入選舉- 執行期間,每個伺服器上的
zxid
可能不同,每個伺服器在第一輪投票也同樣地會投給自己。然後與啟動時投票類似。
可見每次投票都是對(vote_sid, vote_zxid)
和(self_sid, self_zxid)
對比的過程。
監聽機制
- 僅觸發一次:當資料改變時,一個監聽事件被髮送到客戶端,並取消監聽,除非客戶端再次設定監聽,否則不再監聽,所以在回撥中應該新增監聽。
- 服務端維護了兩個監聽佇列:資料監聽佇列和孩子監聽佇列。
getData()
和exists()
設定資料監聽,getChildren()
設定孩子監聽。setData()
將觸發資料監聽,create()
和delete()
將觸發一個節點被建立的資料監聽和孩子變化的孩子監聽。 - 當一個客戶端連線到一個新服務端時,監聽將被觸發。在客戶端與服務端斷開連線後,監聽將不能傳送到客戶端,當客戶端重連後,任何先前的註冊監聽將自動重新註冊並根據情況觸發,整個過程自動完成。
- 存在一種情況將出現監聽事件丟失:當客戶端與服務端斷開連線期間,一個被監聽的節點建立並且被刪除,客戶端將收不到任何監聽事件。
ZooKeeper
服務端的監聽列表僅儲存在記憶體中,不做持久化。當一個客戶端與服務端斷開連線後,它所有的watch
都會從記憶體中移除,客戶端會在重連後自動重新註冊它的所有watch
。
References
:
《大資料日知錄》p96-p104