ZooKeeper編程筆記


初識ZooKeeper,做一些記錄。

ZooKeeper提供一個集中式服務,包括配置維護、服務命名、分布式同步、組管理。子服務常用於分布式應用。

 

ZooKeeper體系結構

ZooKeeper是開源的用於分布式應用的分布式協調服務。它公開了一組接口,分布式應用可在其基礎上實現配置維護、數據同步、服務命名、組管理等上層服務。它采用了類似文件系統的目錄樹型結構的數據模型。協調服務難於處理,特別容易出錯,比如條件競爭和死鎖。ZooKeeper的動機是為了減輕分布式應用實現協調服務的負擔。

zookeeper允許分布式應用通過共享的層次化名字空間進行相互協調。zookeeper在內存中維護數據,訪問上具備高吞吐、低延遲的特點。zookeeper重視高性能、高可用、嚴格有序存取,因此,性能方面能夠勝任大型分布式系統,可靠方面可屏蔽單點故障,嚴格有序存取確保客戶端能夠實現復雜的同步原語。

 

數據模型、層次化名字空間、節點

zookeeper名字空間由節點znode構成,其組織方式類似文件系統,其中各個節點相當於目錄和文件,通過路徑作為唯一標識。與文件系統不同的是,每個節點具有與之對應的數據內容,同時也可以具有子節點。zookeeper用於存儲協調數據,如狀態、配置、位置等信息,每個節點存儲的數據量很小,KB級別(我理解是0~1024KB)。節點維護一個stat結構(包括數據變化的版本號、ACL變化、時間戳),以允許緩存驗證與協調更新。每當節點數據內容改變,版本號會增長。客戶端獲取數據的同時也會獲取數據版本號。節點的數據內容以原子方式讀寫,讀操作讀取全部內容,寫操作替換全部內容。節點具有一個訪問控制列表(Access Control List - ACL)來約束哪些人能執行哪些操作。zookeeper有一種節點叫做臨時節點,臨時節點僅在創建該節點的會話的存存活期間存在,會話結束,臨時節點自動刪除。

 

ZooKeeper組件

如下圖所示,同一個zookeeper服務下的server有兩種,一種是leader server,另一種是follower server。leader特殊之處在於它有決定權,具有Request Processor,在zookeeper整個服務下的每台server將復制各個組件。Replicated Database是包含了所有數據的內存數據庫。讀請求利用本地副本數據庫進行處理。改變zookeeper服務狀態的請求和寫請求將按照一個協同協議執行。所有寫請求(from client)將傳遞到leader,然后leader向所有follower發起提案,follower接收提案並表態,提案通過后執行。消息傳輸層負責在提案失敗時更換leader和followers與leader之間的同步。

 

關於ZooKeeper Watches

zookeeper所有讀操作(getData(),getChildren(),exists())具有設置watch的選項。
zookeeper watch的定義如下:watch事件是一次性觸發器,當watch監視的數據發生變化時,通知設置了該watch的client,即watcher。

需要注意三點:

1.一次性觸發器
client在一個節點上設置watch,隨后節點內容改變,client將獲取事件。當節點內容再次改變,client不會獲取這個事件,除非它又執行了一次讀操作並設置watch

2.發送至client,watch事件延遲
watch事件異步發送至觀察者。比如說client執行一次寫操作,節點數據內容發生變化,操作返回后,而watch事件可能還在發往client的路上。這種情況下,zookeeper提供有序保證:client不會得知數據變化,直到它獲取watch事件。網絡延遲或其他因素可能導致不同client在不同時刻獲取watch事件和操作返回值。

3.設置watch的數據內容
涉及到節點改變的不同方式。比方說zookeeper維護兩個watch列表:節點的數據watch和子節點watch。getData()和exists()設置了內容watch,getChildren()設置了子節點watch,操作返回的數據類型不同,前者是節點的內容,后者是節點的子節點列表。setData()觸發內容watch,create()觸發當前節點的"內容watch"和其父節點的"子節點watch",delete()同時觸發"內容watch"和"子節點watch"(其子節點被全部刪除),以及其父節點的"子節點watch"。說白了,對當前節點的操作,要考慮到對其父節點與子節點的影響。

