文章目錄
摘要
在本文中,我們描述了ZooKeeper,一種用於協調分佈式應用的服務。由於ZooKeeper是關鍵基礎設施的一部分,因此ZooKeeper旨在提供一個簡單而高性能的內核,以在客戶端構建更復雜的協調原語。它在複製的集中式服務中綜合了組消息傳遞、共享寄存器和分佈式鎖服務的原理。ZooKeeper暴露的接口具有共享寄存器的無等待特性,它具有類似於分佈式文件系統的緩存失效的事件驅動機制,以提供簡單而強大的協調服務。
ZooKeeper接口可實現高性能服務。除了無等待特性之外,ZooKeeper還爲每個客戶端提供了保證請求的FIFO執行及所有更改ZooKeeper狀態的請求的線性一致性。這些設計決策可以實現高性能處理流水線,而本地服務器可以滿足讀取請求。對於目標工作負載,我們展示了2:1到100:1的讀寫比,ZooKeeper每秒可以處理成千上萬個事務。這種性能使ZooKeeper可以被客戶端應用廣泛使用。
1. 簡介
大型分佈式應用需要不同形式的協調。配置是最基本的協調形式之一。在最簡單的形式中,配置只是系統進程的操作參數列表,而更復雜的系統具有動態配置參數。組成員身份認證和領導者選舉在分佈式系統中也很常見:通常,進程需要知道哪些其他進程仍在運行以及這些進程負責什麼。鎖構成了強大的協調原語,實現了對關鍵資源的互斥訪問。
一種協調方法是爲每種不同的協調需求開發服務。例如,Amazon Simple Queue Service
[3]特別專注於排隊。針對領導者選舉[25]和配置[27]也開發了其他服務。實現功能更強大的原語的服務可用於實現功能更弱的原語。例如,Chubby[6]是具有強同步保證的鎖服務。然後可以使用鎖來執行領導者選舉、組成員身份認證等。
在設計協調服務時,我們不再在服務器端實現特定的原語,而是選擇暴露一個API,使應用開發人員可以實現自己的原語。這種選擇導致實現了一個協調內核,該協調內核無需更改服務內核即可啓用新原語。這種方法使得適配於應用需求的多種形式的協調成爲可能,而不是將開發人員限制在一組固定的原語上。
在設計ZooKeeper的API時,我們不再使用阻塞原語,例如鎖。阻塞協調服務的原語可能會導致客戶端變慢或出現故障,從而對更快的客戶端的性能產生負面影響。如果處理請求依賴於其他客戶端的響應和故障檢測,則服務本身的實現將變得更加複雜。因此,我們的Zookeeper系統實現了一個API,該API可以像文件系統一樣操作按層次結構組織的簡單的無等待數據對象。實際上,ZooKeeper API類似於任何其他文件系統,並且僅查看API簽名,ZooKeeper似乎是沒有鎖方法、打開和關閉操作的Chubby。但是,實現無等待數據對象使ZooKeeper與基於鎖之類的阻塞原語的系統明顯不同。
儘管無等待特性對於性能和容錯性很重要,但還不足以進行協調。我們還必須爲操作提供順序保證。特別是,我們發現保證所有操作的FIFO客戶端順序和線性化寫入都使服務的高效實現成爲可能,並且足以實現應用感興趣的協調原語。實際上,我們可以使用我們的API對任何數量的進程實現一致性,並且根據Herlihy的層次結構,ZooKeeper實現了通用對象[14]。
ZooKeeper服務由使用複製以實現高可用和高性能的服務器組成。它的高性能使包含大量進程的應用可以使用這種協調內核來管理協調的各個方面。我們能夠使用簡單的流水線體系結構實現ZooKeeper,該體系結構使我們能夠處理數百或數千個請求,同時仍實現低延遲。這樣的流水線自然可以使來自單個客戶端的操作按FIFO順序執行。保證FIFO客戶端順序使客戶端可以異步提交操作。使用異步操作,客戶端一次可以執行多個未完成的操作。例如,當新客戶端成爲領導者並且必須操縱元數據並相應地對其進行更新時,此功能是滿足要求的。由於不可能進行多個未完成的操作,因此初始化時間可能是幾秒左右,而不是亞秒級。
爲了保證更新操作滿足線性化要求,我們實現了基於領導者的原子廣播協議[23],稱爲Zab[24]。但是,ZooKeeper應用的典型工作負載由讀取操作主導,因此需要擴展讀取吞吐量。在ZooKeeper中,服務器在本地處理讀取操作,我們不使用Zab對其進行完全排序。
在客戶端緩存數據是提高讀取性能的一項重要技術。例如,對於一個進程來說,緩存當前領導者的標識而不是在每次需要了解該領導者時探查ZooKeeper是有用的。ZooKeeper使用監聽機制使客戶端可以緩存數據,而無需直接管理客戶端緩存。使用此機制,客戶端可以監聽對給定數據對象的更新,並在更新時接收通知。Chubby直接管理客戶端緩存。它阻塞更新以使所有緩存更改數據的客戶端的緩存無效。在這種設計下,如果這些客戶端中的任何一個緩慢或出現故障,更新都會延遲。Chubby使用租約來防止有故障的客戶端無限期地阻塞系統。但是,租約只能限制慢速或故障客戶端的影響,而ZooKeeper監聽完全可以避免此問題。
在本文中,我們討論了ZooKeeper的設計和實現。使用ZooKeeper(儘管只有寫入是可線性化的),我們也可以實現應用所需的所有協調原語。爲了驗證我們的方法,我們展示瞭如何使用ZooKeeper實現一些協調原語。
總而言之,本文主要貢獻是:
- 協調內核:我們提出了一種無等待的協調服務,該服務具有寬鬆的一致性保證,可用於分佈式系統。特別地,我們描述了協調內核的設計和實現,我們已經在許多關鍵應用中使用了該協調內核來實現各種協調技術。
- 協調方案:我們展示了ZooKeeper如何可用於構建通常在分佈式應用中使用的高級協調原語,甚至是阻塞和強一致性原語。
- 協調經驗:我們分享一些使用ZooKeeper的方式並評估其性能。
2. ZooKeeper服務
客戶端使用ZooKeeper客戶端庫通過客戶端API向ZooKeeper提交請求。除了通過客戶端API暴露ZooKeeper服務接口之外,客戶端庫還管理客戶端和ZooKeeper服務器之間的網絡連接。
在本節中,我們首先提供ZooKeeper服務的高層級視圖。然後,我們討論客戶端用來與ZooKeeper交互的API。
術語。在本文中,我們使用客戶端來表示ZooKeeper服務的用戶,使用服務器來表示提供ZooKeeper服務的進程,使用znode來表示ZooKeeper數據中的內存數據節點,該數據節點以分層命名空間(被稱爲數據樹)組織。我們還使用術語“更新和寫入”來指代任何修改數據樹狀態的操作。客戶端在連接到ZooKeeper並獲得會話句柄以發出請求時建立會話。
2.1 服務概述
ZooKeeper向其客戶端提供了根據分層命名空間組織的一組數據節點(znode)的抽象。此層次結構中的znode是客戶端通過ZooKeeper API操作的數據對象。分層命名空間在文件系統中常常使用。這是組織數據對象的一種理想方式,因爲用戶已經習慣了這種抽象,並且可以更好地組織應用元數據。要引用給定的znode,我們爲文件系統路徑使用標準UNIX表示法。例如,我們使用/A/B/C
表示到znode C的路徑,其中C以B爲父節點,B以A爲父節點。所有znode都存儲數據,並且除臨時znode之外的所有znode都可以有子節點。
客戶端可以創建兩種類型的znode:
- 常規的:客戶端通過顯式創建和刪除來操縱常規znode。
- 臨時的:客戶端創建此類znode,它們要麼顯式刪除它們,要麼讓系統在創建它們的會話終止時(有意或由於故障)自動刪除它們。
此外,在創建新的znode時,客戶端可以設置順序標誌。使用順序標誌集創建的節點在其名稱後附加一個單調遞增計數器的值。如果n是新的znode,p是父znode,則n的序列值永遠不會小於在p下已創建的任何其他序列znode名稱中的值。
ZooKeeper實行監聽,以使客戶端能夠及時收到更改通知,而無需輪詢。當客戶端發出帶有監聽標誌的讀取操作時,該操作將正常完成,除了服務器承諾在返回的信息已更改時會通知客戶端。監聽是與會話相關的一次性觸發器;一旦已觸發或會話關閉,它們將取消註冊。監聽表明發生了更改,但未提供更改。例如,如果客戶端在“/foo”被再次更改之前發佈了一個getData('/foo',true)
消息,則客戶端將收到一個監聽事件,告知客戶端“/foo”的數據已更改。會話事件(例如連接丟失事件)也將發送到監聽回調,因此客戶端知道監聽事件可能會延遲。
2.1.1 數據模型
ZooKeeper的數據模型本質上是一個具有簡化API且只能整體讀取和寫入數據的文件系統,或者是具有分層鍵的鍵/值表。層次命名空間對於爲不同應用的命名空間分配子樹以及設置對這些子樹的訪問權限很有用。我們還將在客戶端利用目錄的概念來構建更高級別的原語,如我們將在2.4節中看到的那樣。
與文件系統中的文件不同,znode不適用於常規數據存儲。相反,znode映射到客戶端應用的抽象,通常對應於用於協調目的的元數據。爲了說明,在圖1中,我們有兩個子樹,一個子樹用於應用1(/app1),另一個子樹用於應用2(/app2)。應用1的子樹實現了一個簡單的組成員身份認證協議:每個客戶端進程pi在/app1下創建一個znode p_i,只要該進程正在運行,該節點便會持續存在。
儘管znode不是設計用於常規數據存儲,但是ZooKeeper確實允許客戶端存儲一些可用於分佈式計算中的元數據或配置的信息。例如,在基於領導者的應用中,這對於剛剛開始瞭解哪個其他服務器當前是領導者的應用服務器很有用。爲了實現此目標,我們可以讓當前的領導者在znode空間中的已知位置寫入此信息。Znode還具有帶有時間戳和版本計數器的相關元數據,這使客戶端可以跟蹤對znode的更改並根據znode的版本執行條件更新。
2.1.2 會話
客戶端連接到ZooKeeper並啓動會話。會話具有關聯的超時時間。如果ZooKeeper在超時時間內沒有收到來自會話的任何消息,則認爲該客戶端有故障。當客戶端明確關閉會話句柄或ZooKeeper檢測到客戶端故障時,會話結束。在會話中,客戶端觀察到一系列狀態變化,這些狀態變化反映了其操作的執行。會話使客戶端可以在全體ZooKeeper中從一臺服務器透明地移動到另一臺服務器,因此可以在ZooKeeper服務器之間持久存在。
2.2 客戶端API
我們在下面提供ZooKeeper API的相關子集,並討論每個請求的語義。
- create(path, data, flags):創建路徑名稱爲path的znode,在其中存儲data[],並返回新znode的名稱。flags使客戶端可以選擇znode的類型:常規的、臨時的,並設置順序標誌;
- delete(path, version):如果該znode處於預期版本,則刪除該znode路徑;
- exists(path, watch):如果路徑名稱爲path的znode存在,則返回true,否則返回false。watch標誌使客戶端可以在znode上設置監聽器;
- getData(path, watch):返回與znode關聯的數據和元數據,例如版本信息。watch標誌的工作方式與
exist()
相同,不同之處在於,如果znode不存在,ZooKeeper不會設置監聽器; - setData(path, data, version):如果版本號是znode的當前版本,則將data[]寫入znode路徑;
- getChildren(path, watch):返回znode的子節點的名稱集合;
- sync(path):等待操作開始時所有未完成的更新傳播到客戶端連接到的服務器(path當前被忽略了)。
所有方法都可以通過API獲得同步版本和異步版本。當應用需要執行單個ZooKeeper操作並且沒有要執行的併發任務時,它會使用同步API,因此它會進行必要的ZooKeeper調用並進行阻塞。但是,異步API使應用可以同時執行多個出色的ZooKeeper操作和其他任務。ZooKeeper客戶端保證按順序調用每個操作的相應回調。
請注意,ZooKeeper不使用句柄訪問znode。相反,每個請求都包含正在操作的znode的完整路徑。這種選擇不僅簡化了API(沒有open()
或close()
方法),而且還消除了服務器需要維護的額外狀態。
每個更新方法均接收期望的版本號參數,從而可以實現條件更新。如果znode的實際版本號與期望版本號不匹配,則更新將以意外的版本號錯誤而失敗。如果版本號爲-1,則不執行版本檢查。
2.3 ZooKeeper保證
ZooKeeper具有兩個基本的排序保證:
- 線性寫入:所有更新ZooKeeper狀態的請求都是可線性化的,並且遵循優先級;
- FIFO客戶端排序:來自給定客戶端的所有請求均按客戶端發送的順序執行。
請注意,我們對線性化的定義與Herlihy[15]最初提出的定義不同,我們稱其爲A-線性化(異步線性化)。在Herlihy最初對線性化的定義中,客戶端一次只能執行一項未完成的操作(客戶端是一個線程)。在我們的系統中,我們允許一個客戶端執行多個未完成的操作,因此,我們可以選擇保證不爲同一客戶端的未完成操作指定特定的順序或保證FIFO順序。我們選擇後者作爲我們的特性。重要的是可以看到,所有可線性化對象的結果也適用於A-線性化對象,因爲滿足A-線性化能力的系統也滿足線性化能力。因爲只有更新請求才是A-線性化的,所以ZooKeeper會在每個副本本地處理讀取請求。這允許服務在服務器添加到系統中時線性擴展。
若要查看這兩個保證如何相互作用,請考慮以下情形。包含多個進程的系統選舉領導者控制工作者進程。當新的領導者負責系統時,必須更改大量配置參數,並在完成後通知其他進程。然後,我們有兩個重要要求:
- 當新領導者開始進行更改時,我們不希望其他進程開始使用正在更改的配置。
- 如果新領導者在完全更新配置之前死掉,則我們不希望進程使用此部分配置。
注意到分佈式鎖(例如Chubby提供的鎖)將有助於滿足第一個要求,但不足以滿足第二個要求。使用ZooKeeper,新的領導者可以將路徑指定爲就緒znode;其他進程將僅在該znode存在時使用配置。新領導者通過刪除就緒znode、更新各種配置znode並再創建就緒znode來進行配置更改。所有這些更改都可以流水線化並異步發佈,以快速更新配置狀態。儘管更改操作的延時約爲2ms,但是如果請求連續發出,則新領導者更新5000個不同znode的將花費10s。通過異步發出請求,請求將花費不到一秒鐘的時間。由於順序保證,如果進程看到就緒znode,則它一定看到新的領導者所做的所有配置更改。如果新領導者在創建就緒znode之前死掉,則其他進程會知道該配置尚未完成,因此將不使用它。
上面的方案仍然存在一個問題:如果在新的領導者開始進行更改之前,發現某個進程已經準備就緒,然後在進行更改的同時開始讀取配置,將會發生什麼情況。通知的排序保證解決了此問題:如果客戶端正在監聽更改,客戶端將在看到系統的新狀態之前看到通知事件(在進行更改後)。因此,如果讀取就緒znode的進程請求獲得對該znode的更改的通知,它將在看到任何新配置之前看到一條通知(通知客戶端該更改)。
當客戶端除了ZooKeeper之外還擁有自己的通信渠道時,可能會出現另一個問題。例如,考慮兩個客戶端A和B,它們在ZooKeeper中具有共享的配置,並通過共享的通信通道進行通信。如果A更改了ZooKeeper中的共享配置,並通過共享的通信通道將更改告知B,則B希望在重新讀取配置時看到更改。如果B的ZooKeeper副本稍微落後於A,則可能看不到新配置。使用以上保證,B可以通過在重新讀取配置之前發出寫入操作來確保它看到了最新信息。爲了更有效地處理這種情況,ZooKeeper提供了sync請求:在其後的讀取,構成一個慢速讀取。sync使服務器在處理讀取之前應用所有未完成的寫入請求,且不會產生全量寫入的開銷。這個原語在概念上與ISIS[5]的flush原語相似。
ZooKeeper還具有以下兩個活躍性和持久性的保證:如果大多數ZooKeeper服務器處於活躍狀態並且可以進行通信,則該服務可用;如果ZooKeeper服務成功響應更改請求,則該更改將在任何數量的故障中持久存在,只要最終能夠達到法定服務器數量即可。
2.4 原語示例
在本節中,我們將展示如何使用ZooKeeper API來實現更強大的原語。ZooKeeper服務對這些更強大的原語一無所知,因爲它們是完全使用ZooKeeper客戶端API在客戶端上實現的。一些常見的原語(例如組成員身份認證和配置管理)也是無需等待的。對於其他原語,例如會合點,客戶端需要等待事件。即使ZooKeeper無需等待,我們也可以使用ZooKeeper實現有效的阻塞原語。ZooKeeper的順序保證允許對系統狀態進行有效的推斷,而監聽則可以實現高效的等待。
2.4.1 配置管理
ZooKeeper可用於在分佈式應用中實現動態配置。以其最簡單的形式,配置存儲在znode zc中。進程以zc的完整路徑名啓動。啓動進程通過將監聽標誌設置爲true來讀取zc以獲取其配置。如果zc中的配置曾經更新過,則會通知進程並讀取新配置,從而再次將監聽標誌設置爲true。
請注意,在此方案中,就像在大多數使用監聽的方案中一樣,監聽用於確保進程具有最新信息。例如,如果向監聽zc的進程通知了zc的更改,並且在可以發出對zc的讀取之前,存在對zc的另外三個更改,則該進程不會再收到三個通知事件。這不會影響進程的行爲,因爲這三個事件只是將已知的信息通知進程:它對zc擁有的信息是過時的。
2.4.2 會合點
有時,在分佈式系統中,並非總是先驗地知道最終的系統配置是什麼樣的。例如,客戶端可能要啓動一個主進程和多個工作進程,但是啓動進程是由調度程序完成的,因此客戶端不提前知道它可以爲工作進程連接到主進程提供如地址和端口等先驗信息。我們使用ZooKeepe中的會合點znode zr處理此場景,zr是由客戶端創建的節點。客戶端將zr的完整路徑名作爲主進程和工作進程的啓動參數傳遞。當主進程啓動時,它會在zr中填充有關其正在使用的地址和端口的信息。當工作進程開始時,他們將監聽設置爲true來讀取zr。如果尚未寫入zr完成,則工作進程將在更新zr時等待通知。如果zr是臨時節點,則主進程和工作進程可以監聽zr被刪除,並在客戶端結束時自行清理。
2.4.3 組成員身份認證
我們利用臨時節點來實現組成員身份認證。具體來說,我們利用臨時節點允許我們查看創建該節點的會話狀態的事實。我們首先指定一個znode zg代表該組。當該組的某個進程成員啓動時,它將在zg下創建一個臨時子znode。如果每個進程都有唯一的名稱或標識符,則將該名稱用作子znode的名稱;否則,該過程將使用SEQUENTIAL標誌創建znode以獲得唯一的名稱分配。進程可以將進程信息放入子znode的數據中,例如該進程使用的地址和端口。
在zg下創建子znode後,該進程將正常啓動。它不需要做任何其他事情。如果該進程失敗或結束,則在zg下代表它的znode會被自動刪除。
進程可以通過簡單列出zg的子節點來獲取組信息。如果某個進程想要監聽組成員身份的更改,則該進程可以在接收到更改通知時將監聽標誌設置爲true,並刷新組信息(始終將監視標誌設置爲true)。
2.4.4 簡易鎖
儘管ZooKeeper不是鎖服務,但可以用來實現鎖。使用ZooKeeper的應用通常使用根據其需求量身定製的同步原語,例如上面所示的那些。在這裏,我們展示瞭如何使用ZooKeeper實現鎖,以表明它可以實現各種各樣的常規同步原語。
最簡單的鎖實現使用“鎖文件”。該鎖由znode表示。爲了獲取鎖,客戶端嘗試使用EPHEMERAL標誌創建指定的znode。如果創建成功,則客戶端將持有該鎖。另外,客戶端可以讀取設置了監聽標誌的znode,以便在當前領導者死亡時得到通知。客戶端死亡或顯式刪除znode時會釋放該鎖。其他等待鎖的客戶端一旦觀察到znode被刪除,就會再次嘗試獲取鎖。
儘管此簡單的鎖協議有效,但確實存在一些問題。首先,它具有羊羣效應。如果有許多等待獲取鎖的客戶端,則即使只有一個客戶端可以獲取該鎖,當鎖釋放後他們都將爭奪該鎖。其次,它僅實現互斥鎖。以下兩個原語顯示瞭如何同時解決這兩個問題。
-
沒有羊羣效應的簡易鎖
我們定義一個鎖znode l來實現這種鎖。直觀地,我們排序所有請求鎖的客戶端,每個客戶端都按照請求到達的順序獲得鎖。因此,希望獲得該鎖的客戶端執行以下操作:
Lock 1 n = create(l + “/lock-”, EPHEMERAL|SEQUENTIAL) 2 C = getChildren(l, false) 3 if n is lowest znode in C, exit 4 p = znode in C ordered just before n 5 if exists(p, true) wait for watch event 6 goto 2 Unlock 1 delete(n)
在Lock的第1行中使用SEQUENTIAL標誌決定了客戶端獲取該鎖嘗試的順序(相對於所有其他嘗試)。如果客戶端的znode在第3行的序列號最小,則客戶端將持有該鎖。否則,客戶端將等待該znode的刪除(在該客戶端的znode之前持有鎖或將獲取到鎖)。通過僅監聽客戶端znode之前的znode,我們僅在鎖釋放或鎖請求放棄時才喚醒一個進程,從而避免了羊羣效應。一旦客戶端監聽的znode消失,客戶端必須檢查它現在是否持有該鎖。(先前的鎖請求可能已被放棄,並且具有較低序號的znode仍在等待或持有該鎖。)
釋放鎖就像刪除代表鎖請求的znode n一樣簡單。通過在創建時使用EPHEMERAL標誌,崩潰的進程將自動清除所有鎖請求或釋放它們可能擁有的任何鎖。
總之,此鎖定方案具有以下優點:
- 刪除一個znode只會導致一個客戶端喚醒,因爲每個znode都恰好被另一個客戶端監視,因此我們沒有羊羣效應;
- 沒有輪詢或超時;
- 由於我們實現鎖的方式,因此通過瀏覽ZooKeeper數據可以看到鎖爭用、中斷鎖和調試鎖問題的數量。
-
讀/寫鎖
爲了實現讀/寫鎖,我們略微更改了鎖過程,並分開了讀鎖和寫鎖過程。釋放鎖過程與全局鎖情況相同。
Write Lock 1 n = create(l + “/write-”, EPHEMERAL|SEQUENTIAL) 2 C = getChildren(l, false) 3 if n is lowest znode in C, exit 4 p = znode in C ordered just before n 5 if exists(p, true) wait for event 6 goto 2 Read Lock 1 n = create(l + “/read-”, EPHEMERAL|SEQUENTIAL) 2 C = getChildren(l, false) 3 if no write znodes lower than n in C, exit 4 p = write znode in C ordered just before n 5 if exists(p, true) wait for event 6 goto 3
該鎖過程與之前的鎖略有不同。寫鎖僅在命名上有所不同。由於讀鎖可能是共享的,因此第3行和第4行略有不同,因爲只有較早的寫鎖znode會阻止客戶端獲得讀鎖。當有多個客戶端在等待讀鎖時,當序列號較低的“write-” znode被刪除時等待讀鎖的客戶端會收到通知,我們似乎會有“羊羣效應”。實際上,這是一種期望的行爲,所有那些讀客戶端都應被釋放,因爲它們現在可以持有鎖。
-
雙重屏障
雙重屏障使客戶端能夠同步計算的開始和結束。當由屏障閾值定義的足夠多的進程加入該屏障時,進程將開始計算並在完成後馬上離開屏障。我們在ZooKeeper中用znode表示一個屏障,稱爲b。每個進程p都會在進入時通過創建b的子節點的znode來向b註冊,並在準備離開時通過刪除該子節點來取消註冊。當b的子znode數量超過屏障閾值時,進程可以進入屏障。當所有進程都刪除了其子節點時,進程可能會離開屏障。我們使用監聽來有效地等待進入和離開的條件得到滿足。要進入,進程會監聽是否存在b的就緒子節點,該子節點將由導致子節點數超過屏障閾值的進程創建。要離開,進程會監聽特定的子節點消失,並且僅在刪除znode之後檢查離開條件。
3. ZooKeeper應用
現在,我們描述一些使用ZooKeeper的應用,並簡要說明它們如何使用它。我們以粗體顯示每個示例的原型。
3.1 The Fetching Service
爬蟲是搜索引擎的重要組成部分,而Yahoo! 爬取數十億個Web文檔。The Fetching Service
(FS)是正在線上的Yahoo!爬蟲的一部分。實質上,它具有一個控制頁面提取過程的主進程。主進程爲提取程序提供配置,並且提取程序回寫通知其狀態和運行狀況。在FS中使用ZooKeeper的主要優點是可以從主進程的故障中恢復,即使出現故障也可以保證可用性,並且可以將客戶端與服務器分離,從而允許他們僅通過從ZooKeeper讀取狀態即可將請求定向到正常的服務器。因此,FS使用ZooKeeper主要來管理配置元數據,儘管它也使用ZooKeeper來選舉主進程(領導者選舉)。
圖2顯示了三天中FS使用的ZooKeeper服務器的讀寫流量。爲了生成此圖,我們計算該時間段每秒內的操作數,每個點對應於該秒內的操作數。我們觀察到,與寫入流量相比,讀取流量要高得多。在速率高於每秒1000次操作的時間段內,讀:寫比率在10:1和100:1之間變化。此工作負載中的讀取操作爲getData()
、getChildren()
和exist()
(按使用次數遞增的順序)。
3.2 Katta
Katta[17]是使用ZooKeeper進行協調的分佈式檢索器,它是非Yahoo!的應用示例。Katta使用分片劃分檢索工作。主服務器將分片分配給從服務器並跟蹤進度。從服務器可能會發生故障,因此主服務器必須根據從服務器的加入和退出來分配負載。主服務器也可能發生故障,因此在發生故障時,其他服務器必須準備好接管。Katta使用ZooKeeper跟蹤從服務器和主服務器(組成員身份認證)的狀態,並處理主服務器故障恢復(領導者選舉)。Katta還使用ZooKeeper來跟蹤並將分片的分配傳播給從服務器(配置管理)。
3.3 Yahoo! Message Broker
Yahoo! Message Broker
(YMB)是一個分佈式的發佈-訂閱系統。該系統管理着數千個主題,客戶端可以向其發佈消息或從中接收消息。主題分佈在一組服務器之間,以提供可擴展性。每個主題使用主備方案進行復制,該方案可確保將消息複製到兩臺計算機上,以確保可靠的消息傳遞。組成YMB的服務器使用無共享分佈式架構,這使得協調對於正確操作至關重要。YMB使用ZooKeeper來管理主題(配置元數據)的分佈,處理系統中機器的故障(故障檢測和組成員身份認證)以及控制系統操作。
圖3顯示了YMB的znode數據佈局的一部分。每個代理域都有一個稱爲節點的znode,該節點對組成YMB服務的每個活躍服務器都有一個臨時znode。每個YMB服務器在具有負載和狀態信息的節點下創建一個臨時znode,並通過ZooKeeper提供組成員身份認證和狀態信息。組成該服務並允許對YMB進行集中控制的所有服務器都將監聽節點諸如關閉和禁止遷移之類操作。主題目錄爲YMB管理的每個主題都有一個子znode。這些主題的znode具有子znode,這些子znode表明每個主題的主服務器和備份服務器以及該主題的訂閱者。主服務器和備份服務器znode不僅允許服務器發現負責主題的服務器,而且還管理領導者選舉和服務器崩潰。
4. ZooKeeper的實現
ZooKeeper通過在組成服務的每臺服務器上覆制ZooKeeper數據來提供高可用性。我們假設服務器因崩潰而失敗,並且此類故障服務器稍後可能會恢復。圖4顯示了ZooKeeper服務的高級組件。收到請求後,服務器會爲執行做準備(請求進程)。如果這樣的請求需要服務器之間的協調(寫請求),則它們使用一致性協議(原子廣播的實現),最後服務器將更改提交到ZooKeeper數據庫中,該更改已在整個服務器集羣中完全複製。對於讀取請求,服務器僅讀取本地數據庫的狀態並生成對該請求的響應。
複製的數據庫是包含整個數據樹的內存數據庫。默認情況下,數據樹中的每個znode最多存儲1MB數據,但是此最大值是一個配置參數,在特定情況下可以更改。爲了實現可恢復性,我們將日誌有效地更新到磁盤上,並且在其應用到內存數據庫之前將其強制寫入磁盤介質中。實際上,正如Chubby[8]一樣,我們保留已提交操作的重播日誌(在我們情況中是預寫日誌),並生成內存數據庫的定期快照。
每個ZooKeeper服務器都爲客戶端提供服務。客戶端僅連接到一臺服務器以提交其請求。如前所述,讀取請求是從每個服務器數據庫的本地副本提供服務的。更改服務狀態的請求(寫請求)由一致性協議處理。
作爲一致性協議的一部分,寫請求被轉發到稱爲領導者的單個服務器。其餘的ZooKeeper服務器(稱爲跟隨者)從領導者那裏接收包含狀態更改的消息提議,並就狀態更改達成一致。
4.1 請求處理器
由於消息傳遞層是原子的,因此我們保證本地副本不會偏離,儘管在某些時間點某些服務器可能比其他服務器應用了更多的事務。與客戶端發送的請求不同,事務是冪等的。領導者收到寫請求後,它將計算寫操作應用時系統的狀態,並將其轉換爲捕獲此新狀態的事務。因爲可能存在尚未應用到數據庫的未完成事務,所以必須計算將來的狀態。例如,如果客戶端執行條件setData,並且請求中的版本號與正在更新的znode的將來版本號匹配,則該服務將生成一個setDataTXN,其中包含新數據、新版本號和更新的時間戳。如果發生錯誤,例如版本號不匹配或要更新的znode不存在,則會生成errorTXN。
4.2 原子廣播
所有更新ZooKeeper狀態的請求都轉發給領導者。領導者執行該請求,並通過Zab[24](一種原子廣播協議)將更改廣播給ZooKeeper狀態。接收到客戶端請求的服務器在傳遞相應的狀態更改時會響應客戶端。Zab默認情況下使用簡單的多數仲裁來決定提議的方案,因此Zab和ZooKeeper僅在大多數服務器正確的情況下才可以工作(例如,使用2f+1
個服務器,我們可以容忍f個故障)。
爲了獲得高吞吐量,ZooKeeper嘗試保持請求處理流水線滿載。在處理流水線的不同部分中可能有數千個請求。因爲狀態更改取決於先前狀態更改的應用,所以Zab比常規的原子廣播提供了更強的順序保證。更具體地說,Zab保證領導者廣播的更改按發送順序發送,並且先前的領導者的所有更改在廣播自己的更改之前都已傳遞給公認的領導者。
有一些實現細節簡化了我們的實現併爲我們提供出色的性能。我們使用TCP進行傳輸,因此消息順序由網絡維護,這使我們可以簡化實現。我們使用Zab選擇的領導者作爲ZooKeeper領導者,因此創建事務的相同進程也會提議這些事務。我們使用該日誌來跟蹤提議,將其作爲內存數據庫的預寫日誌,這樣就不必將消息兩次寫入磁盤。
在正常操作期間,Zab確實按順序準確地傳遞了所有消息,但是由於Zab不會永久記錄所傳遞的每個消息的ID,因此Zab可能會在恢復期間重新傳遞消息。因爲我們使用冪等事務,所以可以按順序進行多次發送。實際上,ZooKeeper要求Zab至少重新傳遞從上一個快照開始之後傳遞的所有消息。
4.3 副本數據庫
每個副本都有一個在ZooKeeper狀態的內存副本。當ZooKeeper服務器從崩潰中恢復時,它需要恢復該內部狀態。在運行服務器一段時間後,重播所有已傳遞的消息以恢復狀態將花費很長時間,因此ZooKeeper使用定期快照,並且僅需要重新傳遞自快照開始以後的消息。因爲我們沒有鎖定ZooKeeper狀態來獲取快照,所以我們將ZooKeeper快照稱爲模糊快照。相反,我們對數據樹進行深度優先掃描,以原子方式讀取每個znode的數據和元數據並將它們寫入磁盤。由於生成的模糊快照可能已應用了快照生成期間傳遞的狀態更改的某些子集,因此結果可能不對應於ZooKeeper在任何時間點的狀態。但是,由於狀態更改是冪等的,因此只要按順序應用狀態更改,我們就可以應用它們兩次。
例如,假定在ZooKeeper數據樹中,兩個節點/foo和/goo在模糊快照開始時分別具有值f1和g1,並且都處於版本1,並且以下狀態更改流以格式<transactionType, path, value, new-version>
到達:
<SetDataTXN, /foo, f2, 2>
<SetDataTXN, /goo, g2, 2>
<SetDataTXN, /foo, f3, 3>
處理完這些狀態更改後,/foo和/goo的值分別爲f3和g2,版本分別爲3和2。但是,模糊快照可能已記錄/foo和/goo的值分別爲f3和g1,版本爲3和1,這不是ZooKeeper數據樹的有效狀態。如果服務器崩潰並使用此快照恢復,並且Zab重新發送狀態更改,則結果狀態與服務崩潰前的狀態一致。
4.4 客戶端-服務器交互
服務器處理寫請求時,還會發出並清除與該更新對應的任何監聽有關的通知。服務器按順序處理寫入,並且不會同時處理其他寫入或讀取。這確保了通知的嚴格繼承。請注意,服務器在本地處理通知。僅客戶端連接到的服務器跟蹤並觸發給該客戶端的通知。
讀取請求在每個服務器的本地處理。每個讀取請求都被處理並用zxid標記,該zxid對應於服務器看到的最後一個事務。該zxid定義了相對於寫入請求的讀取請求的偏序。通過本地處理讀取,我們獲得了出色的讀取性能,因爲它只是本地服務器上的內存操作,並且沒有要運行的磁盤活動或一致性協議。這種設計選擇對於我們以讀取爲主的工作負載下實現出色性能的目標至關重要。
使用快速讀取的一個缺點是不能保證讀取操作的優先順序。也就是說,即使已提交對同一znode的更近的更新,讀取操作也可能返回舊的值。並非我們所有的應用都需要優先順序,但是對於確實需要優先順序的應用,我們已經實現了同步。該原語異步執行,並在所有未完成更新寫入其本地副本後由領導者排序。爲了保證給定的讀取操作返回最新的更新值,客戶端將先調用sync,然後再執行讀取操作。客戶端操作的FIFO順序保證與同步的全局保證一起使讀取操作的結果能夠反映發出同步之前發生的所有更改。在我們的實現中,我們不需要原子廣播同步,因爲我們使用基於領導者的算法,我們只需將同步操作放在領導者和執行同步調用的服務器之間的請求隊列的末尾。爲了使它起作用,跟隨者必須確保領導者仍然是領導者。如果有已提交的待處理事務,則服務器不會懷疑該領導者。如果待處理隊列爲空,則領導者需要發出一個空事務來提交併對該事務後的同步進行排序。它具有很好的特性,即當領導者處於負載狀態時,不會產成額外的廣播流量。在我們的實現中,設置超時時間是爲了使領導者在跟隨者放棄他們之前意識到他們不是領導者,因此我們不會發布空事務。
ZooKeeper服務器按FIFO順序處理來自客戶端的請求。響應中包括對應的zxid。甚至在無活動間隙期間的心跳消息也包括了客戶端連接到的服務器看到的最後一個zxid。如果客戶端連接到新服務器,則該新服務器通過對照其最後一個zxid與客戶端的最後一個zxid來檢查其ZooKeeper數據的視圖至少與客戶端的視圖一樣新。如果客戶端的視圖比服務器的視圖新,則服務器在追趕上之前不會與客戶端重新建立會話。保證客戶端能夠找到另一臺具有最新系統視圖的服務器,因爲客戶端只能看到已經複製到大多數ZooKeeper服務器的更改。此行爲對於保證持久性很重要。
爲了檢測客戶端會話失敗,ZooKeeper使用超時。如果會話超時期間沒有其他服務器從客戶端會話收到任何消息,則領導者將確定其存在故障。如果客戶端足夠頻繁地發送請求,則無需發送任何其他消息。否則,客戶端會在活動不足時發送心跳消息。如果客戶端無法與服務器通信以發送請求或心跳,則它將連接到其他ZooKeeper服務器以重新建立其會話。爲了防止會話超時,ZooKeeper客戶端庫在會話閒置了s/3ms後發送心跳信號,如果在2s/3ms內未收到服務器的消息,則切換到新服務器,其中s是會話超時時間(以毫秒爲單位)。
5. 評估
我們在50臺服務器的集羣上進行所有評估。每個服務器都有一個Xeon雙核2.1GHz處理器,4GB RAM,千兆以太網和兩個SATA硬盤驅動器。我們將以下討論分爲兩部分:吞吐量和請求延遲。
5.1 吞吐量
爲了評估我們的系統,我們以系統飽和時的吞吐量以及各種注入故障的吞吐量變化爲基準。我們改變了組成ZooKeeper服務的服務器數量,但始終保持客戶端數量不變。爲了模擬大量的客戶端,我們使用了35臺機器來模擬250個併發客戶端。
我們有ZooKeeper服務器的Java實現,以及客戶端的Java和C的實現。對於這些實驗,我們使用配置爲登錄到一個專用磁盤並在另一個磁盤上生成快照的Java服務器。我們的基準客戶端使用異步Java客戶端API,每個客戶端至少有100個未完成的請求。每個請求都包含對1K數據的讀取或寫入。我們沒有展示其他操作的基準,因爲所有修改狀態的操作的性能大致相同,並且非狀態修改操作(不包括同步)的性能大致相同。(由於請求必鬚髮送到領導者,但不會廣播,因此同步的性能近似於輕量級寫操作。)客戶端每300ms發送一次已完成操作的計數,我們每6s採樣一次。爲了防止內存溢出,服務器會限制系統中併發請求的數量。ZooKeeper使用請求限制來防止服務器過載。對於這些實驗,我們將ZooKeeper服務器配置爲最多可處理2000個請求。
在圖5中,我們展示了吞吐量,因爲我們改變了讀寫請求的比率,每條曲線對應於提供給ZooKeeper服務的不同的服務器數量。表1顯示了極限讀取負載時的數量。讀取吞吐量高於寫入吞吐量,因爲讀取不使用原子廣播。該圖還顯示服務器的數量也對廣播協議的性能產生負面影響。從這些圖中,我們觀察到系統中的服務器數量不僅影響服務可以處理的故障數量,而且還影響服務可以處理的工作負載。請注意,三個服務器的曲線與其他服務器的曲線在約60%處相交。這種情況並不排除三服務器配置,並且由於啓用了本地讀取的並行性,所有配置都會發生這種情況。但是,這對於圖中的其他配置而言看不到,因爲我們爲可讀性設置了最大y軸吞吐量。
寫請求比讀請求花費更長的時間有兩個原因。首先,寫請求必須經過原子廣播,這需要一些額外的處理並增加請求的延遲。較長時間處理寫請求的另一個原因是,服務器必須確保在將確認發送回領導者之前,將事務記錄到非易失性存儲中。原則上,此要求過高,但由於ZooKeeper構成了應用的正確根基,因此對於我們的生產系統,我們以可靠性爲代價來交換性能。我們使用更多的服務器來容忍更多的錯誤。通過將ZooKeeper數據劃分爲多個ZooKeeper集合,我們提高了寫入吞吐量。Gray等人[12]先前已經觀察到複製和分區之間的這種性能折衷。
ZooKeeper可以通過在組成服務的服務器之間分配負載來實現如此高的吞吐量。由於我們寬鬆的一致性保證,我們可以分配負載。相反,Chubby的客戶端會將所有請求定向到領導者。圖6顯示瞭如果我們不利用這種寬鬆而強迫客戶端僅連接到領導者,會發生什麼。正如預期的那樣,對於讀取爲主的工作負載,吞吐量要低得多,但是即使對於寫入爲主的工作負載,吞吐量也要低。爲客戶端提供服務會導致額外的CPU和網絡負載,從而影響領導者協調提議廣播的能力,進而對總體寫入性能產生不利影響。
原子廣播協議完成了系統的大部分工作,因此,ZooKeeper的性能比其他任何組件都受到更多的限制。圖7顯示了原子廣播組件的吞吐量。爲了測試其性能,我們通過直接在領導者處生成事務來模擬客戶端,因此沒有客戶端連接或客戶端請求和回覆。在最大吞吐量下,原子廣播組件成爲CPU瓶頸的。從理論上講,圖7的性能將與100%寫操作的ZooKeeper的性能相匹配。但是,ZooKeeper客戶端通信、ACL檢查以及對事務轉換的請求均需要CPU。對CPU的爭用將ZooKeeper吞吐量降低到實質上低於隔離狀態下的原子廣播組件。因爲ZooKeeper是關鍵的生產組件,所以到目前爲止,我們對ZooKeeper的開發重點一直是正確性和魯棒性。有很多機會可以顯著提高性能,如通過消除多餘的副本、同一對象的多個序列化、更有效的內部數據結構等方面。
爲了顯示隨着時間的推移系統在注入故障時的行爲,我們運行了由5臺計算機組成的ZooKeeper服務。我們使用與以前相同的飽和度基準,但是這次我們將寫入百分比保持在恆定的30%,這是我們預期工作負載的保守比率。我們定期殺死了一些服務器進程。圖8顯示了系統吞吐量隨時間變化的情況。圖中標記的事件如下:
- 跟隨者的失敗和恢復;
- 另一個跟隨者的失敗和恢復;
- 領導者的失敗;
- 前兩個標記中有兩個跟隨者(a,b)失敗,併到第三標記(c)恢復;
- 領導者失敗。
- 領導者的恢復。
該圖有一些重要的觀察結果。首先,如果跟隨者失敗並迅速恢復,則ZooKeeper能夠在失敗的情況下維持高吞吐量。單個跟隨者的故障不會阻止服務器作出仲裁,而只會由於共享服務器在故障之前正在處理的讀取請求來稍微降低吞吐量。其次,我們的領導者選舉算法能夠足夠快地恢復,以防止吞吐量大幅下降。根據我們的觀察,ZooKeeper只需不到200ms即可選出新的領導者。因此,儘管服務器停止了幾分之一秒的請求服務,但由於我們的採樣週期(秒級),我們沒有觀察到吞吐量爲零。第三,即使跟隨者需要更多時間來恢復,ZooKeeper也可以在開始處理請求後再次提高吞吐量。我們沒有在事件1、2和4之後恢復到滿吞吐量級別的原因之一是,客戶端僅在其與跟隨者的連接斷開時才切換跟隨者。因此,在事件4之後,直到領導者在事件3和事件5失敗之前,客戶端不會重新分配自己。實際上,隨着客戶端來來往往,這種失衡會隨着時間的流逝逐漸消失。
5.2 請求延遲
爲了評估請求延遲,我們創建了一個以Chubby基準[6]爲模型的基準測試。我們創建一個工作進程,該工作進程僅發送一個創建、等待它完成、發送新節點的異步刪除,然後開始下一個創建。我們會相應地更改工作程序的數量,並且對於每次運行,我們每個工作程序創建50000個節點。我們將完成的創建請求數除以所有工作程序完成所花費的總時間來計算吞吐量。
表2顯示了我們基準測試的結果。創建請求包含1K的數據,而不是Chubby基準中的5個字節,以更好地與我們的預期使用相吻合。即使有這些較大的請求,ZooKeeper的吞吐量也比Chubby公佈的吞吐量高出3倍以上。單個ZooKeeper工作者基準測試的吞吐量表明,三臺服務器的平均請求延遲爲1.2ms,而九臺服務器的平均請求延遲爲1.4ms。
5.3 屏障的性能
在本實驗中,我們依次執行許多屏障來評估使用ZooKeeper實現的原語的性能。對於給定數量的屏障b,每個客戶端首先進入所有b屏障,然後依次離開所有b屏障。當我們使用第2.4節中的雙重屏障算法時,客戶端首先要等待所有其他客戶端執行enter()
過程,然後再移至下一個調用(與Leave()
類似)。
我們在表3中報告了我們的實驗結果。在此實驗中,我們分別有50、100和200個客戶連續輸入b個障礙,b屬於集合{200; 400; 800; 1600}
。儘管一個應用可以有成千上萬的ZooKeeper客戶端,但是由於每個客戶端通常根據應用的具體情況進行分組,因此通常每個協調操作中都只會涉及一個較小的子集。
該實驗的兩個有趣觀察結果是,處理所有屏障的時間隨屏障數量的增加而近似線性增長,這表明併發訪問數據樹的相同部分不會產生任何額外的延遲,並且延遲隨着客戶端數量成比例地增加。這是不使ZooKeeper服務飽和的結果。實際上我們觀察到,即使客戶端步調一致,在所有情況下,屏障操作(進入和離開)的吞吐量在每秒1950至3100次操作之間。在ZooKeeper操作中,這對應於每秒10700至17000次操作之間的吞吐量值。在我們的實現中,讀寫比率爲4:1(讀操作佔80%),與ZooKeeper可以達到的原始吞吐量(根據圖5超過40000)相比,我們的基準代碼使用的吞吐量要低得多。這是由於客戶端在等待其他客戶端。
6. 相關工作
ZooKeeper的目標是提供減輕分佈式應用中協調過程問題的服務。爲了實現此目標,其設計使用了以前的協調服務、容錯系統、分佈式算法和文件系統的思想。
我們不是首先提出用於協調分佈式應用的系統的人。一些早期的系統提出了一種分佈式鎖服務,用於事務應用[13],並用於在計算機集羣中共享信息[19]。最近,Chubby提出了一種用於管理分佈式應用的諮詢鎖的系統[6]。Chubby與ZooKeeper有幾個相同的目標。它具有類似於文件系統的接口,並且使用一致性協議來確保副本的一致性。但是,ZooKeeper不是鎖服務。客戶端可以使用它來實現鎖,但是其API中沒有鎖操作。與Chubby不同,ZooKeeper允許客戶端連接到任何ZooKeeper服務器,而不僅僅是領導者。ZooKeeper客戶端的一致性模型比Chubby寬鬆得多,因此可以使用其本地副本來提供數據和管理監聽。這使ZooKeeper可以提供比Chubby更高的性能,從而使應用可以更廣泛地使用ZooKeeper。
文獻中已經提出過容錯系統,其目的是減輕構建容錯分佈式應用的問題。一個早期的系統是ISIS[5]。ISIS系統將抽象類型規範轉換爲容錯的分佈式對象,從而使容錯機制對用戶透明。Horus[30]和Ensemble[31]是從ISIS演變而來的系統。ZooKeeper包含ISIS虛擬同步的概念。最後,Totem在利用局域網[22]的硬件廣播的架構中保證了消息傳遞的總體順序。ZooKeeper可使用多種網絡拓撲,這促使我們依靠服務器進程之間的TCP連接,而不是假設任何特殊的拓撲或硬件功能。我們也不暴露ZooKeeper內部使用的任何通信。
建立容錯服務的一項重要技術是狀態機複製[26],而Paxos[20]是一種算法,可在異步系統中有效實現複製狀態機。我們使用一種算法,該算法具有Paxos的某些特性,但是將一致性所需的事務日誌記錄與數據樹恢復所需的預寫日誌記錄結合在一起,以進行高效的實現。已經提出了一些協議的建議,以實際實現[7,10,18,1,28]拜占庭容錯的複製狀態機。ZooKeeper並不假定服務器可以是拜占庭式的,但是我們確實採用了諸如校驗和以及完整性檢查之類的機制來捕獲非惡意的拜占庭式故障。Clement等人討論了一種在不修改當前服務器代碼庫的情況下使ZooKeeper完全具有拜占庭容錯能力的方法[9]。迄今爲止,我們還沒有觀察到使用完全拜占庭容錯協議可以避免的生產錯誤[29]。
Boxwood[21]是使用分佈式鎖服務器的系統。Boxwood爲應用提供了更高級別的抽象,並且依賴於基於Paxos的分佈式鎖服務。與Boxwood一樣,ZooKeeper是用於構建分佈式系統的組件。但是,ZooKeeper具有高性能要求,並且在客戶端應用中得到了更廣泛的使用。ZooKeeper暴露了應用用來實現高級原語的低級原語。
ZooKeeper類似於小型文件系統,但僅提供文件系統操作的一小部分,並增加了大多數文件系統中不存在的功能,例如排序保證和有條件的寫入。ZooKeeper監聽在本質上與AFS[16]的高速緩存回調相似。
Sinfonia[2]引入了小事務,這是用於構建可擴展分佈式系統的新範例。Sinfonia旨在存儲應用數據,而ZooKeeper存儲應用元數據。ZooKeeper保持狀態完全複製並存儲在內存中,以實現高性能和在要求的延遲。我們對文件系統的使用(如操作和排序)使功能類似於小事務。znode是添加監聽的便捷抽象,Sinfonia中缺少此功能。Dynamo[11]允許客戶端在分佈式鍵值存儲中獲取及寫入相對少量(少於1M)的數據。與ZooKeeper不同,Dynamo中的鍵值空間不是分層的。Dynamo還沒有爲寫入提供強大的持久性和一致性保證,而是解決了讀取衝突。
DepSpace[4]使用元組空間來提供拜占庭容錯服務。像ZooKeeper一樣,DepSpace使用簡單的服務器接口在客戶端實現強大的同步原語。儘管DepSpace的性能遠低於ZooKeeper,但它提供了更強的容錯能力和保密保證。
7. 結論
ZooKeeper通過將無等待對象暴露給客戶端,從而採用無等待方法來解決分佈式系統中的協調過程問題。我們發現ZooKeeper對於Yahoo!內部和外部的多個應用很有用。ZooKeeper通過使用帶有監聽的快速讀取功能來實現以讀取爲主的工作負載的每秒數十萬次操作的吞吐量值,這兩者均由本地副本提供服務。儘管我們對讀取和監聽的一致性保證似乎很弱,但是我們已經通過用例表明,這種結合使我們能夠在客戶端實現高效且複雜的協調協議,即使讀取沒有優先順序並且數據對象的實現也是無等待的。事實證明,無等待特性對於高性能至關重要。
儘管我們僅描述了少數幾個應用,但還有許多其他使用ZooKeeper的應用。我們相信,成功的原因在於其簡單的接口以及可以通過此接口實現的強大抽象。此外,由於ZooKeeper的高吞吐量,應用可以廣泛使用它,而不僅僅是粗粒度鎖。
致謝
我們要感謝Andrew Kornev和Runping Qi對ZooKeeper的貢獻;Zeke Huang和Mark Marchukov提供了寶貴的反饋;Brian Cooper和Laurence Ramontianu爲ZooKeeper的早期貢獻;Brian Bershad和Geoff Voelker在展示中給出了重要評論。
參考文獻
- M. Abd-El-Malek, G. R. Ganger, G. R. Goodson, M. K. Reiter, and J. J. Wylie. Fault-scalable byzantine fault-tolerant services. In SOSP ’05: Proceedings of the twentieth ACM symposium on Operating systems principles, pages 59–74, New York, NY, USA, 2005. ACM.
- M. Aguilera, A. Merchant, M. Shah, A. Veitch, and C. Karamanolis. Sinfonia: A new paradigm for building scalable distributed systems. In SOSP ’07: Proceedings of the 21st ACM symposium on Operating systems principles, New York, NY, 2007.
- Amazon. Amazon simple queue service. http://aws.amazon.com/sqs/, 2008.
- A. N. Bessani, E. P. Alchieri, M. Correia, and J. da Silva Fraga. Depspace: A byzantine fault-tolerant coordination service. In Proceedings of the 3rd ACM SIGOPS/EuroSys European Systems Conference - EuroSys 2008, Apr. 2008.
- K. P. Birman. Replication and fault-tolerance in the ISIS system. In SOSP ’85: Proceedings of the 10th ACM symposium on Operating systems principles, New York, USA, 1985. ACM Press.
- M. Burrows. The Chubby lock service for loosely-coupled distributed systems. In Proceedings of the 7th ACM/USENIX Symposium on Operating Systems Design and Implementation (OSDI), 2006.
- M. Castro and B. Liskov. Practical byzantine fault tolerance and proactive recovery. ACM Transactions on Computer Systems, 20(4), 2002.
- T. Chandra, R. Griesemer, and J. Redstone. Paxos made live: An engineering perspective. In Proceedings of the 26th annual ACM symposium on Principles of distributed computing (PODC), Aug. 2007.
- A. Clement, M. Kapritsos, S. Lee, Y.Wang, L. Alvisi, M. Dahlin, and T. Riche. UpRight cluster services. In Proceedings of the 22 nd ACM Symposium on Operating Systems Principles (SOSP), Oct. 2009.
- J. Cowling, D. Myers, B. Liskov, R. Rodrigues, and L. Shira. Hq replication: A hybrid quorum protocol for byzantine fault tolerance. In SOSP ’07: Proceedings of the 21st ACM symposium on Operating systems principles, New York, NY, USA, 2007.
- G. DeCandia, D. Hastorun, M. Jampani, G. Kakulapati, A. Lakshman, A. Pilchin, S. Sivasubramanian, P. Vosshall, and W. Vogels. Dynamo: Amazons highly available key-value store. In SOSP ’07: Proceedings of the 21st ACM symposium on Operating systems principles, New York, NY, USA, 2007. ACM Press.
- J. Gray, P. Helland, P. O’Neil, and D. Shasha. The dangers of replication and a solution. In Proceedings of SIGMOD ’96, pages 173–182, New York, NY, USA, 1996. ACM.
- A. Hastings. Distributed lock management in a transaction processing environment. In Proceedings of IEEE 9th Symposium on Reliable Distributed Systems, Oct. 1990.
- M. Herlihy. Wait-free synchronization. ACM Transactions on Programming Languages and Systems, 13(1), 1991.
- M. Herlihy and J. Wing. Linearizability: A correctness condition for concurrent objects. ACM Transactions on Programming Languages and Systems, 12(3), July 1990.
- J. H. Howard, M. L. Kazar, S. G. Menees, D. A. Nichols, M. Satyanarayanan, R. N. Sidebotham, and M. J. West. Scale and performance in a distributed file system. ACM Trans. Comput. Syst., 6(1), 1988.
- Katta. Katta - distribute lucene indexes in a grid. http://katta.wiki.sourceforge.net/, 2008.
- R. Kotla, L. Alvisi, M. Dahlin, A. Clement, and E. Wong. Zyzzyva: speculative byzantine fault tolerance. SIGOPS Oper. Syst. Rev., 41(6):45–58, 2007.
- N. P. Kronenberg, H. M. Levy, and W. D. Strecker. Vaxclusters (extended abstract): a closely-coupled distributed system. SIGOPS Oper. Syst. Rev., 19(5), 1985.
- L. Lamport. The part-time parliament. ACM Transactions on Computer Systems, 16(2), May 1998.
- J. MacCormick, N. Murphy, M. Najork, C. A. Thekkath, and L. Zhou. Boxwood: Abstractions as the foundation for storage infrastructure. In Proceedings of the 6th ACM/USENIX Symposium on Operating Systems Design and Implementation (OSDI), 2004.
- L. Moser, P. Melliar-Smith, D. Agarwal, R. Budhia, C. Lingley-Papadopoulos, and T. Archambault. The totem system. In Proceedings of the 25th International Symposium on Fault-Tolerant Computing, June 1995.
- S. Mullender, editor. Distributed Systems, 2nd edition. ACM Press, New York, NY, USA, 1993.
- B. Reed and F. P. Junqueira. A simple totally ordered broadcast protocol. In LADIS ’08: Proceedings of the 2nd Workshop on Large-Scale Distributed Systems and Middleware, pages 1–6, New York, NY, USA, 2008. ACM.
- N. Schiper and S. Toueg. A robust and lightweight stable leader election service for dynamic systems. In DSN, 2008.
- F. B. Schneider. Implementing fault-tolerant services using the state machine approach: A tutorial. ACM Computing Surveys, 22(4), 1990.
- A. Sherman, P. A. Lisiecki, A. Berkheimer, and J. Wein. ACMS: The Akamai configuration management system. In NSDI, 2005.
- A. Singh, P. Fonseca, P. Kuznetsov, R. Rodrigues, and P. Maniatis. Zeno: eventually consistent byzantine-fault tolerance. In NSDI’09: Proceedings of the 6th USENIX symposium on Networked systems design and implementation, pages 169–184, Berkeley, CA, USA, 2009. USENIX Association.
- Y. J. Song, F. Junqueira, and B. Reed. BFT for the skeptics. http://www.net.t-labs.tu-berlin.de/˜petr/BFTW3/abstracts/talk-abstract.pdf.
- R. van Renesse and K. Birman. Horus, a flexible group communication systems. Communications of the ACM, 39(16), Apr. 1996.
- R. van Renesse, K. Birman, M. Hayden, A. Vaysburd, and D. Karr. Building adaptive systems using ensemble. Software - Practice and Experience, 28(5), July 1998.