在寒假前,完成了Zookeeper系列的前5篇文章,主要是分佈式的相關理論,包括CAP,BASE理論,分佈式數據一致性算法:2PC,3PC,Paxos算法,Zookeeper的相關基本特性,ZAB協議。今天,完成Zookeeper系列的最後一篇也是最爲重要的內容:Zookeeper的典型應用場景的介紹,咱們只有知道zk怎麼用,用在哪,咱們才能真正掌握Zookeeper這個優秀的分佈式協調框架。node
首先,咱們要知道,Zookeeper是一個具備高可用、高性能和具備分佈式數據一致性的分佈式數據管理及協調框架,是基於對ZAB算法的實現,基於這樣的特性,使ZK成爲解決分佈式一致性問題的利器,同時Zookeeper提供了豐富的節點類型和Watcher監聽機制,經過這兩個特色,能夠很是方便的構建一系列分佈式系統中都會涉及的核心功能: 如:數據發佈/訂閱,負載均衡,命名服務,分佈式協調/通知,集羣管理,Master選舉,分佈式鎖,分佈式隊列等。這一篇,將針對這些分佈式應用場景來作介紹,並介紹Zookeeper在如今的大型分佈式系統中的做爲核心組件的實際應用。算法
數據發佈/訂閱系統,即配置中心。須要發佈者將數據發佈到Zookeeper的節點上,供訂閱者進行數據訂閱,進而達到動態獲取數據的目的,實現配置信息的集中式管理和數據的動態更新(能夠把咱們知道RPC的註冊中心當作是此場景的應用)。數據庫
發佈/訂閱通常有兩種設計模式:推模式和拉模式,服務端主動將數據更新發送給全部訂閱的客戶端稱爲推模式;客戶端主動請求獲取最新數據稱爲拉模式,Zookeeper採用了推拉相結合的模式,客戶端向服務端註冊本身須要關注的節點,一旦該節點數據發生變動,那麼服務端就會向相應的客戶端推送Watcher事件通知,客戶端接收到此通知後,主動到服務端獲取最新的數據。設計模式
若將配置信息存放到Zookeeper上進行集中管理,在一般狀況下,應用在啓動時會主動到Zookeeper服務端上進行一次配置信息的獲取,同時,在指定節點上註冊一個Watcher監聽,這樣在配置信息發生變動,服務端都會實時通知全部訂閱的客戶端,從而達到實時獲取最新配置的目的。服務器
注意:對於像Dubbo這樣的RPC框架來講,zk將做爲其註冊中心,客戶端第一次經過向zk集羣得到服務的地址,而後會存儲在本地,下一次進行調用時就不會再次去zk集羣中查詢,而是直接使用本地存儲的地址,只有當服務地址變動時,纔會通知客戶端再次獲取。網絡
在平時的開發中,常常會碰到這樣的需求:系統中須要使用一些通用的配置信息,例如:機器列表信息,數據庫的配置信息(好比:要實現數據庫的切換的應用場景),運行時的開關配置等。這些全局配置信息一般有3個特性:數據量一般比較小;數據內容在運行時會發生動態變化;集羣中各機器共享、配置一致。假設,咱們的集羣規模很大,且配置信息常常變動,因此經過存儲本地配置文件或內存變量的形式實現都很困難,因此咱們使用zk來作一個全局配置信息的管理。session
負載均衡是一種至關常見的計算機網絡技術,用來對多個計算機、網絡鏈接、CPU、磁盤驅動或其餘資源進行分配負載,以達到優化資源使用、最大化吞吐率、最小化響應時間和避免過載的目的。一般負載均衡能夠分爲硬件(F5)和軟件(Nginx)負載均衡兩類。Zookeeper也能夠做爲實現軟負載均衡的一種方式。數據結構
分佈式系統爲了保證可用性,一般經過副本的方式來對數據和服務進行部署,而對於客戶端吧來講,只須要在這樣對等的服務提供方式中選擇一個來執行相關的業務邏輯,怎麼選,這就是負載均衡的應用。架構
好比,典型的須要負載均衡的DNS(Domain Name System)服務,咱們能夠用zookeeper實現動態的DNS方案,能夠參考《從Paxos到Zookeeper》這本書對於用zk實現動態DNS的方案P167。併發
zk實現負載均衡就是經過watcher機制和臨時節點判斷哪些節點宕機來得到可用的節點實現的:
ZooKeeper會維護一個樹形的數據結構,相似於Windows資源管理器目錄,其中EPHEMERAL類型的節點會隨着建立它的客戶端斷開而被刪除,利用這個特性很容易實現軟負載均衡。
基本原理是,每一個應用的Server啓動時建立一個EPHEMERAL節點,應用客戶端經過讀取節點列表得到可用服務器列表,並訂閱節點事件,有Server宕機斷開時觸發事件,客戶端監測到後把該Server從可用列表中刪除。
消息中間件中發佈者和訂閱者的負載均衡,linkedin開源的KafkaMQ和阿里開源的MetaQ都是經過zookeeper來作到生產者、消費者的負載均衡。
命名服務是分步實現系統中較爲常見的一類場景,分佈式系統中,被命名的實體一般能夠是集羣中的機器、提供的服務地址或遠程對象等,經過命名服務,客戶端能夠根據指定名字來獲取資源的實體、服務地址和提供者的信息,最多見的就是RPC 框架的服務地址列表的命名。Zookeeper也可幫助應用系統經過資源引用的方式來實現對資源的定位和使用,廣義上的命名服務的資源定位都不是真正意義上的實體資源,在分佈式環境中,上層應用僅僅須要一個全局惟一的名字。Zookeeper能夠實現一套分佈式全局惟一ID的分配機制。(用UUID的方式的問題在於生成的字符串過長,浪費存儲空間且字符串無規律不利於開發調試)
經過調用Zookeeper節點建立的API接口就能夠建立一個順序節點,而且在API返回值中會返回這個節點的完整名字,利用此特性,能夠生成全局ID,其步驟以下
1. 客戶端根據任務類型,在指定類型的任務下經過調用接口建立一個順序節點,如"job-"。
2. 建立完成後,會返回一個完整的節點名,如"job-00000001"。
3. 客戶端拼接type類型和返回值後,就能夠做爲全局惟一ID了,如"type2-job-00000001"。
阿里巴巴集團開源的分佈式服務框架Dubbo中使用ZooKeeper來做爲其命名服務,維護全局的服務地址列表。在Dubbo實現中:
服務提供者在啓動的時候,向ZK上的指定節點/dubbo/${serviceName}/providers目錄下寫入本身的URL地址,這個操做就完成了服務的發佈。
服務消費者啓動的時候,訂閱/dubbo/${serviceName}/providers目錄下的提供者URL地址,並向/dubbo/${serviceName}/consumers目錄下寫入本身的URL地址。
注意,全部向ZK上註冊的地址都是臨時節點,這樣就可以保證服務提供者和消費者可以自動感應資源的變化。另外,Dubbo還有針對服務粒度的監控,方法是訂閱/dubbo/${serviceName}目錄下全部提供者和消費者的信息。
Zookeeper中特有的Watcher註冊於異步通知機制,可以很好地實現分佈式環境下不一樣機器,甚至不一樣系統之間的協調與通知,從而實現對數據變動的實時處理。一般的作法是不一樣的客戶端都對Zookeeper上的同一個數據節點進行Watcher註冊,監聽數據節點的變化(包括節點自己和子節點),若數據節點發生變化,那麼全部訂閱的客戶端都可以接收到相應的Watcher通知,並做出相應處理。
在絕大多數分佈式系統中,系統機器間的通訊無外乎心跳檢測、工做進度彙報和系統調度。這三種類型的機器通訊方式均可以使用zookeeper來實現:
① 心跳檢測,不一樣機器間須要檢測到彼此是否在正常運行,可使用Zookeeper實現機器間的心跳檢測,基於其臨時節點特性(臨時節點的生存週期是客戶端會話,客戶端若立即後,其臨時節點天然再也不存在),可讓不一樣機器都在Zookeeper的一個指定節點下建立臨時子節點,不一樣的機器之間能夠根據這個臨時子節點來判斷對應的客戶端機器是否存活。經過Zookeeper能夠大大減小系統耦合。
② 工做進度彙報,一般任務被分發到不一樣機器後,須要實時地將本身的任務執行進度彙報給分發系統,能夠在Zookeeper上選擇一個節點,每一個任務客戶端都在這個節點下面建立臨時子節點,這樣不只能夠判斷機器是否存活,同時各個機器能夠將本身的任務執行進度寫到該臨時節點中去,以便中心繫統可以實時獲取任務的執行進度。
③ 系統調度,Zookeeper可以實現以下系統調度模式:分佈式系統由控制檯和一些客戶端系統兩部分構成,控制檯的職責就是須要將一些指令信息發送給全部的客戶端,以控制他們進行相應的業務邏輯,後臺管理人員在控制檯上作一些操做,實際上就是修改Zookeeper上某些節點的數據,Zookeeper能夠把數據變動以時間通知的形式發送給訂閱客戶端。
Zookeeper的兩大特性(節點特性和watcher機制):
· 客戶端若是對Zookeeper的數據節點註冊Watcher監聽,那麼當該數據及誒單內容或是其子節點列表發生變動時,Zookeeper服務器就會向訂閱的客戶端發送變動通知。
· 對在Zookeeper上建立的臨時節點,一旦客戶端與服務器之間的會話失效,那麼臨時節點也會被自動刪除。
機器在線率有較高要求的場景,可以快速對集羣中機器變化做出響應。這樣的場景中,每每有一個監控系統,實時檢測集羣機器是否存活。過去的作法一般是:監控系統經過某種手段(好比ping)定時檢測每一個機器,或者每一個機器本身定時向監控系統彙報「我還活着」。這種作法可行,可是存在兩個比較明顯的問題:
1. 集羣中機器有變更的時候,牽連修改的東西比較多。
2. 有必定的延時。
利用ZooKeeper有兩個特性,就能夠實時另外一種集羣機器存活性監控系統。能夠實現集羣機器存活監控系統,若監控系統在/clusterServers節點上註冊一個Watcher監聽,那麼但凡進行動態添加機器的操做,就會在/clusterServers節點下建立一個臨時節點:/clusterServers/[Hostname],這樣,監控系統就可以實時監測機器的變更狀況。
下面經過分佈式日誌收集系統的典型應用來學習Zookeeper如何實現集羣管理。
分佈式日誌收集系統的核心工做就是收集分佈在不一樣機器上的系統日誌,在典型的日誌系統架構設計中,整個日誌系統會把全部須要收集的日誌機器分爲多個組別,每一個組別對應一個收集器,這個收集器其實就是一個後臺機器,用於收集日誌,對於大規模的分佈式日誌收集系統場景,一般須要解決兩個問題:
· 變化的日誌源機器
· 變化的收集器機器
不管是日誌源機器仍是收集器機器的變動,最終均可以歸結爲如何快速、合理、動態地爲每一個收集器分配對應的日誌源機器。
① 註冊收集器機器,在Zookeeper上建立一個節點做爲收集器的根節點,例如/logs/collector的收集器節點,每一個收集器機器啓動時都會在收集器節點下建立本身的節點,如/logs/collector/[Hostname]
② 任務分發,全部收集器機器都建立完對應節點後,系統根據收集器節點下子節點的個數,將全部日誌源機器分紅對應的若干組,而後將分組後的機器列表分別寫到這些收集器機器建立的子節點,如/logs/collector/host1(持久節點)上去。這樣,收集器機器就可以根據本身對應的收集器節點上獲取日誌源機器列表,進而開始進行日誌收集工做。
③ 狀態彙報,完成任務分發後,機器隨時會宕機,因此須要有一個收集器的狀態彙報機制,每一個收集器機器上建立完節點後,還須要再對應子節點上建立一個狀態子節點,如/logs/collector/host/status(臨時節點),每一個收集器機器都須要按期向該結點寫入本身的狀態信息,這可看作是心跳檢測機制,一般收集器機器都會寫入日誌收集狀態信息,日誌系統經過判斷狀態子節點最後的更新時間來肯定收集器機器是否存活。
④ 動態分配,若收集器機器宕機,則須要動態進行收集任務的分配,收集系統運行過程當中關注/logs/collector節點下全部子節點的變動,一旦有機器中止彙報或有新機器加入,就開始進行任務的從新分配,此時一般由兩種作法:
· 全局動態分配,當收集器機器宕機或有新的機器加入,系統根據新的收集器機器列表,當即對全部的日誌源機器從新進行一次分組,而後將其分配給剩下的收集器機器。
· 局部動態分配,每一個收集器機器在彙報本身日誌收集狀態的同時,也會把本身的負載彙報上去,若是一個機器宕機了,那麼日誌系統就會把以前分配給這個機器的任務從新分配到那些負載較低的機器,一樣,若是有新機器加入,會從那些負載高的機器上轉移一部分任務給新機器。
在分佈式系統中,Master每每用來協調集羣中其餘系統單元,具備對分佈式系統狀態變動的決定權,如在讀寫分離的應用場景中,客戶端的寫請求每每是由Master來處理,或者其經常處理一些複雜的邏輯並將處理結果同步給其餘系統單元。利用Zookeeper的一致性,可以很好地保證在分佈式高併發狀況下節點的建立必定可以保證全局惟一性,即Zookeeper將會保證客戶端沒法重複建立一個已經存在的數據節點(由其分佈式數據的一致性保證)。
首先建立/master_election/2016-11-12節點,客戶端集羣天天會定時往該節點下建立臨時節點,如/master_election/2016-11-12/binding,這個過程當中,只有一個客戶端可以成功建立,此時其變成master,其餘節點都會在節點/master_election/2016-11-12上註冊一個子節點變動的Watcher,用於監控當前的Master機器是否存活,一旦發現當前Master掛了,其他客戶端將會從新進行Master選舉。
另外,這種場景演化一下,就是動態Master選舉。這就要用到?EPHEMERAL_SEQUENTIAL類型節點的特性了。
上文中提到,全部客戶端建立請求,最終只有一個可以建立成功。在這裏稍微變化下,就是容許全部請求都可以建立成功,可是得有個建立順序,因而全部的請求最終在ZK上建立結果的一種可能狀況是這樣:/currentMaster/{sessionId}-1 ,?/currentMaster/{sessionId}-2,?/currentMaster/{sessionId}-3 ….. 每次選取序列號最小的那個機器做爲Master,若是這個機器掛了,因爲他建立的節點會立刻小時,那麼以後最小的那個機器就是Master了。
其在實際中應用有:
· 在搜索系統中,若是集羣中每一個機器都生成一份全量索引,不只耗時,並且不能保證彼此之間索引數據一致。所以讓集羣中的Master來進行全量索引的生成,而後同步到集羣中其它機器。另外,Master選舉的容災措施是,能夠隨時進行手動指定master,就是說應用在zk在沒法獲取master信息時,能夠經過好比http方式,向一個地方獲取master。
在Hbase中,也是使用ZooKeeper來實現動態HMaster的選舉。在Hbase實現中,會在ZK上存儲一些ROOT表的地址和 HMaster的地址,HRegionServer也會把本身以臨時節點(Ephemeral)的方式註冊到Zookeeper中,使得HMaster能夠隨時感知到各個HRegionServer的存活狀態,同時,一旦HMaster出現問題,會從新選舉出一個HMaster來運行,從而避免了 HMaster的單點問題。
分佈式鎖用於控制分佈式系統之間同步訪問共享資源的一種方式,能夠保證不一樣系統訪問一個或一組資源時的一致性,主要分爲排它鎖和共享鎖。排它鎖又稱爲寫鎖或獨佔鎖,若事務T1對數據對象O1加上了排它鎖,那麼在整個加鎖期間,只容許事務T1對O1進行讀取和更新操做,其餘任何事務都不能再對這個數據對象進行任何類型的操做,直到T1釋放了排它鎖。
① 獲取鎖,在須要獲取排它鎖時,全部客戶端經過調用接口,在/exclusive_lock節點下建立臨時子節點/exclusive_lock/lock。Zookeeper能夠保證只有一個客戶端可以建立成功,沒有成功的客戶端須要註冊/exclusive_lock節點監聽。
② 釋放鎖,當獲取鎖的客戶端宕機或者正常完成業務邏輯都會致使臨時節點的刪除,此時,全部在/exclusive_lock節點上註冊監聽的客戶端都會收到通知,能夠從新發起分佈式鎖獲取。
共享鎖又稱爲讀鎖,若事務T1對數據對象O1加上共享鎖,那麼當前事務只能對O1進行讀取操做,其餘事務也只能對這個數據對象加共享鎖,直到該數據對象上的全部共享鎖都被釋放。(控制時序)
① 獲取鎖,在須要獲取共享鎖時,全部客戶端都會到/shared_lock下面建立一個臨時順序節點,若是是讀請求,那麼就建立例如/shared_lock/host1-R-00000001的節點,若是是寫請求,那麼就建立例如/shared_lock/host2-W-00000002的節點。
② 判斷讀寫順序,不一樣事務能夠同時對一個數據對象進行讀寫操做,而更新操做必須在當前沒有任何事務進行讀寫狀況下進行,經過Zookeeper來肯定分佈式讀寫順序,大體分爲四步。
1. 建立完節點後,獲取/shared_lock節點下全部子節點,並對該節點變動註冊監聽。
2. 肯定本身的節點序號在全部子節點中的順序。
3. 對於讀請求:若沒有比本身序號小的子節點或全部比本身序號小的子節點都是讀請求,那麼代表本身已經成功獲取到共享鎖,同時開始執行讀取邏輯,如有寫請求,則須要等待。對於寫請求:若本身不是序號最小的子節點,那麼須要等待。
4. 接收到Watcher通知後,重複步驟1。
③ 釋放鎖,其釋放鎖的流程與獨佔鎖一致。
上述共享鎖的實現方案,能夠知足通常分佈式集羣競爭鎖的需求,可是若是機器規模擴大會出現一些問題,下面着重分析判斷讀寫順序的步驟3。
針對如上圖所示的狀況進行分析
1. host1首先進行讀操做,完成後將節點/shared_lock/host1-R-00000001刪除。
2. 餘下4臺機器均收到這個節點移除的通知,而後從新從/shared_lock節點上獲取一份新的子節點列表。
3. 每臺機器判斷本身的讀寫順序,其中host2檢測到本身序號最小,因而進行寫操做,餘下的機器則繼續等待。
4. 繼續...
能夠看到,host1客戶端在移除本身的共享鎖後,Zookeeper發送了子節點更變Watcher通知給全部機器,然而除了給host2產生影響外,對其餘機器沒有任何做用。大量的Watcher通知和子節點列表獲取兩個操做會重複運行,這樣會形成系能鞥影響和網絡開銷,更爲嚴重的是,若是同一時間有多個節點對應的客戶端完成事務或事務中斷引發節點小時,Zookeeper服務器就會在短期內向其餘全部客戶端發送大量的事件通知,這就是所謂的羊羣效應。
能夠有以下改動來避免羊羣效應。
1. 客戶端調用create接口常見相似於/shared_lock/[Hostname]-請求類型-序號的臨時順序節點。
2. 客戶端調用getChildren接口獲取全部已經建立的子節點列表(不註冊任何Watcher)。
3. 若是沒法獲取共享鎖,就調用exist接口來對比本身小的節點註冊Watcher。對於讀請求:向比本身序號小的最後一個寫請求節點註冊Watcher監聽。對於寫請求:向比本身序號小的最後一個節點註冊Watcher監聽。
4. 等待Watcher通知,繼續進入步驟2。
此方案改動主要在於:每一個鎖競爭者,只須要關注/shared_lock節點下序號比本身小的那個節點是否存在便可。
分佈式隊列能夠簡單分爲先入先出隊列模型和等待隊列元素彙集後統一安排處理執行的Barrier模型。
① FIFO先入先出,先進入隊列的請求操做先完成後,纔會開始處理後面的請求。FIFO隊列就相似於全寫的共享模型,全部客戶端都會到/queue_fifo這個節點下建立一個臨時節點,如/queue_fifo/host1-00000001。
建立完節點後,按照以下步驟執行。
1. 經過調用getChildren接口來獲取/queue_fifo節點的全部子節點,即獲取隊列中全部的元素。
2. 肯定本身的節點序號在全部子節點中的順序。
3. 若是本身的序號不是最小,那麼須要等待,同時向比本身序號小的最後一個節點註冊Watcher監聽。
4. 接收到Watcher通知後,重複步驟1。
② Barrier分佈式屏障,最終的合併計算須要基於不少並行計算的子結果來進行,開始時,/queue_barrier節點已經默認存在,而且將結點數據內容賦值爲數字n來表明Barrier值,以後,全部客戶端都會到/queue_barrier節點下建立一個臨時節點,例如/queue_barrier/host1。
建立完節點後,按照以下步驟執行。
1. 經過調用getData接口獲取/queue_barrier節點的數據內容,如10。
2. 經過調用getChildren接口獲取/queue_barrier節點下的全部子節點,同時註冊對子節點變動的Watcher監聽。
3. 統計子節點的個數。
4. 若是子節點個數還不足10個,那麼須要等待。
5. 接受到Wacher通知後,重複步驟3
上邊咱們介紹了Zookeeper的典型的應用場景。zookeeper已經被普遍應用於愈來愈多的大型分佈式系統中了,其中包括:Dubbo的註冊中心,HDFS的namenode和YARN框架的ResourceManager的HA(用zookeeper解決單點問題實現HA),HBase,Kafka等大數據和分佈式系統框架中。咱們能夠學習這些內容時,注意一下Zookeeper的具體的應用實現。
————————————————
版權聲明:本文爲CSDN博主「冷麪寒槍biu」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接及本聲明。
原文連接:https://blog.csdn.net/u013679744/article/details/79371022