watch在客戶端所連接的服務端本地維護。watch的設置、維護、分發操作都很輕量級。當客戶端連接到新的服務端,watch將被任一會話事件觸發。與服務端斷開連接時,不能獲取watch事件。客戶端重連后,之前注冊的watch將被重新注冊並在需要時觸發。通常這一切透明地發生,用戶不會察覺到。有一種情況watch可能丟失:之前對一個尚未建立的節點的設置了exists watch,如果斷開期間該節點被建立或刪除,那么此watch將丟失。

對於watch,zookeeper提供以下保證:
1.watch對於其他事件、watch、異步響應是有序的。zookeeper client library保證有序分發
2.客戶端監視一個節點,總是先獲取watch事件,再發現節點的數據變化。
3.watch事件的順序對應於zookeeper服務所見的數據更新的順序。

關於watch要記住的是:
1.watch是一次性觸發的,如果獲取一個watch事件並希望得到新變化的通知,需要重新設置watch
2.watch是一次性觸發的並且在獲取watch事件和設置新watch事件之間有延遲,所以不能可靠的觀察到節點的每一次變化。要認識到這一點。
3.watch object只觸發一次,比如,一個watch object被注冊到同一個節點的getData()和exists(),節點被刪除,僅對應於exists()的watch ojbect被調用
4.若與服務端斷開連接,直到重連后才能獲取watch事件。

 

ZooKeeper訪問控制

zookeeper使用ACL(Access Control List)控制對節點的訪問。ACL與Unix文件訪問權限類似,采用權限位的形式控制節點的可操作類型,zookeeper沒有擁有者和用戶組的概念,使用ACL指定每個用戶的訪問權限。一個ACL只用於一個節點,注意不能用於該節點的子節點,即每個節點的訪問權限由其自身的ACL決定。

每一個客戶端連接在zookeeper內部有唯一的id,zookeeper將連接和id關聯起來,客戶端試圖訪問節點時,用id與節點ACL進行比對,以確定客戶端的訪問權限。

ACL由鍵值對構成,格式是(scheme:expression, perms)。expression內容格式特定於scheme。

ACL權限

 

ZooKeeper客戶端開發

關於C開發,zookeeper提供了兩個庫:zookeeper_st(單線程庫)與zookeeper_mt(多線程庫)。zookeeper_st放棄了事件循環,可在事件驅動的應用程序中使用。而zookeeper_mt更加易用,與Java API類似,創建一個網絡IO線程和一個事件分發線程,用來維護連接和執行回調。

在具體使用上,zookeeper_st僅提供了異步API與回調,用以集成至應用程序的事件循環。它只是為了支持pthread庫不可用或不穩定的平台而存在,例如FreeBSD 4.x。除此以外的其他情況,應使用提供同步與異步兩種API的zookeeper_mt。

采用zookeeper_mt庫的zookeeper客戶端使用了3個線程:
線程1是業務邏輯層,負責與用戶直接交互,主要是zookeeper庫提供的API
線程2是網絡IO層,負責與zookeeper服務端的網絡通信,包括發送業務邏輯層的API調用生成的請求數據、服務端的響應數據、服務端的watch事件數據
線程3是事件處理層,負責執行watch回調

 

 

 

 

 

 

Watch事件類型:

ZOO_CREATED_EVENT:節點創建事件,需要watch一個不存在的節點,當節點被創建時觸發,此watch通過zoo_exists()設置
ZOO_DELETED_EVENT:節點刪除事件,此watch通過zoo_exists()或zoo_get()設置
ZOO_CHANGED_EVENT:節點數據改變事件,此watch通過zoo_exists()或zoo_get()設置
ZOO_CHILD_EVENT:子節點列表改變事件,此watch通過zoo_get_children()或zoo_get_children2()設置
ZOO_SESSION_EVENT:會話失效事件,客戶端與服務端斷開或重連時觸發
ZOO_NOTWATCHING_EVENT:watch移除事件,服務端出於某些原因不再為客戶端watch節點時觸發

watch事件與zookeeper讀操作的對應關系圖:

 

 

API:

 

typedef void (*watcher_fn)(zhandle_t *zh, int type, int state, const char *path,void *watcherCtx);
watch回調函數,兩種watch事件通知方式:
1.legacy:預先實現watch回調函數並將函數指針傳入zookeeper_init,然后使用其他api設置watch
2.watcher object:一個函數指針和一個watcher上下文指針。watch觸發時,兩者結合調用。使用此類型,需使用'w'前綴api,如zoo_awexists、zoo_wget等

