鏈接:https://www.zhihu.com/question/35139415/answer/61562488
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
zookeeper作爲一個開源的分佈式應用協調系統,已經用到了許多分佈式項目中,用來完成統一命名服務、狀態同步服務、集羣管理、分佈式應用配置項的管理等工作。
這裏不再具體提zk的選主邏輯,paxos協議什麼的,只開始講一些用法。
kazoo是一個封裝了zookeeper操作的python庫,其中除了提供底層的zookeeper接口外,還提供了一些更高級別的封裝。
zookeeper的基本操作
zookeeper主要操作分以下幾種:
- 創建節點
- 讀節點數據
- 更新節點數據
- 刪除節點
- 監控節點變化
其中節點被組織成目錄樹的形式,每個節點下面都可以有一些子節點。
節點可以是以下四種類型:
PERSISTENT:持久化目錄節點,這個目錄節點存儲的數據不會丟失;
PERSISTENT_SEQUENTIAL:順序自動編號的目錄節點,這種目錄節點會根據當前已近存在的節點數自動加 1,然後返回給客戶端已經成功創建的目錄節點名;
EPHEMERAL:臨時目錄節點,一旦創建這個節點的客戶端與服務器端口也就是 session 超時,這種節點會被自動刪除;
EPHEMERAL_SEQUENTIAL:臨時自動編號節點。
監控節點變化時,可以監控一個節點的變化,也可以監控一個節點所有子節點的變化。
ZK應用
統一命名服務
在分佈式系統中,經常需要給一個資源生成一個唯一的ID,在沒有中心管理結點的情況下生成這個ID並不是一件很容易的事兒。zk就提供了這樣一個命名服務。
一般是使用create方法,創建一個自動編號的節點。
配置管理
主要用於多個結點共享配置,並且在配置發生更新時,利用zk可以讓這些使用了這些配置的結點獲得通知,進行重新加載等操作。
集羣管理
主要有兩個方面:一是集羣選主,二是資源定位。
集羣選主是當一個集羣會啓動一主一備兩個服務單元時,可以使用zk來選出一個主服務單元。 具體方法就是在一個節點下創建一個自動編號的臨時結點,然後watch父節點,如果該臨時節點成爲父節點下編號最小的節點,則認爲其成爲了主服務單元。
在kazoo中,提供了 election 相關的封裝,使用極其簡單。
zk = KazooClient(hosts='127.0.0.1:2181')
zk.start(10)
election = zk.Election("/electionpath", "my-identifier")
# blocks until the election is won, then calls
# my_leader_function()
election.run(my_leader_function)
在實際使用時有時候會遇到一些工作單元watch集羣的master結點,當主從切換時,工作單元可能會需要重新連接到新的主節點以使工作能夠繼續。在kazoo中,暫沒發現有方法能夠直接使孩子watch Election的狀態,暫未試驗是否可以直接使用watch接口直接watch electionpath。不過即使不能通過watch electionpath解決,也可以在master切換之後,通過直接去修改另外一個固定位置的結點而工作單元都watch那個結點來解決。
資源定位主要是用於分佈式系統中一些服務節點位置或者狀態發生變化時,通知一些相關的需要知道的服務節點發生了這些變化,以便於其能夠做出一定的響應。比如,一個rpc_server發生了故障遷移,這時就需要client重新能夠發現並向新的地址、端口發起請求。
共享鎖
實現起來和集羣選主基本一致,都是創建一個自動編號的臨時結點,然後watch父結點,判斷自己是否是最小編號節點。
在kazoo中提供了 lock 相關封裝:
import os
import sys
import time
from kazoo.client import KazooClient
zk = KazooClient(hosts='127.0.0.1:2181')
zk.start(10)
lock = zk.Lock("/lockpath", "my-identifier")
with lock:
print "got lock"
time.sleep(10)
快速的運行兩遍這個程序,會發現第二次運行的程序會等到第一次運行結束之後纔會輸出“got lock”.
隊列管理
...
一些基本用法
關於Watch:
前面講集羣資源定位時提到了watch,zk原生的watch都是通過get/get_children/exists等查詢接口提供的,用戶在查詢的時候可以再設置一個watch函數,當有關心的事件觸發時,watch函數會被調用。
例如,get方法中設置的watch函數會在數據發生更新或者刪除時被觸發。exists在節點的存活性發生變化時觸發,而get_children則在子節點的存活性發生變化時觸發。
另外,當watch函數觸發後,用戶需要重新重新設置watch函數,不然隨後的事件再次發生時,將不會被觸發。
仔細思考上述接口會發現,當一次watch事件觸發之後,到再次設置watch函數之間,如果發生了watch的事件,這個事件是不會被觸發的。
雖然表面上這個接口會導致部分watch的事件丟失,但實際上由於設置watch和get/get_children/exists是同一個原子性的操作,故丟失的事件不會影響zookeeper上的數據與client端得到的數據的最終一致性。
使用kazoo的DataWatch可以簡化watch的使用,但有時候可能還是需要去猜測其watch的實現,並不如想象中那麼好用。
個人認爲,好多時候,我們其實並不真正的需要watch,輪詢可能會使代碼更爲清晰,更少出錯