Apache ZooKeeper進一步學習
最然之前自己搞過一段時間的分布式理論知識,對Paxos、ZAB、Raft一致性協議也或深或淺地有些了解認識,所以對這種最終一致性分布式系統的使用場景也有一定的概念(感覺和別人侃起來也更有談資了),但是作為一個程序員來說,一旦要把他們落實到代碼上來,下起手來還真是有些困難。前幾天偶遇一本英文的《Apache ZooKeeper Essentials》,其中對ZooKeeper實現分布式系統常用組件的構建過程講的相對具體一些,這裡不敢獨享也和大家分享一下。
額外的在此感嘆一下:一方面覺得Java的程序員好享福,Apache全家桶好多項目都是Java實現的,所以在企業項目開發中Java可選用的成熟組件非常的多,自然使用資料和經驗也遍地都是;雖然很多庫也提供了C語言訪問庫的綁定,而且通常這是必須的,因為很多腳本語言(比如Python、PHP)的綁定,受限於語言的操作能力和性能方面的考慮,大多也是基於C語言綁定之上再進行一層特定語言的封裝。這個過程中,C++的地位感覺有些尷尬了,除非原生使用C++開發的,絕大多數的組件感覺都沒有原生C++綁定,而C++程序員想要用的Happy順手(比如自動構造中初始化,自動析構釋放資源),就必須自己基於C語言綁定再進行進一步的封裝,因此C++的世界中,這種簡單封裝的輪子在我們的項目中非常之普遍,由此可見C++對C語言封裝技能將會是C++程序員必備重要技能之一啊!
一、ZooKeeper的啟動配置
為了兼顧於測試和生產環境的需要,ZooKeeper具有單機模式和集群模式的部署形式可供選擇。
1.1 standalone模式
在該配置中有幾個必須的參數需要說明:tickTime表示每個tick所代表的時長單元,以ms為單位,後續很多的配置都是基於這個tickTime來計數的,比如心跳間隔;dataDir表示數據存儲目錄,ZooKeeper服務會有一個in-memory狀態資料庫,而這個目錄就是用於存儲資料庫的快照內容和修改事務日誌信息使用的,這是一個類似的append only的記錄文件;clientPort是接收客戶端連接請求的偵聽埠,默認是2181。
使用上面的配置信息,使用 就可以啟動ZooKeeper服務端了,通過使用 命令可以看見,當前服務工作在standalone模式下,因為該模式存在單點故障的風險,所以只可以用於測試使用。
ZooKeeper提供Java和C兩種語言命令行客戶端訪問版本,分別通過 和 可以連接服務端,他們風格有些差異,自行選擇。
1.2 multinode cluster模式
生產環境的ZooKeeper至少需要3台,通常需要5台甚至更多的實例部署成集群模式,且最好是奇數個的主機數量。這種模式下,zoo.cfg配置文件需要一些額外的信息:
initLimit=10 syncLimit=5 server.1=zoo1:2888:3888 server.2=zoo2:2888:3888 server.3=zoo3:2888:3888
initLimit表示fellower初始連接到leader的超時ticket;syncLimit表示fellower和leader同步的超時ticket;剩下部分使用 的格式列出主機列表,其中id的範圍是1-255證書表示的主機ID,指定後需要在每個實體主機的dataDir根目錄通過新建myid文件並寫入對應相匹配的ID,port1表示集群中peer-to-peer各主機相互交互埠(比如fellower和leader相互交互的埠),而port2主要用來選主操作過程中使用的埠號。
在集群模式下,客戶端連接方式的主機參數需要指明集群中的所有機器,形如: 。
1.3 multiple node模式
在單個機器下也可以運行一個ZooKeeper集群,各個主機分別用不同的埠(而不是上面的IP)來獨立運行,不過這種情況也是建議作為測試目的。配置文件中,各個主機的clientPort埠號以及port1、port2埠就必須各不相同,同時還要為各個主機指定不同的dataDir目錄:
然後,相對應的客戶端連接集群的方式就變成如下形式:
二、ZooKeeper原理再次淺析2.1 數據模型
ZooKeeper基本數據模型是znode,以類似文件系統的方式組織成為樹狀結構,每個znode可以存儲一個任意二進位比特串,其長度被限制成1MB的大小,這通常情況下足夠使用了,只不過不支持數據類型的存儲在實際使用中還是有些不便,znode的路徑表示必須是絕對路徑形式的。znode分為persistent、ephemeral兩種類型,可以外加sequential屬性,那麼總共可以組織成4種類型的znode節點。
(1) persistent
這種模式持久節點除非被顯式調用API刪除外,否則會一直存在,在使用中通常用於存儲高可用的、重要的數據內容,比如程序、服務的配置信息等。
(2) ephemeral
這種模式臨時節點會在創建它的客戶端和伺服器建立的session會話斷開後自動刪除,當然該節點也可以使用delete的API顯式刪除,當前ZooKeeper的限制是這種臨時節點不允許有children。臨時節點在分布式系統中通常用於某些組件需要動態知道其他組件、服務或者資源狀態和可用性的時候,命令行創建使用-e參數指定。
(3) sequential
當節點有這種屬性的時候,ZooKeeper在創建該節點的時候會動態為其產生一個單調遞增的序列號,並追加成為節點名的一部分。序列號是使用int(4字)存儲,以0打頭補齊總共用10個數字顯示(這樣的目的是為了好排序的目的),順序節點命令行創建需使用-s參數。
2.2 ZooKeeper Watch監聽
服務更新通常有poll和push兩種模式,前者在規模大和複雜的系統下伸縮性不強,而ZooKeeper採用了事件通知的方法來實現一種類似push的通知模式,相關事件會被推送給感興趣的註冊客戶端,在ZooKeepr中註冊的過程就是在znode上面設置watch event的過程。需要注意的是watch是one-time類型的操作,即一次註冊只會觸發一次通知,如果客戶端還想繼續偵聽znode節點的事件,就必須在接收到通知的時候再次手動註冊之。還需要注意的是,因為ZooKeeper的watch event是one-time觸發的,所以在服務發送通知和客戶端收到通知並重置監聽這個時間間隔中,如果這個znode此時再有notification發生,則這個消息就會被丟失掉,應用程序開發的時候需要考慮到會有這個問題。
在watch event和notification機制中,ZooKeeper保證:所有的事件按照FIFO的順序處理,會保證notification是按序發送的;對於一個znode,在其notification通知到客戶端之前,不會再對這個znode做任何其他修改操作;發送事件的順序是按照ZooKeeper服務接收到改變順序為準。此外如果客戶端同ZooKeeper服務因為某種原因斷開連接後,而後又重新恢復連接(會話),無論新連接上的主機是否是之前會話所在的主機,之前註冊的watch event將會被自動恢復註冊,而且之前的notification也將會被正常推送,所以可以說客戶端連接到集群的任何伺服器都是絕對透明的,而唯一的特例是當一個客戶端註冊一個znode的exist事件偵聽,在客戶端離線的時候這個znode被創建然後又被刪除,而後來客戶端恢復連接的時候這個事件是不會得到這個過程的事件通知的。
2.3 ZooKeeper數據模型的操作API
ZooKeeper數據模型支持的操作介面有:create、delete、exists、getChildren、getData、setData、getACL、setACL、sync,除了上面的普通介面外ZooKeeper還支持對znode的批次更新multi,通過批次操作可以將對znode的多個原語操作合并成一個單元,且這個操作整體是一個原子操作。客戶端對讀操作可以直接由客戶端連接的伺服器處理(很可能是fellower),這個過程還可以使用sync讓客戶端同步得到最新的狀態或結果,而對於寫操作會自動轉發到leader節點上,並且在事務持久化到絕大多數節點之後,才會為這個請求生成響應信息。
在對znode進行讀操作的時候(比如exists、getChildren、getData),允許watch event設置在對應節點上,而寫操作(比如create、delete、setData)則會觸發watch event並發生notification,常見的watch event事件類型有:NodeChildrenChanged、NodeCreated、NodeDateChanged、NodeDeleted等。
2.4 客戶端會話
開始的時候,客戶端會隨機連接主機列表中的伺服器,如果失敗後會繼續嘗試下一台伺服器,這個過程將一直嘗試直到成功建立連接或者嘗試了所有的伺服器。當客戶端和伺服器連接成功後,一個session就被創建了,並且使用一個64-bit的數字進行標識,session在ZooKeeper中扮演著重要的角色,比如ephemeral節點的生命周期就同客戶端和伺服器的連接相關聯的,一旦session結束後其臨時節點將會被自動刪除。session具有超時周期屬性,這在客戶端和伺服器創建連接的時候由客戶端作為參數制定並由ZooKeeper集群管理,當前實現要求timeout的值最小為2 x tickTIme,最大為20 x tickTime。
session是通過客戶端向ZooKeeper發送ping request心跳維持的,而且是由客戶端庫自動發送(應用程序無須關心),當心跳超時之後ZooKeeper和客戶端的連接會被刪除,且此時客戶端庫會自動透明實現和ZooKeeper的重連操作,一旦重連成功(無論是否是之前那台主機),現存的session、相關的ephemeral節點會繼續存在生效,且客戶端斷線這段時間中所有pending notification將會被按照順序重新發送給客戶端。
三、ZooKeeper構建常用分布式組件3.1 Barrier
Barrier屏障是分布式系統中常用的用於阻塞所有執行在某個執行點目的,直到某個條件被滿足後再讓所有的節點繼續執行,ZooKeeper實現屏障的步驟是:
當這個znode存在的時候表示barrier生效;
c. 如果exists()返回true則表示barrier生效,此時客戶端阻塞等待notification;
e. 上面的刪除操作會觸發watch event,此時所有阻塞在此的客戶端都接收到notification,他們再次調用exists()都返回false,表明barrier已經被消除,則所有客戶端開始執行。
根據上面的原理,還可以輕鬆的實現double-barrier,可以用於控制計算的開始、退出時間點,原理是當指定數目的線程加入到barrier中時允許計算開始執行,而當所有的計算節點計算完成退出後,整個計算過程被標示為結束狀態。其過程可以描述為:
Phase 1
每一個客戶端處理進程都會在/barrier目錄下創建ephemeral節點,而通常客戶端會用自己的主機名標識自己這個臨時節點;同時,客戶端還需要設置/ready節點的watch event監聽;
b. N是預先知曉的客戶端處理進程數目最小閾值,每當一個客戶端進程加入之後,都調用 檢查子節點的數目;
d. 上面的創建操作會觸發所有其他客戶端開始計算操作;
Phase 2
a. 當客戶端進程計算結束之後,都刪除當時自己創建的臨時節點;
b. 然後客戶端進程調用 ,當M !=0 時候繼續等待,而當M == 0時候,則大家就可以全部退出barrier了。
上面實現的不理想之處是存在驚群(herd effect)效應,改進的方式是創建臨時節點時候採用sequential ephemeral節點,而在最後退出的時候,每個客戶端進程只對臨近於自己較小的順序節點添加watch event即可。
3.2 Queue
Queue作為聯繫生產者和消費最常用紐帶,在分布式系統中也經常會被使用。通常而言生產者在某個節點下創建子節點以表示為「生產」行為,而消費者刪除子節點表示「消費」過程,當然常常任務隊列還需要FIFO的順序特性,此時創建的子節點可以則可以是sequential順序節點。
b. 生產者通過創建順序子節點表示產生消息,其調用為 ,其產生的消息形如queue-N;
c. 消費者通過調用 ,該調用會返回一個子節點列表,通過排序位元組點列表並從中選出序列號最小的子節點出來,消費者可以刪除掉這個位元組點表示已經消費之;
d. 消費者一直等到M中的子節點消費完後,再調用一次getChildren()調用,以查看是否有新的子節點被添加進來。
上面的操作在刪除子節點的時候可能會有問題,就是在其他客戶端獲取該自己點訪問的時候,此時刪除之會返回失敗,客戶端則需要重新嘗試刪除之(這麼看來無法保證節點只被一個消費者消費啊)。queue實現的例子可以從recipes中查看官方樣例。基於上面的例子,實現PriorityQueue也很簡單,需要創建的隊列名字是」queue-YY」即可,其中YY用於標示隊列的優先順序。
3.3 Lock
分布式鎖是分布式系統中用於同步訪問共享資源的重要原語,分布式鎖要求多個客戶端不會同時持有該鎖資源,以實現某一時刻只有一個客戶端可以對指定的資源進行訪問操作,比如寫共享的文件、資料庫。
為了創建分布式鎖,首先需要創建一個持久節點作為鎖節點,客戶端如果需要這個鎖就必須在其下創建臨時序列ephemeral-sequential節點,然後我們約定擁有最小序列號的節點擁有該鎖,而當客戶端釋放鎖的時候,只需要刪除自己這個臨時節點就可以了,其請求鎖過程可以表述如下:
客戶端獲取鎖的時候創建順序臨時節點 ;locknode
b. 檢查鎖節點的所有子節點 ,這裡設置watch event為false是為了避免驚群效應;
c. 如果在步驟a中創建的節點擁有最小序列號,則該節點獲取到該鎖,退出獲取鎖的演算法過程;
d. 否則就調用 ,如果返回false則進入步驟b;
e. 如果上面步驟返回true,則客戶單安心等待釋放鎖的通知既可以;
釋放鎖的過程如下:
a. 當持有鎖的客戶端直接刪除相應的臨時節點,這個刪除操作也會讓等待其釋放所的其他某個客戶端會收到notification;
b. 那個收到通知的客戶端應當在此時具有最小序列號的客戶端,其得到通知的過程就應當是獲取鎖的過程。
3.4 Leader Election
選主就是要求分布式系統中只有一個客戶端進程作為組織、協調者的情況,這樣可以簡化多個客戶端進程的同步、分配、管理等工作,選主操作的重要作用就是消除Leader帶來的單點問題,能夠在Leader掛掉的情況下快速選出新Leader繼續服務。通常,選主操作必須滿足的條件是在任何時候,至多只有一個Leader存在。
在下面的選主操作中,會使用臨時序列節點,我們同樣假定具有最小序列號的臨時節點代表的主機為Leader。最簡單的方式就是當作為Leader最小序列號的節點消失後,所有節點收到通知並檢查自己是否是最小節點,然後具有最小序列號的那個節點行駛Leader操作,這必然是驚群的實現。
參選的客戶端創建臨時序列節點/election/candidate-sessionID_路徑,使用sessionID的識別碼主要是用於幫助異常情況的名字識別,在創建臨時順序節點的時候可能create()已經成功但是server此時crash掉了,那麼客戶端就無法得到當前創建的名字,而此時client的會話仍然是有效的,此時客戶端通過掃描sessionID可以得到相關節點,這裡所涉及到的ZooKeeper的特性,是序列節點的序列號是基於目錄遞增的,而不是基於特定前綴遞增的。下面假設create()調用成功後返回N序列號;electionelection
b. 客戶端可以獲取當前參選者信息 ,不設置watch event是為了防止競選過程中出現驚群情況;
d. 當得到上面節點的刪除notification的時候,調用 獲取所有的參選節點;
e. 當得到最新的參選列表L時候,此時:如果本節點candidate-sessionID_N是L中的最小節點,其中M是小於N相鄰序列號;election
f. 如果當前的Leader已經crash掉,則擁有次最小序列號的客戶端收到通知,通過上面的d步驟檢查將會作為leader繼任;
可選的,Leader可以將自己的識別信息保留在固定節點上,這時候其他節點將可以方便的查詢那個固定節點的信息,得知當前系統的Leader是誰了。
3.5 Group membership
群組管理功能是允許其他進程能夠相互感知進程加入、退出集群的功能,以便得到當前集群的最新狀態。在ZooKeeper中通過ephemeral臨時節點的特性,任何客戶端加入集群都可以在一個預設的路徑作為父節點創建一個ephemeral臨時節點,而通過在這個父節點上增加watch event就可以感知加入、離開集群的操作導致集群成員的變化。
任何客戶端加入群組都需要在這個路徑下創建ephemeral臨時節點;
通過調用 方法,以感知群組成員的變化事件;
c. 當有客戶端加入群組的時候就會創建ephemeral臨時節點,客戶端離開或者crash等情況,ZooKeeper服務會自動刪除其對應的臨時節點,所有的其他成員將得到通知;
d. 通過查看L,成員可以得知加入或者離開群組的成員;
當然,上面的實現會有herd effect驚群問題。
3.6 Two-Phase commit
2PC是事務系統中最常見的一致性手段,用於保證某個事務在所有客戶端都是原子性commit或rollback。2PC的兩個階段是:協調者詢問所有參與者,讓所有參與者投票是提交還是放棄某個事務;協調者收集所有選票,如果所有參與者都贊成commit那麼就提交事務,否則就回滾事務,協調者最終將結果通知給所有參與者。
協調者(可以通過之前的選主方式選出Leader作為協調者)在其下創建事務,然後在其上設置watch event;
e. 協調者發現應當參與的所有參與者都建立了臨時節點後,就清點各個客戶端的選票,根據投票結果將最終的事務結果寫入到tx_result節點中;
f. 當tx_result節點更新的時候,所有的客戶端都會收到NodeDataChanged事件通知,然後根據最終的決議結果發起提交還是回滾操作。
3.7 Service Discovery
服務發現是分布式系統的核心功能和SOA架構的核心構件,最簡單的服務發現是可以讓客戶端發現服務提供者的IP:Port地址信息。服務發現的核心特性包括:其允許服務進行註冊以表明自己的可用性;其通過某種方式可以定位到一個現存可用的服務;服務的變更可以通知傳播出去。
c. 服務發現的過程,客戶端加入集群後可以對某個服務的路徑添加watch偵聽,新服務實例的加入和退出都可以被通知到。
哈哈,是不是感覺離實踐更進一步了,加油!
本文完!
※IE SetAttributeStringAndPointer釋放後重用漏洞分析
※Mybatis 處理列名、欄位名映射–AS用法&ResultMap
※如何做 Go 的性能優化?
※Xcode代碼全黑的另一種解決辦法
※微軟推出了一款 App 可以幫你讀出全世界
TAG:推酷 |
※阿里Apache Dubbo佈道師談Service Mesh技術
※Apache已修復Apache Tomcat中的高危漏洞
※Apache Kafka creator 饒軍談Kafka未來規劃
※Apache Traffic Server發布新版v
※重溫 Apache Kafka
※專訪朱詩雄:Apache Spark中的全新流式引擎Structured Streaming
※BT 棄用 Apache CloudStack
※Docker 安裝 Apache
※建站初學者必知的wordpress在Nginx/Apache/IIS中的偽靜態規則
※Apache Storm流計算模型 及WordCount源碼實踐
※從Spark Streaming到Apache Flink: 實時數據流在愛奇藝的演進
※最全面跨組件數據Apache Atlas實現跨組件沿襲Apache Hadoop
※如何成為 Apache 項目的 committer
※Apache Commons IO 入門教程
※Netcraft 6月Web 伺服器排名:Nginx有望超越Microsoft,Apache持續走低
※centos下apache伺服器以及Tomcat的配置
※apache的commons-email 類庫開發示例
※RPC框架實踐之:Apache Thrift
※聖思園第二門課程業已確定——Apache Cassandra
※Apache Shiro 的Web應用支持指南