zhandle_t *zookeeper_init(const char *host, watcher_fn fn, int recv_timeout, const clientid_t *clientid, void *context, int flags);
創建與ZooKeeper服務端通信的句柄和對應於此句柄的會話。會話的創建過程是異步的,收到ZOO_CONNECTED_STATE狀態事件后,確認會話成功建立。 
int zookeeper_close(zhandle_t *zh); 關閉句柄,釋放資源。調用函數后,會話將不可用,函數返回前會將未發送完畢的請求發送完,所以可能會引起阻塞。 對一個句柄來說,這個方法只許調用一次,調用多次將產生不確定的結果。對於調用過此方法的句柄,其他句柄操作也將產生不確定的結果。 const clientid_t *zoo_client_id(zhandle_t *zh); 返回客戶端會話id,僅在與服務端連接正常時有效 int zoo_recv_timeout(zhandle_t *zh); 返回會話超時時間,僅在於服務端連接正常時有效,該值在與服務器重連后可能改變 const void *zoo_get_context(zhandle_t *zh); 返回句柄上下文 void zoo_set_context(zhandle_t *zh, void *context); 設置句柄上下文 watcher_fn zoo_set_watcher(zhandle_t *zh,watcher_fn newFn); 設置watch回調,返回之前的watch回調 struct sockaddr* zookeeper_get_connected_host(zhandle_t *zh, struct sockaddr *addr, socklen_t *addr_len); 返回服務端的網絡地址(sockaddr結構),僅在與服務端連接正常是有效 int zookeeper_interest(zhandle_t *zh, int *fd, int *interest, struct timeval *tv); 暫時不太理解,可能是返回zookeeper在監聽某個fd的讀或者寫 int zookeeper_process(zhandle_t *zh, int events); 暫時不太理解,通知zookeeper監聽的事件發生了 typedef void (*void_completion_t)(int rc, const void *data); 函數類型定義,異步調用或連接斷開或連接超時執行的回調類型 typedef void (*stat_completion_t)(int rc, const struct Stat *stat, const void *data); 同上,有返回值 typedef void (*data_completion_t)(int rc, const char *value, int value_len, const struct Stat *stat, const void *data); 同上,返回詳細數據 typedef void (*strings_completion_t)(int rc, const struct String_vector *strings, const void *data); 同上 typedef void (*strings_stat_completion_t)(int rc, const struct String_vector *strings, const struct Stat *stat, const void *data); 同上 typedef void (*string_completion_t)(int rc, const char *value, const void *data); 同上 typedef void (*acl_completion_t)(int rc, struct ACL_vector *acl, struct Stat *stat, const void *data); 同上 int zoo_state(zhandle_t *zh); 返回句柄狀態 int zoo_acreate(zhandle_t *zh, const char *path, const char *value, int valuelen, const struct ACL_vector *acl, int flags, string_completion_t completion, const void *data); 創建一個之前不存在的節點。如果設置ZOO_EPHEMERAL,客戶端會話失效,節點將自動刪除;如果設置ZOO_SEQUENCE,一個唯一的自動增加的序列號附加到路徑名,序列號寬度是10個數字的寬度,不足用0填充 int zoo_adelete(zhandle_t *zh, const char *path, int version, void_completion_t completion, const void *data); 刪除一個節點 int zoo_aexists(zhandle_t *zh, const char *path, int watch, stat_completion_t completion, const void *data); 檢查一個節點是否存在 int zoo_awexists(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, stat_completion_t completion, const void *data); 檢查一個節點是否存在,它允許指定一個watcher對象(一個函數指針watcher和對應的上下文watcherCtx),在watch解除時,此函數會調用,watcherCtx作為watcher的傳入參數 int zoo_aget(zhandle_t *zh, const char *path, int watch, data_completion_t completion, const void *data); 獲取節點數據(legacy方式)。completion是回調函數,其rc參數可能是以下參數:ZOK-完成,ZNONODE-節點不存在,ZNOAUTH-客戶端無權限 int zoo_awget(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, data_completion_t completion, const void *data); 獲取節點數據(watcher object方式)。 int zoo_aset(zhandle_t *zh, const char *path, const char *buffer, int buflen, int version, stat_completion_t completion, const void *data); 設置節點數據 int zoo_aget_children(zhandle_t *zh, const char *path, int watch, strings_completion_t completion, const void *data); 獲取子節點列表(legacy) int zoo_awget_children(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, strings_completion_t completion, const void *data); 獲取子節點列表(watcher object) int zoo_aget_children2(zhandle_t *zh, const char *path, int watch, strings_stat_completion_t completion, const void *data); 獲取子節點列表,3.3.0版本加入(legacy) int zoo_awget_children2(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, strings_stat_completion_t completion, const void *data); 獲取子節點列表,3.3.0版本加入(watcher object) int zoo_async(zhandle_t *zh, const char *path, string_completion_t completion, const void *data); Flush leader channel. 暫時不明確 int zoo_aget_acl(zhandle_t *zh, const char *path, acl_completion_t completion, const void *data); 獲取節點的ACL。ACL描述了操作該節點所需具備的條件,即哪些人(id)具備哪些權限后才允許對節點執行哪些操作。 int zoo_aset_acl(zhandle_t *zh, const char *path, int version, struct ACL_vector *acl, void_completion_t, const void *data); 設置節點的ACL int zoo_amulti(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_result_t *results, void_completion_t, const void *data); 以原子方式執行一系列操作 const char* zerror(int c); 返回錯誤信息 int zoo_add_auth(zhandle_t *zh, const char* scheme, const char* cert, int certLen, void_completion_t completion, const void *data); 為應用程序指定證書。調用此函數用於認證的證書。服務端用scheme指定的安全服務對客戶端連接進行認證。 如果認證失敗,將與服務端斷開連接,watcher觸發,狀態碼是ZOO_AUTH_FAILED_STATE int is_unrecoverable(zhandle_t *zh); 檢查zookeeper連接是否可恢復 void zoo_set_debug_level(ZooLogLevel logLevel); 設置調試級別 void zoo_set_log_stream(FILE* logStream); 設置用於記錄日志的文件流。默認使用stderr。若logStream為NULL,則使用默認值stderr。 void zoo_deterministic_conn_order(int yesOrNo); 用於啟用或停用quarum端點的隨機化排序,通常僅在測試時使用。 如果非0,使得client連接到quarum端按照被初始化的順序。 如果是0,zookeeper_init將變更端點順序,使得client連接分布在更優的端點上。 int zoo_create(zhandle_t *zh, const char *path, const char *value, int valuelen, const struct ACL_vector *acl, int flags, char *path_buffer, int path_buffer_len); 同步建立節點 int zoo_delete(zhandle_t *zh, const char *path, int version); 同步刪除節點 int zoo_exists(zhandle_t *zh, const char *path, int watch, struct Stat *stat); 同步檢查節點是否存在 int zoo_wexists(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, struct Stat *stat); 同步檢查節點是否存在(watcher object) int zoo_get(zhandle_t *zh, const char *path, int watch, char *buffer, int* buffer_len, struct Stat *stat); 同步獲取節點數據(legacy) int zoo_wget(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, char *buffer, int* buffer_len, struct Stat *stat); 同步獲取節點數據(watcher object) int zoo_set(zhandle_t *zh, const char *path, const char *buffer, int buflen, int version); 同步設置節點數據 int zoo_set2(zhandle_t *zh, const char *path, const char *buffer, int buflen, int version, struct Stat *stat); 同步設置節點數據並返回當前節點的stat信息 int zoo_get_children(zhandle_t *zh, const char *path, int watch, struct String_vector *strings); 同步獲取子節點列表(legacy) int zoo_wget_children(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, struct String_vector *strings); 同步獲取子節點列表(watcher object) int zoo_get_children2(zhandle_t *zh, const char *path, int watch, struct String_vector *strings, struct Stat *stat); 同步獲取子節點列表並返回當前節點的stat信息(legacy),3.3.0版本加入 int zoo_wget_children2(zhandle_t *zh, const char *path, watcher_fn watcher, void* watcherCtx, struct String_vector *strings, struct Stat *stat); 同步獲取子節點列表並返回當前節點的stat信息(watcher object),3.3.0版本加入 int zoo_get_acl(zhandle_t *zh, const char *path, struct ACL_vector *acl, struct Stat *stat); 同步獲取節點ACL int zoo_set_acl(zhandle_t *zh, const char *path, int version, const struct ACL_vector *acl); 同步設置節點ACL int zoo_multi(zhandle_t *zh, int count, const zoo_op_t *ops, zoo_op_result_t *results); 同步以原子方式執行一系列操作

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM