ZooKeeper還可以用作其他用途,例如:
- 數據發布與訂閱(配置中心)
- 負載均衡
- 命名服務(Naming Service)
- 分布式通知/協調
- 集群管理與Master選舉
- 分布式鎖
- 分布式隊列
一些在線系統在運行中,需要在不停止程序的情況下能夠動態調整某一個變量的值並且能夠及時生效。特別是當部署了多台應用服務器的時候,需要能夠做到在一台機器上修改配置文件,然后在同步到所有應用服務器。這時候使用ZooKeeper來實現就很合適了。
ZooKeeper是一個高可用的分布式數據管理與系統協調框架。基於對Paxos算法的實現,使該框架保證了分布式環境中數據的強一致性,也正是基於這樣的特性,使得ZooKeeper解決很多分布式問題。網上對ZK的應用場景也有不少介紹,本文將結合作者身邊的項目例子,系統地對ZK的應用場景進行一個分門歸類的介紹。
值得注意的是,ZK並非天生就是為這些應用場景設計的,都是后來眾多開發者根據其框架的特性,利用其提供的一系列API接口(或者稱為原語集),摸索出來的典型使用方法。因此,也非常歡迎讀者分享你在ZK使用上的奇技淫巧。
ZooKeeper典型應用場景一覽數據發布與訂閱(配置中心)
發布與訂閱模型,即所謂的配置中心,顧名思義就是發布者將數據發布到ZK節點上,供訂閱者動態獲取數據,實現配置信息的集中式管理和動態更新。例如全局的配置信息,服務式服務框架的服務地址列表等就非常適合使用。
- 應用中用到的一些配置信息放到ZK上進行集中管理。這類場景通常是這樣:應用在啟動的時候會主動來獲取一次配置,同時,在節點上注冊一個Watcher,這樣一來,以后每次配置有更新的時候,都會實時通知到訂閱的客戶端,從來達到獲取最新配置信息的目的。
- 分布式搜索服務中,索引的元信息和服務器集群機器的節點狀態存放在ZK的一些指定節點,供各個客戶端訂閱使用。
- 分布式日志收集系統。這個系統的核心工作是收集分布在不同機器的日志。收集器通常是按照應用來分配收集任務單元,因此需要在ZK上創建一個以應用名作為path的節點P,並將這個應用的所有機器ip,以子節點的形式注冊到節點P上,這樣一來就能夠實現機器變動的時候,能夠實時通知到收集器調整任務分配。
- 系統中有些信息需要動態獲取,並且還會存在人工手動去修改這個信息的發問。通常是暴露出接口,例如JMX接口,來獲取一些運行時的信息。引入ZK之后,就不用自己實現一套方案了,只要將這些信息存放到指定的ZK節點上即可。
注意:在上面提到的應用場景中,有個默認前提是:數據量很小,但是數據更新可能會比較快的場景。
負載均衡
這里說的負載均衡是指軟負載均衡。在分布式環境中,為了保證高可用性,通常同一個應用或同一個服務的提供方都會部署多份,達到對等服務。而消費者就須要在這些對等的服務器中選擇一個來執行相關的業務邏輯,其中比較典型的是消息中間件中的生產者,消費者負載均衡。
消息中間件中發布者和訂閱者的負載均衡,linkedin開源的KafkaMQ和阿里開源的metaq都是通過zookeeper來做到生產者、消費者的負載均衡。這里以metaq為例如講下:
- 生產者負載均衡:metaq發送消息的時候,生產者在發送消息的時候必須選擇一台broker上的一個分區來發送消息,因此metaq在運行過程中,會把所有broker和對應的分區信息全部注冊到ZK指定節點上,默認的策略是一個依次輪詢的過程,生產者在通過ZK獲取分區列表之后,會按照brokerId和partition的順序排列組織成一個有序的分區列表,發送的時候按照從頭到尾循環往復的方式選擇一個分區來發送消息。
-
消費負載均衡:
在消費過程中,一個消費者會消費一個或多個分區中的消息,但是一個分區只會由一個消費者來消費。MetaQ的消費策略是:
- 每個分區針對同一個group只掛載一個消費者。
- 如果同一個group的消費者數目大於分區數目,則多出來的消費者將不參與消費。
- 如果同一個group的消費者數目小於分區數目,則有部分消費者需要額外承擔消費任務。
命名服務(Naming Service)
命名服務也是分布式系統中比較常見的一類場景。在分布式系統中,通過使用命名服務,客戶端應用能夠根據指定名字來獲取資源或服務的地址,提供者等信息。被命名的實體通常可以是集群中的機器,提供的服務地址,遠程對象等等——這些我們都可以統稱他們為名字(Name)。其中較為常見的就是一些分布式服務框架中的服務地址列表。通過調用ZK提供的創建節點的API,能夠很容易創建一個全局唯一的path,這個path就可以作為一個名稱。
阿里巴巴集團開源的分布式服務框架Dubbo中使用ZooKeeper來作為其命名服務,維護全局的服務地址列表,點擊這里查看Dubbo開源項目。在Dubbo實現中:
服務提供者在啟動的時候,向ZK上的指定節點/dubbo/${serviceName}/providers目錄下寫入自己的URL地址,這個操作就完成了服務的發布。
服務消費者啟動的時候,訂閱/dubbo/${serviceName}/providers目錄下的提供者URL地址, 並向/dubbo/${serviceName} /consumers目錄下寫入自己的URL地址。
注意,所有向ZK上注冊的地址都是臨時節點,這樣就能夠保證服務提供者和消費者能夠自動感應資源的變化。
另外,Dubbo還有針對服務粒度的監控,方法是訂閱/dubbo/${serviceName}目錄下所有提供者和消費者的信息。
分布式通知/協調
ZooKeeper中特有watcher注冊與異步通知機制,能夠很好的實現分布式環境下不同系統之間的通知與協調,實現對數據變更的實時處理。使用方法通常是不同系統都對ZK上同一個znode進行注冊,監聽znode的變化(包括znode本身內容及子節點的),其中一個系統update了znode,那么另一個系統能夠收到通知,並作出相應處理
- 另一種心跳檢測機制:檢測系統和被檢測系統之間並不直接關聯起來,而是通過zk上某個節點關聯,大大減少系統耦合。
- 另一種系統調度模式:某系統有控制台和推送系統兩部分組成,控制台的職責是控制推送系統進行相應的推送工作。管理人員在控制台作的一些操作,實際上是修改了ZK上某些節點的狀態,而ZK就把這些變化通知給他們注冊Watcher的客戶端,即推送系統,於是,作出相應的推送任務。
- 另一種工作匯報模式:一些類似於任務分發系統,子任務啟動后,到zk來注冊一個臨時節點,並且定時將自己的進度進行匯報(將進度寫回這個臨時節點),這樣任務管理者就能夠實時知道任務進度。 總之,使用zookeeper來進行分布式通知和協調能夠大大降低系統之間的耦合
集群管理與Master選舉
-
集群機器監控:這通常用於那種對集群中機器狀態,機器在線率有較高要求的場景,能夠快速對集群中機器變化作出響應。這樣的場景中,往往有一個監控系統,實時檢測集群機器是否存活。過去的做法通常是:監控系統通過某種手段(比如ping)定時檢測每個機器,或者每個機器自己定時向監控系統匯報“我還活着”。 這種做法可行,但是存在兩個比較明顯的問題:
- 集群中機器有變動的時候,牽連修改的東西比較多。
- 有一定的延時。
利用ZooKeeper有兩個特性,就可以實時另一種集群機器存活性監控系統:
- 客戶端在節點 x 上注冊一個Watcher,那么如果 x?的子節點變化了,會通知該客戶端。
- 創建EPHEMERAL類型的節點,一旦客戶端和服務器的會話結束或過期,那么該節點就會消失。
-
Master選舉則是zookeeper中最為經典的應用場景了。 在分布式環境中,相同的業務應用分布在不同的機器上,有些業務邏輯(例如一些耗時的計算,網絡I/O處理),往往只需要讓整個集群中的某一台機器進行執行,其余機器可以共享這個結果,這樣可以大大減少重復勞動,提高性能,於是這個master選舉便是這種場景下的碰到的主要問題。
利用ZooKeeper的強一致性,能夠保證在分布式高並發情況下節點創建的全局唯一性,即:同時有多個客戶端請求創建 /currentMaster 節點,最終一定只有一個客戶端請求能夠創建成功。利用這個特性,就能很輕易的在分布式環境中進行集群選取了。
另外,這種場景演化一下,就是動態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的單點問題
分布式鎖
分布式鎖,這個主要得益於ZooKeeper為我們保證了數據的強一致性。鎖服務可以分為兩類,一個是保持獨占,另一個是控制時序。
- 所謂保持獨占,就是所有試圖來獲取這個鎖的客戶端,最終只有一個可以成功獲得這把鎖。通常的做法是把zk上的一個znode看作是一把鎖,通過create znode的方式來實現。所有客戶端都去創建 /distribute_lock 節點,最終成功創建的那個客戶端也即擁有了這把鎖。
- 控制時序,就是所有視圖來獲取這個鎖的客戶端,最終都是會被安排執行,只是有個全局時序了。做法和上面基本類似,只是這里/distribute_lock 已經預先存在,客戶端在它下面創建臨時有序節點(這個可以通過節點的屬性控制:CreateMode.EPHEMERAL_SEQUENTIAL來指定)。Zk的父節點(/distribute_lock)維持一份sequence,保證子節點創建的時序性,從而也形成了每個客戶端的全局時序。
分布式隊列
隊列方面,簡單地講有兩種,一種是常規的先進先出隊列,另一種是要等到隊列成員聚齊之后的才統一按序執行。對於第一種先進先出隊列,和分布式鎖服務中的控制時序場景基本原理一致,這里不再贅述。
第二種隊列其實是在FIFO隊列的基礎上作了一個增強。通常可以在 /queue 這個znode下預先建立一個/queue/num 節點,並且賦值為n(或者直接給/queue賦值n),表示隊列大小,之后每次有隊列成員加入后,就判斷下是否已經到達隊列大小,決定是否可以開始執行了。這種用法的典型場景是,分布式環境中,一個大任務Task A,需要在很多子任務完成(或條件就緒)情況下才能進行。這個時候,凡是其中一個子任務完成(就緒),那么就去 /taskList 下建立自己的臨時時序節點 (CreateMode.EPHEMERAL_SEQUENTIAL),當 /taskList 發現自己下面的子節點滿足指定個數,就可以進行下一步按序進行處理了。
轉載至:http://rdc.taobao.com/team/jm/archives/1232
分布式網站架構后續:zookeeper技術淺析
Zookeeper是hadoop的一個子項目,雖然源自hadoop,但是我發現zookeeper脫離hadoop的范疇開發分布式框架的運用越來越多。今天我想談談zookeeper,本文不談如何使用zookeeper,而是zookeeper到底有哪些實際的運用,哪些類型的應用能發揮zookeeper的優勢,最后談談zookeeper對分布式網站架構能產生怎樣的作用。
Zookeeper是針對大型分布式系統的高可靠的協調系統。由這個定義我們知道zookeeper是個協調系統,作用的對象是分布式系統。為什么分布式系統需要一個協調系統了?理由如下:
開發分布式系統是件很困難的事情,其中的困難主要體現在分布式系統的“部分失敗”。“部分失敗”是指信息在網絡的兩個節點之間傳送時候,如果網絡出了故障,發送者無法知道接收者是否收到了這個信息,而且這種故障的原因很復雜,接收者可能在出現網絡錯誤之前已經收到了信息,也可能沒有收到,又或接收者的進程死掉了。發送者能夠獲得真實情況的唯一辦法就是重新連接到接收者,詢問接收者錯誤的原因,這就是分布式系統開發里的“部分失敗”問題。
Zookeeper就是解決分布式系統“部分失敗”的框架。Zookeeper不是讓分布式系統避免“部分失敗”問題,而是讓分布式系統當碰到部分失敗時候,可以正確的處理此類的問題,讓分布式系統能正常的運行。
下面我要講講zookeeper的實際運用場景:
場景一:有一組服務器向客戶端提供某種服務(例如:我前面做的分布式網站的服務端,就是由四台服務器組成的集群,向前端集群提供服務),我們希望客戶端每次請求服務端都可以找到服務端集群中某一台服務器,這樣服務端就可以向客戶端提供客戶端所需的服務。對於這種場景,我們的程序中一定有一份這組服務器的列表,每次客戶端請求時候,都是從這份列表里讀取這份服務器列表。那么這分列表顯然不能存儲在一台單節點的服務器上,否則這個節點掛掉了,整個集群都會發生故障,我們希望這份列表時高可用的。高可用的解決方案是:這份列表是分布式存儲的,它是由存儲這份列表的服務器共同管理的,如果存儲列表里的某台服務器壞掉了,其他服務器馬上可以替代壞掉的服務器,並且可以把壞掉的服務器從列表里刪除掉,讓故障服務器退出整個集群的運行,而這一切的操作又不會由故障的服務器來操作,而是集群里正常的服務器來完成。這是一種主動的分布式數據結構,能夠在外部情況發生變化時候主動修改數據項狀態的數據機構。Zookeeper框架提供了這種服務。這種服務名字就是:統一命名服務,它和javaEE里的JNDI服務很像。
場景二:分布式鎖服務。當分布式系統操作數據,例如:讀取數據、分析數據、最后修改數據。在分布式系統里這些操作可能會分散到集群里不同的節點上,那么這時候就存在數據操作過程中一致性的問題,如果不一致,我們將會得到一個錯誤的運算結果,在單一進程的程序里,一致性的問題很好解決,但是到了分布式系統就比較困難,因為分布式系統里不同服務器的運算都是在獨立的進程里,運算的中間結果和過程還要通過網絡進行傳遞,那么想做到數據操作一致性要困難的多。Zookeeper提供了一個鎖服務解決了這樣的問題,能讓我們在做分布式數據運算時候,保證數據操作的一致性。
場景三:配置管理。在分布式系統里,我們會把一個服務應用分別部署到n台服務器上,這些服務器的配置文件是相同的(例如:我設計的分布式網站框架里,服務端就有4台服務器,4台服務器上的程序都是一樣,配置文件都是一樣),如果配置文件的配置選項發生變化,那么我們就得一個個去改這些配置文件,如果我們需要改的服務器比較少,這些操作還不是太麻煩,如果我們分布式的服務器特別多,比如某些大型互聯網公司的hadoop集群有數千台服務器,那么更改配置選項就是一件麻煩而且危險的事情。這時候zookeeper就可以派上用場了,我們可以把zookeeper當成一個高可用的配置存儲器,把這樣的事情交給zookeeper進行管理,我們將集群的配置文件拷貝到zookeeper的文件系統的某個節點上,然后用zookeeper監控所有分布式系統里配置文件的狀態,一旦發現有配置文件發生了變化,每台服務器都會收到zookeeper的通知,讓每台服務器同步zookeeper里的配置文件,zookeeper服務也會保證同步操作原子性,確保每個服務器的配置文件都能被正確的更新。
場景四:為分布式系統提供故障修復的功能。集群管理是很困難的,在分布式系統里加入了zookeeper服務,能讓我們很容易的對集群進行管理。集群管理最麻煩的事情就是節點故障管理,zookeeper可以讓集群選出一個健康的節點作為master,master節點會知道當前集群的每台服務器的運行狀況,一旦某個節點發生故障,master會把這個情況通知給集群其他服務器,從而重新分配不同節點的計算任務。Zookeeper不僅可以發現故障,也會對有故障的服務器進行甄別,看故障服務器是什么樣的故障,如果該故障可以修復,zookeeper可以自動修復或者告訴系統管理員錯誤的原因讓管理員迅速定位問題,修復節點的故障。大家也許還會有個疑問,master故障了,那怎么辦了?zookeeper也考慮到了這點,zookeeper內部有一個“選舉領導者的算法”,master可以動態選擇,當master故障時候,zookeeper能馬上選出新的master對集群進行管理。
下面我要講講zookeeper的特點:
- zookeeper是一個精簡的文件系統。這點它和hadoop有點像,但是zookeeper這個文件系統是管理小文件的,而hadoop是管理超大文件的。
- zookeeper提供了豐富的“構件”,這些構件可以實現很多協調數據結構和協議的操作。例如:分布式隊列、分布式鎖以及一組同級節點的“領導者選舉”算法。
- zookeeper是高可用的,它本身的穩定性是相當之好,分布式集群完全可以依賴zookeeper集群的管理,利用zookeeper避免分布式系統的單點故障的問題。
- zookeeper采用了松耦合的交互模式。這點在zookeeper提供分布式鎖上表現最為明顯,zookeeper可以被用作一個約會機制,讓參入的進程不在了解其他進程的(或網絡)的情況下能夠彼此發現並進行交互,參入的各方甚至不必同時存在,只要在zookeeper留下一條消息,在該進程結束后,另外一個進程還可以讀取這條信息,從而解耦了各個節點之間的關系。
- zookeeper為集群提供了一個共享存儲庫,集群可以從這里集中讀寫共享的信息,避免了每個節點的共享操作編程,減輕了分布式系統的開發難度。
- zookeeper的設計采用的是觀察者的設計模式,zookeeper主要是負責存儲和管理大家關心的數據,然后接受觀察者的注冊,一旦這些數據的狀態發生變化,Zookeeper 就將負責通知已經在 Zookeeper 上注冊的那些觀察者做出相應的反應,從而實現集群中類似 Master/Slave 管理模式。
由此可見zookeeper很利於分布式系統開發,它能讓分布式系統更加健壯和高效。
前不久我參加了部門的hadoop興趣小組,測試環境的hadoop、mapreduce、hive及hbase都是我來安裝的,安裝hbase時候安裝要預先安裝zookeeper,最早我是在四台服務器上都安裝了zookeeper,但是同事說安裝四台和安裝三台是一回事,這是因為zookeeper要求半數以上的機器可用,zookeeper才能提供服務,所以3台的半數以上就是2台了,4台的半數以上也是兩台,因此裝了三台服務器完全可以達到4台服務器的效果,這個問題說明zookeeper進行安裝的時候通常選擇奇數台服務器。在學習hadoop的過程中,我感覺zookeeper是最難理解的一個子項目,原因倒不是它技術負責,而是它的應用方向很讓我困惑,所以我有關hadoop技術第一篇文章就從zookeeper開始,也不講具體技術實現,而從zookeeper的應用場景講起,理解了zookeeper應用的領域,我想再學習zookeeper就會更加事半功倍。
之所以今天要談談zookeeper,也是為我上一篇文章分布式網站框架的補充。雖然我設計網站架構是分布式結構,也做了簡單的故障處理機制,比如:心跳機制,但是對集群的單點故障還是沒有辦法的,如果某一台服務器壞掉了,客戶端任然會嘗試連接這個服務器,導致部分請求的阻塞,也會導致服務器資源的浪費。不過我目前也不想去修改自己的框架,因為我總覺得在現有的服務上添加zookeeper服務會影響網站的效率,如果有獨立的服務器集群部署zookeeper還是值得考慮的,但是服務器資源太寶貴了,這個可能性不大。幸好我們部門也發現了這樣的問題,我們部門將開發一個強大的遠程調用框架,將集群管理和通訊管理這塊剝離出來,集中式提供高效可用的服務,等部門的遠程框架開發完畢,我們的網站加入新的服務,我想我們的網站將會更加穩定和高效。
哈哈,講了這么久估計有童鞋可能有點煩了,不是說應用zookeeper的實例嗎?怎么還沒見到zookeeper的影子。別着急,zookeeper馬上就要上場了。
還是以我前面博客里寫分布式網站講起,服務端系統我們可以當做服務提供者,前端系統當做服務調用者,提供者可以類比商戶,調用者可以類比客戶,商戶和客戶可以直接進行交易,這種直接交易方式非常原始甚至還會有風險,現代社會商戶和客戶直接的交易十分高效,高效的原因是因為有一個規范的大市場,商戶和客戶的交易在市場里進行的,這樣交易會變得更加安全和高效,我設計的分布式框架最大的特點就是提供了一個類似市場的角色,它來管理服務提供者和服務調用者,我把這個功能模塊稱為遠程調用管理組件。
遠程調用管理組件是本框架的核心,它的主要作用是接收服務端提供者的注冊的通知,該通知一般是接口以及該接口的實現類還有服務器的ip地址,管理組件會將這些通知記錄下來,並且根據配置對這些服務程序進行分組和標記,注冊好的信息管理組件會將這些信息推送到服務調用者。遠程調用管理組件還包含心跳機制,這個心跳機制是針對服務提供者,通過心跳機制檢測服務提供者的健康狀況,管理組件不會檢測服務調用者的健康狀態,因為這個沒必要,因為本框架的使用還是調用者直接去請求提供者,邏輯上是沒必要關心調用者的狀態,這和bs架構里瀏覽器一樣,我們不會去關心瀏覽器用戶是不是存在。服務提供者、服務調用者和遠程調用管理組件的關系如下圖所示:
遠程調用框架運行的過程是:當服務提供者啟動時候,它會將自己的ip地址和注冊的方法傳輸到遠程調用管理組件,管理組件接收到注冊信息會將這些信息存儲下來,存儲技術就是使用zookeeper,存儲成功后,管理組件會將成功通知傳回給服務提供者,同時管理組件還會通過心跳檢測服務提供者是否健康;當服務調用者啟動時候,它會向管理組件請求服務提供者信息,管理組件接收到請求后會將相關信息推送給服務調用者。在實際系統運行時候,服務調用者直接和服務提供者進行通信交互了,通信方式是netty,如果調用者和提供者有相關變化,都會先通知服務管理組件,服務管理組件會將相關變更信息推送給相應的系統。
遠程調用管理組件主要是通過zookeeper實現,zookeeper擁有一個層次的命名空間,它的模型是一個樹狀結構,樹狀結構是一個強大的數據類型,它幾乎能存儲所有不同的數據類型,我們通過zookeeper將這些信息保存起來,便於我們管理整個遠程調用框架,同時zookeeper還是高可靠的,這個我在前面zookeeper文章里講到了,這樣就保證了整個遠程調用框架的穩定性,實際應用中我們會將組件編譯成一個jar包,不同的項目直接引用這個jar包,這樣管理組件服務端和服務的提供者和調用者就聯系起來。至於提供者和調用者的通信機制是直接進行,因為我們將通信程序集成在jar包里,只不過相應的管理機制抽取到外部服務端進行統一管理。
這就是我設計的遠程調用框架,可惜的是,這個構思我還沒有真正實現過,今天拿出來是想體現zookeeper的實際應用,為我后面講解zookeeper做鋪墊,至於是否可行,看以后有沒有機會開發個類似的系統,到時估計還有很多意想不到的問題要解決。