http://blog.xiping.me/2010/12/google-chubby-in-chinese.html?#failoverhtml
本文翻譯自Google的[The Chubby lock service for loosely-coupled distributed systems]。翻譯此文旨在傳播更多信息。翻譯水平有限,時間少且文章太長了,質量通常。完整版本請閱讀原文。若是轉載請完整轉載,幷包含本申明。node
=========================================================================
咱們描述了咱們在Chubby鎖服務方面的經歷。Chubby的目標是爲鬆散耦合的分佈式系統,提供粗粒度的鎖定、可靠的存儲(儘管容量不大)。Chubby提供了一個很像帶有意向鎖的分佈式文件系統的接口(interface),不過它的設計重點是可用性和可靠性,而不是高性能。許多Chubby服務實例已經使用了超過一年,其中的幾個實例每一個都同時處理着數萬臺客戶端。本文描述了最初的設計和預期使用,將之與實際的使用相比較,並解釋了設計是怎樣修改以適應這些差異的。
本文介紹了一種鎖服務: Chubby。它被計劃用在由適度規模的大量小型計算機經過高速網絡互聯構成的的鬆散耦合的分佈式系統中。例如,一個Chubby實例(也叫Chubby單元(Cell))可能服務於經過1Gbit/s網絡互聯的一萬臺4覈計算機。大部分Chubby單元限於一個數據中心或者機房,不過咱們運行着至少一個各個副本(replica)之間相隔數千千米的Chubby單元。
鎖服務的目的是容許它的客戶應用們(clients)同步它們的活動,並對它們所處環境的基本信息取得一致。主要的目標包括在適度大規模的客戶應用羣時的可靠性、可用性,以及易於理解的語義;吞吐量和存儲容量被認爲是次要的。Chubby的客戶端接口很像這樣一個簡單的文件系統:能執行整文件的讀和寫,另外還有意向鎖和和多種諸如文件改動等事件的通知。
咱們預期Chubby幫助開發者處理他們的系統中的粗粒度同步,特別是處理從一組各方面至關的服務器中選舉領導者。例如Google文件系統(Google File System)[7]使用一個Chubby鎖來選擇GFS Master 服務器,Bigtable[3]以數種方式使用Chbbuy:選擇領導者;使得Master能夠發現它控制的服務器;使得客戶應用(client)能夠找到Master。此外,GFS和Bigtable都用Chubby做爲衆所周知的、可訪問的存儲小部分元數據(meta-data)的位置;實際上它們用Chubby做爲它們的分佈式數據結構的根。一些服務使用鎖在多個服務器間(在粗粒度級別上)拆分工做。
在Chubby發佈以前,Google的大部分分佈式系統使用必需的、未提早規劃的(ad hoc)方法作主從選舉(primary election)(在工做可能被重複但無害時),或者須要人工干預(在正確性相當重要時)。對於前一種狀況,Chubby能夠節省一些計算能力。對於後一種狀況,它使得系統在失敗時再也不須要人工干預,顯著改進了可用性。
熟悉分佈式計算的讀者會意識到在多個相同體(peer)間primay選舉是分佈式協同(distributed consensus)問題的一個特例,同時意識到咱們須要一種異步(asynchronous)通訊的解決方案。異步(asynchronous)這個術語描述了絕大多數真實網絡(real networks)如以太網或因特網的行爲:它們允許數據包丟失、延時和重排序。(專家們通常應該瞭解(真實網絡的)協議集創建在對環境作了很強假設的模型之上。) 異步一致性由Paxos協議[12, 13]解決了。一樣的協議被Oki和Liskov(見於他們有關Viewstamped replication的論文[19, $4])使用,其餘人使用了等價的協議[14, $6]。實際上,迄今爲止咱們遇到的全部可用的異步協同協議的核心都有Paxos。Paxos不須要計時假設來維持安全性,但必須引入時鐘來確保活躍度(liveness)。這克服了Fisher等人的不可能性結果(impossibility result of Fisher et al.)[5, $1]。
構建Chubby是一種知足上文提到的各類需求的工程上的工做,不是學術研究。咱們聲明沒有提出新的算法和技術。本文的目的在於描述咱們作了什麼以及爲何這麼作,而不是提出這些。在接下來的章節中,咱們描述Chubby的設計和實現,以及在實際過程當中它是怎麼改變的。咱們描述了預料以外的Chubby的使用方式,以及被證實是錯誤的特性。咱們忽略了在其餘文獻中已經包括的細節,例如一致性協議或者RPC系統。
有人可能會爭論說咱們應該構建一個包含Paxos的庫,而不是一個訪問中心化鎖服務的庫,即便是一個高可靠的鎖服務。客戶端Paxos庫將不依賴於其餘服務器(除了名字服務(name service)之外),而且假定他們的服務能夠實現爲狀態機,將爲開發者提供標準化的框架(and would provide a standard framework for programmers, assuming their services can be implemented as state machines)。事實上,咱們提供了一個這樣的與Chubby無關的客戶端庫。
然而,一個鎖服務具備一些客戶端庫不具備的優勢。第一,有時開發者並無如人們但願的那樣,爲高可用性作好規劃。一般他們的系統從只有很小負載和寬鬆的可用性保證的原型開始,代碼老是沒有特別地構造爲使用一致性協議。當服務成熟,獲得了用戶,可用性變得更重要了,複製(replaction)和主從選舉(primary election)隨後被加入到已有的設計中。 儘管這能經過提供分佈式協同的庫來搞定,但鎖服務更易於保持已經存在的程序結構和通訊模式。例如,選擇一個master,而後將選舉結果寫入一個已經存在的文件服務器中,只須要加兩條語句和一個RPC參數到已經存在的系統中:一條語句請求一個鎖以成爲master,另外傳遞一個整數(鎖請求計數)給寫RPC,再加入一條if語句給文件服務器拒絕寫入若是請求計數小於當前的值(用於防止延時的包)。咱們發現這種技術比將已有的服務器加入一致性協議更容易,尤爲是在遷移期間(transition period)必須維持兼容性時。
第二,咱們的許多服務在選舉parimary,或在它們的各個組件間劃分數據時,須要一種公佈結果的機制。這意味着咱們應該容許客戶端存儲和取得小量的數據–也就是讀寫小文件。這能經過名字服務來完成,可是咱們的經驗是鎖服務自身至關適合作這件事,既由於這樣減小了客戶端要依賴的服務器數,也由於協議的一致性特性是相同(shared)的。Chubby的做爲一個名字服務器的成功,應很大程度上應歸功於一致的客戶端緩存,而不是基於時間的緩存。特別地,咱們發現開發者至關地賞識不須要選擇一個像DNS生存時間值(time-to-live)同樣的緩存超時值,這個值若是選擇不當會致使很高的DNS負載或者較長的客戶端故障修復時間。
第三,基於鎖的接口更爲程序員所熟悉。Paxos的複製狀態機(replicated state machine)和與排他鎖關聯的臨界區都能爲程序員提供順序編程的幻覺。但是,許多程序員已經用過鎖了,而且認爲他們知道怎麼使用它們。頗爲諷刺的是,這樣的程序員常常是錯的,尤爲是當他們在分佈式系統裏使用鎖時。不多人考慮單個機器的失敗對一個異步通訊的系統中的鎖的影響。無論怎樣,對鎖的表面上的熟悉性,打敗了試圖說服程序員們爲分佈式決策使用(其餘)可靠機制的努力。
最後,分佈式協同算法使用quorums作決策,因而它們使用多個副原本達到高可用性。例如,Chubby自己一般在每一個單元中有五個副本,Chubby單元要存活(to be up)的話必須保證其中三個副本在正常運行。相反,若是一個客戶端系統使用一個鎖服務,即便只有一個客戶端能成功取到鎖也能繼續安全地運行。所以,鎖服務能減小可靠的客戶端系統運行所須要服務器數目。在更寬泛的意義上,人們可以將鎖服務視做一種經過提供通用的全體選民(electorate)的方式,容許一個客戶系統在少於其多數的成員存活(up)時正確地決策。人們能夠設想用不一樣的方式解決最後的這個問題:經過提供一個「協同服務」(consensus service), 使用一些服務器提供Paxos協議中的」acceptors」。像鎖服務同樣,「協同服務」也將容許客戶端(clients)在即便只有一個活躍客戶進程的狀況下繼續安全地運行。相似的技術曾經被用於減小拜占庭故障兼容問題(Byzantine fault tolerance)所需的狀態機(state machines)數目[24].。然而,假如協同服務不專門地提供鎖(也就是將其刪減爲一個鎖服務),這種途徑不能解決任意一個上文提到的其它問題。
上面這些討論建議了咱們的兩個關鍵的設計決策:
咱們選擇一個鎖服務,而不是一個庫或者一致性服務,以及
咱們選擇提供小文件,以使得被選出來的primaries能夠公佈它們自身以及它們的參數,而不是建立和維護另一個服務。
某些設計決策則來自於咱們預期的用途和咱們的環境:
一個經過Chubby文件來公佈其Primary的服務,可能擁有數千的客戶端。所以,咱們必須容許數千的客戶端監視這個文件,而且最好不須要太多服務器。
客戶端和有多個副本(replica)的服務的各個副本要知道何時服務的primary發生了變化。這意味着一種事件通知機制將頗有用,以免輪詢。
即便客戶端不須要間歇性地輪詢文件,不少客戶端仍是會這樣作;這是支持許多開發者的結果。所以,緩存這些文件是很可取的。
咱們的開發者對不直觀的緩存語義感到困擾,因此咱們傾向於一致的緩存(consistent caching)。
爲了不金錢上的損失與牢獄之災(jail time),咱們提供了包括訪問控制在內的安全機制。
一個可能讓一些讀者吃驚的選擇是,咱們不但願鎖被細粒度地使用,這種狀況下這些鎖可能只被持有一段很短的時間(數秒或更短);實際上(instead),咱們但願粗粒度地使用。例如,一個應用程序可能使用鎖來選舉一個primary,而後這個primary在一段至關長的時間多是數小時或數天裏,處理全部對數據的訪問。這兩種使用方式意味着對鎖服務器的不一樣的需求。
粗粒度的鎖在鎖服務器引入的負載要小得多。特別是鎖的獲取頻率一般只會與客戶端應用系統的事務頻率只有很微弱的關聯。粗粒度的鎖不常被請求,因此臨時性的鎖服務器不可用給客戶端的形成的延時會更少。在另外一方面,一個鎖從客戶端到另外一個客戶端可能須要高昂的恢復處理,全部人們不但願鎖服務器的故障恢復形成鎖的丟失。所以,粗粒度的鎖可以經歷鎖服務器的失敗而繼續有效是頗有用的,這裏不太在乎這樣作的開銷,而且這樣的鎖使得許多客戶端可由少數可用性稍低的鎖服務器服務得足夠好(and such locks allow many clients to be adequately served by a modest number of lock servers with somewhat lower availability)。
細粒度的鎖會有不一樣的結論。即便鎖服務的短暫的不可用也可能致使許多客戶端掛起。由於鎖服務上的事務頻率將隨着全部客戶端的事務頻率之和一塊兒增加,性能和隨意增長新服務器的能力十分重要。不須要在鎖服務器失敗之間維持鎖能夠減小鎖定的開銷,這是優點;頻繁地丟棄鎖也不是一個很嚴重的問題,由於鎖只被持有一段很短的時間。(客戶端必須準備好在網絡分離期間丟失鎖,所以鎖服務器故障恢復形成的鎖的丟失不會引入新的恢復路徑。(Clients must be prepared to lose locks during network partitions, so the loss of locks on lock server fail-over introduces no new recovery paths.))
Chubby被計劃爲只提供粗粒度的鎖定。幸運的是,對於客戶端而言,實現根據其自身的應用系統定製的細粒度鎖是很簡單的。一個應用程序可能將它的鎖劃分紅組,並使用Chubby的粗粒度鎖將這些鎖分組分配給應用程序特定的鎖服務器。維護這些細粒度的鎖只須要不多的狀態,這些服務器只須要持有一個不常變的、單調遞增的請求計數器,這個請求計數器不會常常被更新。客戶端可以在解鎖時發現丟失了的鎖,而且若是使用了一個簡單定長的租期(lease),其協議將會簡單高效。這種模式的最重要的收益是咱們的客戶端開發者對他們的負載所需的服務器的供應負責,也將他們從本身實現協同的複雜性中解放出來。
Chubby有兩個主要的經過RPC通訊的組件:一個服務器和一個客戶端應用連接的庫,如圖一所示。全部Chubby客戶端和服務器之間的通訊由客戶端庫居間達成。還有一個可選的第三個組件,代理服務器,將在第3.1節討論。
一個Chubby單元由一組稱做副本集(replicas)的服務器(典型的是五個)組成,按下降關聯失敗的可能性來放置(例如分別放在不一樣的機架)。這些副本使用分佈式一致的協議來選舉一個Master,Master必須從副本集獲得多數投票,並獲得副本集在一個持續數秒的被稱爲master租期(lease)的時間段內再也不選舉另外一個不一樣的Master的承諾。只要Master繼續贏得大多數投票,這個master租期就會週期性地被副本集刷新。
副本集維護着一個簡單數據庫的備份集,可是隻有master發起對數據庫的讀寫。其餘的全部副本簡單地從master複製使用一致性協議傳送的更新。
客戶端們經過發送master位置請求給列在DNS中的副本集來查找master。非master的副本返回master的標識來回應這些請求。一旦一個客戶端定位到master,它將全部請求指引到該master,直到該master中止迴應或者指明本身再也不是master。寫請求被經過一致性協議傳播給全部副本,這些請求在寫入達到Chubby單元中的多數副本時被答謝確認。杜請求有master獨自知足,這樣是安全的–只要master租期沒有過時,由於沒有別的master可能存在。若是一個master失敗了,其餘的副本在他們的master租期到期時運行選舉協議,典型地,一個新的master將在幾秒以內選舉出來。例如,最近兩次的選舉花費了6秒和4秒,可是咱們也見太高達30秒的。
若是一個副本失敗了而且在幾個小時內沒有恢復,一個簡單的替換系統從一個空閒池選擇一臺新的機器,並在其上啓動鎖服務器的二進制文件(binary)。而後它將更形DNS表,將失敗了的副本的IP替換爲新啓動的啓動的IP。當前的master週期性地輪詢DNS並最終注意到這個變化,而後它在該Chubby單元的數據庫中更新單元成員列表,這個列表在全部成員之間經過常規的複製協議保持一致。與此同時,新的副本取得存儲在文件服務器上的數據庫備份和活躍副本的更新的組合。一旦新副本處理了一個當前master在等待提交的請求,新的副本就被許可在新master的選舉中投票。
Chubby開放出一個相似於UNIX的文件系統[22]的接口,但比Unix的文件系統更簡單。它由一個嚴格的文件和目錄樹組成,名字的各部分使用反斜槓劃分。一個典型的名字以下:
/ls/foo/wombat/pouch
其中的ls前綴與全部的Chubby名字相同,表明着鎖服務(lock service)。第二個部分(foo)是Chubby單元的名字,經過DNS查詢被解析到一個或多個Chubby服務器。一個特殊的單元名字local,代表應該使用客戶端的本地Chubby單元,這個Chubby單元一般在同一棟樓裏,所以這個單元最有可能能訪問到。名字的剩餘部分,/wombat/pouch,由Chubby單元內部解析。一樣跟UNIX同樣,每一個目錄包含一個全部子文件和子目錄的列表,而每一個文件包含一串不解析的字節。
由於Chubby的命名結構組成了一個文件系統,咱們既能夠經過它的專門API將它開放給應用系統,也能夠經過咱們的其餘文件系統例如GFS使用的接口。這樣顯著地減小了編寫基本瀏覽和名字空間操做的工具的工做(effort),也減小了培訓那些偶然用Chubby的用戶的需求。
這種設計使得chubby接口不一樣於UNIX文件系統,它使得分佈更容易(The design differs from UNIX in a ways that easy distribution)。爲容許不一樣目錄下的文件由不一樣的Chubby Master來服務,咱們沒有放出(expose)那些將文件從一個目錄移動到另外一個目錄的操做,咱們不維護目錄修改時間,也避開路徑相關的權限語義(也就是文件的訪問由其自己的權限控制,而不禁它上層路徑上的目錄控制)。 爲使緩存文件元數據更容易,系統不公開最後訪問時間。
其名字空間包含文件和目錄,統一叫作節點(nodes)。每一個這樣的節點在其所在的Chubby單元內只有一個名字,沒有符號連接和硬連接。
節點多是永久的或者瞬時(ephemeral)的。任意節點均可以被顯示地(explicitly)刪除,可是瞬時節點也會在沒有客戶端打開它時被刪除(另外,對目錄而言,在它們爲空時被刪除)。瞬時文件被用做臨時文件,也被用做一個客戶端是否存活的指示器(給其餘客戶端)。任意節點都能做爲一個意向性(advisory)的讀寫鎖;這些鎖將在2.4節更詳細地描述。
每一個節點有多種元數據,包括訪問控制列表(ACLs)的三個名字,分別用於控制讀、寫和修改其ACL。除非被覆蓋,節點在建立時繼承父目錄的ACL名字。ACLs自己是位於一個ACL目錄中的文件, 這個ACL目錄是Chubby單元的一個爲人熟知的本地名字空間。這些ACL文件的內容由簡單的訪問名字(principals)列表組成;這裏讀者可能會想起Plan 9的 groups[21]。這樣,若是文件F的寫ACL名字是foo,而且ACL目錄中包含了一個名foo的文件,foo文件中包含bar這個條目,那麼用戶bar就能夠寫文件F。用戶由內嵌在RPC系統裏的機制鑑權。由於Chubby的ACLs是日常的文件,它們自動地就能夠由其餘想使用相似的訪問控制機制的服務訪問。
每一個節點的元數據包括四個單調遞增的64位編號。這些編號容許客戶端很容易地檢測變化:
實例編號:大於任意先前的同名節點的實例編號。
內容的世代編號(只針對文件):這個編號在文件內容被寫入時增長。
鎖的世代編號:這個編號在節點的鎖由自由(free)轉換到持有(held)時增長。
ACL的世代編號:這個編號在節點的ACL名字被寫入時增長。
Chubby也放出了一個64位的文件內容的校驗碼,因此客戶端能夠分辨文件是否有變化。
客戶端經過打開節點獲得相似於UNIX文件描述符的句柄(handles)。句柄包括:
校驗位:阻止客戶端自行建立或猜想句柄,因此完整的訪問控制檢查只須要在句柄建立時執行(對比UNIX,UNIX在打開時檢查權限位但在每次讀寫時不檢查,由於文件描述符不能僞造)。
一個序列號:這個序列號容許master分辨一個句柄是由它或前面的master生成。
模式信息:在句柄打開時設定的是否容許新master在碰見一個由前面的master建立的舊句柄時重建該句柄的狀態。
每一個Chubby文件和目錄都能做爲一個讀寫鎖:要麼一個客戶端句柄以排他(寫者)模式持有這個鎖,要麼任意數目的客戶端句柄以共享(讀者)模式持有這個鎖。像大部分程序員熟知的互斥器(mutexes),鎖是意向性的(advisory)。就是隻與對同一個鎖的加鎖請求衝突:持有鎖F既不是訪問文件F的必要條件,也不會阻止其餘客戶端訪問文件F。咱們捨棄強制鎖—強制鎖使得其餘沒有持有鎖的客戶端不能訪問被鎖定的對象:
Chubby鎖常常保護由其餘服務實現的資源,而不只僅是與鎖關聯的文件。以一種有意義的方式執行強制鎖定可能須要咱們對這些服務作更普遍的修改。
咱們不想強迫用戶在他們爲了調試和管理而訪問這些鎖定的文件時關閉應用程序。在一個複雜的系統中,這種在我的電腦上經常使用的方法是很難用的。我的電腦上的管理員見能夠經過指示用戶關閉應用或者重啓來停止強制鎖定。
咱們的開發者用常規的方式來執行錯誤檢測,例如「lock X is held」,因此他們從強制檢查中受益不多。有Bug或者惡意的進程有不少機會在沒有持有鎖時破壞數據,因此咱們咱們發現強制鎖定提供的額外保護沒有實際價值。
在Chubby中,請求任意模式的鎖都須要寫權限,於是一個無權限的讀者不能阻止一個寫者的操做。
在分佈式系統中,鎖定是很複雜的,由於通訊常常發生變化(communication is typically uncertain),而且進程可能各自獨立地失敗(fail independently)。像這樣,一個持有鎖 L 的進程可能發起請求 R ,但接着就失敗了。另外一個進程可能請求 L 並在 R 到達目的地以前執行了某些操做。若是 R 來晚了,它可能在沒有鎖 L 的保護下操做,也有可能在不一致的數據上操做。接收消息順序紊亂的問題已經被研究得很完全:解決方案包括虛擬時間(virtual time)[11]和虛擬同步(virtual synchrony)[1],它經過確保與每一個參與者的觀察一致的順序處理消息來避免這個問題。
在一個已有的複雜系統中給全部的交互(interaction)引入順序編號(sequence number)的成本很高。做爲替代,Chubby經過只在使用鎖的交互(interaction)中引入序號提供了一種方法。任什麼時候候,鎖持有者可能請求一個序號,一個不透明(opaque)的描述了鎖剛得到時的狀態的字節串(byte-string)。它包括鎖的名字,被請求的鎖定模式(獨佔仍是共享),以及鎖的世代編號。客戶端在其但願操做將被鎖保護時傳遞這個序號給服務器(例如文件服務器)。接受服務器被預期將測試這個序號是否仍然有效而且具備適當的鎖定模式,若是它將拒絕該請求。序號的有效性可與服務器的Chubby緩存覈對,或者若是服務器不想維護一個到Chubby的Session的話,可與服務器最近觀察到的序號比對。這種序號機制只須要給受影響的消息增長一條字符串,而且很容易地解釋給咱們的開發者。
雖然咱們發現序號使用簡單,但重要的協議也在緩慢演化。所以Chubby提供了一種不完美可是更容易的機制來下降延遲的或者重排序的到不支持序號的服務器的風險。若是一個客戶端以一般的方式釋放了一個鎖,像人們期待的那樣,這個鎖當即可供其餘的客戶端索取。然而,若是一個鎖是由於持有者中止工做或者不可訪問,鎖服務器將阻止其餘的客戶端在一個被稱做鎖延時(lock-delay)的小於某個上界的時間段內索取該鎖。客戶端可能指定任意的低於某個上界(當前是一分鐘)的鎖延時,這個限制防止有毛病的客戶端形成一個鎖(和像這樣的某些資源)在一個隨意長的時間裏不可用。 儘管不完美,鎖延時保護未修改過的服務器和客戶端免受消息延時和重啓致使的平常問題的影響。
在建立句柄時,Chubby客戶端可能會訂閱一系列事件。這些事件經過Chubby庫的向上調用(up-call)異步地傳遞給客戶端。這些事件包括:
文件內容被修改–經常使用於監視經過文件公佈的某個服務的位置信息(location)。
子節點的增長、刪除和修改 — 用於實現鏡像(mirroring)(第2.12節)。(除了容許新文件可被發現外,返回子節點上的事件使得能夠監控臨時文件(ephemeral files)而不影響這些臨時文件的引用計算(reference counts))
Chubby master故障恢復 — 警告客戶端其餘的事件可能已經丟失了,因此必須從新檢查數據。
一個句柄(和它的鎖)失效了 — 這常常意味着某個通信問題。
鎖被請求了(lock required) — 可用於判斷何時primary選出來了。
來自另外一個客戶端的相沖突的鎖請求 — 容許鎖的緩存。
事件在相應的動做發生以後被遞送。這樣,若是一個客戶端被告知文件內容發生了變化,必定能保證(it’s guaranteed) 接下來它讀這個文件會看到新的數據(或者比該事件還要更近的數據)。
上面提到的最後兩種事件極少使用,過後思考它們能夠略去(and with hindsight could have been omitted)。例如在primary選舉(primary election)後,客戶端一般須要與新的primary聯繫,而不是簡單地知道一個新的primary存在了;於是,它們會等待primary將地址寫入文件的文件修改事件。鎖衝突事件在理論上容許客戶端緩存其餘服務器持有的數據,使用Chubby鎖來維護緩存的一致性。一個衝突的鎖請求將會告訴客戶端結束使用與鎖相關的數據;它將結束等待進行的操做,將修改刷新到原來的位置(home location),丟棄緩存數據,而後釋放鎖。到目前爲止,沒有人採納這種用法。
客戶端將Chubby句柄看做一個指向一個支持多種操做的不透明結構的指針。句柄之經過open()建立,以及經過close()銷燬。
Open() 打開一個帶名字的文件或目錄產生一個句柄,相似於一個UNIX文件描述符。只有這個調用須要一個節點名,全部其餘的調用都在句柄上操做。
這個節點名是相對於某個已知的目錄句柄的,客戶端庫提供了一個一直有效的「/」目錄句柄。Directory handles avoid the difficulties of using a program-widecurrent directory in a multi-threaded program that contains many layers of abstraction. (目錄句柄避開了在包含不少層抽象的多線程程序內使用程序範圍內的當前目錄的困難[18]。)
在調用Open()時,客戶端指定了多個選項:
句柄將被如何使用(讀;寫和鎖定;改變ACL);句柄只在客戶端擁有合適的權限時建立。
須要傳遞的事件(查看第2.5節)。
鎖延時(第2.4節)。
是否應該(或者必須)建立一個新的文件或目錄。若是要建立一個文件,調用者可能要提供初始內容和初始的ACL名字。其返回值代表這個文件其實是否已經建立。
Close() 關閉一個打開的句柄。之後再也不容許使用這個句柄。這個調用永遠不會失敗。一個相近的調用Poison()引發該句柄上未完成的和接下來的操做失敗,但不關閉它,這就容許一個客戶端取消由其餘線程建立的Chubby調用而不須要擔憂被它們訪問的內存的釋放。
在句柄上進行的主要調用包括:
GetContentsAndStat() 返回文件的內容和元數據。一個文件的內容被原子地、完整地讀取。咱們避開部分讀取和寫入以阻礙大文件。一個相關的調用GetStat()只返回元數據,而ReadDir()返回一個目錄下的名字和元數據(the names and meta-data for the children of a directory)。
SetContents()將內容寫到文件。可選擇地,客戶端可能提供一個內容世代編號以容許客戶端模擬在文件上的比較並交換:只有在世代編號是當前值時內容才被改變。文件的內容老是完整地、原子地寫入。一個相關的調用SetACL()在節點關聯的ACL名字上執行一個相似的操做。
Delete() 刪除一節節點,若是它沒有孩子的話。
Accquire(), TryAccquire(), Release() 得到和釋放鎖。
GetSequencer() 返回一個描述這個句柄持有的鎖的序號(sequencer)(見第2.4節)。
SetSequencer() 將一個序號與句柄關聯。在這個句柄上的隨後的操做將失敗若是這個序號再也不有效的話。
CheckSequencer() 檢查一個序號是否有效。(見第2.4節)
若是節點在該句柄建立以後被刪除,其上的調用將會失敗,即便這個文件隨後又從新建立了也是如此。也就是一個句柄關聯到一個文件的實例,而不是文件名。Chubby可能在任意的調用上使用訪問控制,可是老是檢查Open() 調用。
除了調用自身須要的參數以外, 上面的全部調用都帶有一個操做(operation)參數。這個操做參數持有可能與任何調用相關的數據和控制信息。特別地,經過操做的參數客戶端可能:
提供一個回調(callback)以使調用以異步方式執行。
等待調用的結束,和/或
取得展開的錯誤和診斷信息。
客戶端能夠利用這個API執行primary選舉:全部潛在的primaries打開鎖文件,並嘗試得到鎖。其中一個成功得到鎖,併成爲primary,而其餘的則做爲副本。這個Primary將它的標識用SetContents()寫入到鎖文件,在對文件修改事件的響應中,它被客戶端和副本們發現,它們用GetContentsAndStat()讀取鎖文件。理想狀況下,Primary經過GetSequencer()取得一個序號,而後將序號傳遞給與它通訊的其餘服務器:這些服務器應該用CheckSequencer()確認它仍然是primary。一個鎖延時可能與不能檢查序號(第2.4節)的服務一塊兒使用。
爲了減小讀傳輸量,Chubby客戶端將文件數據和節點元數據(包括文件缺失信息(file absence)) 緩存在內存中的一個一致的(consistent)、write-through的緩存中。這個緩存由一個如後文描述的租期機制(lease mechanism)維護,並經過由master發送的過時信號(invalidations)來維護一致性。Master保留着每一個客戶端可能正在緩存的數據的列表。這個協議保證客戶端要麼看到一致的Chubby狀態,要麼看到錯誤。
當文件數據或者元數據將被修改時,修改操做被阻塞,同時master發送過時信號給全部可能緩存了這些數據的客戶端。這種機制創建在下一節要詳細討論的KeepAlive RPC之上。在接收到過時信號後,客戶端清除過時的狀態信息,並經過發起它的下一個KeepAlive調用應答服務器。修改操做只在服務器知道每一個客戶端都將這些緩存失效之後再繼續,要麼由於客戶端答謝了過時信號,要麼由於客戶端讓它的緩存租期(cache lease)過時。
只有一次過時來回(round)是必需的,由於master在緩存過時信號沒有答謝期間將這個節點視爲不可緩存的(uncachable)。這種方式讓讀操做老是被無延時地獲得處理;這是頗有用的,由於讀操做的數量要大大超過了寫操做。另外一種選擇多是在過時操做期間阻塞對該節點的訪問;這將使得過分急切的客戶端在過時操做期間接二連三地以無緩存的訪問轟炸master的可能性大大下降,其代價是偶然的延遲。 若是這成爲問題,人們可能會想採用一種混合方案,在檢測到過載時切換處理策略(tactics)。
緩存協議很簡單:它在修改時將緩存的數據失效,而永不去更新這些數據。有可能去更新緩存而不是讓緩存失效也會同樣簡單,可是隻更新的協議可能會無理由地低效;某個訪問文件的客戶端可能無限期地收到更新,從而致使次數無限制的、徹底沒必要要的更新。
儘管提供嚴格一致性的開銷不小,咱們拒絕了更弱的模型,由於咱們以爲程序員們將發現它們很難用。相似地,像虛擬同步(virtual synchrony)這種要求客戶端在全部的消息中交換序號的機制,在一個有多種已經存在的通訊協議的環境中也被認爲是不合適的。
除了緩存數據和元數據,Chubby客戶端還緩存打開的句柄。於是,若是一個客戶端打開一個前面已經打開的文件,只有第一次Open()調用時會引發一個給Master的RPC。這種緩存被限制在較低層次(in a minor ways),因此它不會影響客戶端觀察到的語義:臨時文件上的句柄在被應用程序關閉後,不能再保留在打開狀態;而允許鎖定的句柄則可被重用,可是不能由多個應用程序句柄併發使用。最後的這個限制是由於客戶端可能利用Close()或者Poison()的邊際效應:取消正在進行的向Master請求的Accquire()調用。
Chubby的協議容許客戶端緩存鎖 — 也就是,懷着這個鎖會被同一個客戶端從新使用的但願,持有鎖比實際須要更長的時間。若是另外一個客戶端請求了一個衝突的鎖,能夠用一個事件告知鎖持有者,這容許鎖持有者只在別的地方須要這個鎖時才釋放鎖(參考§2.5)。
一個Chubby會話是Chubby單元和Chubby客戶端之間的一種關係,它存在於麼某個時間間隔內,並由稱作KeepAlive的間歇性地握手來維持。除非一個Chubby客戶端通知master,不然,只要它的會話依然有效,該客戶端的句柄、鎖和緩存的數據都仍然有效。(然而,會話維持的協議可能要求客戶端答謝一個緩存過時信號以維持它的會話,請看下文)
一個客戶端在第一次聯繫一個Chubby單元的master時請求一個新的會話。它要麼在它結束時明確地結束會話,或者在會話陷入空轉時(在一段時間內沒有任何打開的句柄和調用)結束會話。
每一個會話都有關聯了一個租期(lease) — 一個延伸向將來的時間間隔,在這個時間間隔內master保證不單方面停止會話。間隔的結束被稱做租期到期時間(lease timeout)。Master能夠自由地向將來延長租期到期時間,可是不能將它往回移動。
在三種情形下,Master延長租期到期時間:會話建立時、master故障恢復(見下文)發生時和它應答來自客戶端的KeepAlive RPC時。在接收KeepAlive時,master一般阻塞這個RPC請求(不容許其返回),直到客戶端的上前一個租期接近過時。而後,Master容許RPC返回給客戶端,並告知客戶端新的租期過時時間。Master可能任意長度地延伸超時時間。默認的延伸是12秒,但一個過載的master可能使用更高的值,以下降它必須處理的KeepAlive調用數目。客戶端在收到上一個回覆之後當即發起一個新的KeepAlive,這樣客戶端保證幾乎老是有一個KeepAlive調用阻塞在master上。
除了延伸客戶端的租期,KeepAlive的回覆還被用於將事件和緩存過時傳回給客戶端。Master容許一個KeepAlive在有事件或者緩存過時須要遞送時提早返回。在KeepAlive的回覆上搭載事件,保證了客戶端不該答緩存過時則不能維持會話,並使得全部的RPC都是從客戶端流向master。這樣既簡化了客戶端,也使得協議能夠經過只容許單向發起鏈接的防火牆。
客戶端維持着一個本地租期超時,這個本地超時值是Master的租期的較小的近似值。它跟master的租期過時不同,是由於客戶端必須在兩方面作保守的假設。一是KeepAlive花在傳輸上的時間,一是master的時鐘超前的度。爲了維護一致性,咱們要求服務器的時鐘頻率相對於客戶端的時鐘頻率,不會快於某個常數量(constant factor)。
若是一個客戶端的本地租期過時了,它就變得不能肯定master是否終結了它的會話。因而這個客戶端清空並禁用本身的緩存,咱們稱它的會話處於危險(jeopardy)中。客戶端在等待一個以後的被稱做寬限期的間隔,默認是45秒。若是這個客戶端和master在寬限期結束以前設法完成了一個成功的KeepAlive,客戶端就再開啓它的緩存。不然,客戶端假定會話已通過期。這樣作了,因此Chubby的API調用不會在Chubby單元變成不可訪問時無限期阻塞,若是在通信從新創建以前寬限期結束了,調用返回一個錯誤。
Chubby庫可以經過一個」危險」(jeopardy)事件在寬限期開始時通知應用程序。當知道會話在通信問題後倖存下來,一個」安全」(safe)事件告知應用程序繼續工做;而若是會話時間過去了,一個」過時」(expire)事件被髮送給應用程序。這些通知容許應用程序在對它的會話狀態不肯定時停下來,而且在問題被證實是瞬時的時不須要重啓進行恢復。在啓動開銷很高的服務中,這在避免服務不可用方面多是很重要的。
若是一個客戶端持有某個節點上的句柄 H,而且任意一個H上的操做都由於關聯的會話過時了而失敗,那麼全部接下來的H上的操做(除了Close()和Poison()) 將以一樣的方式失敗。客戶端能夠用這去保證網絡和服務器不可用時會致使隨後的一串操做丟失了,而不是一個隨機的操做子序列丟失,這樣就容許複雜的修改能夠以其最後一次寫標記爲已提交。
當一個master失效了或者失去了master身份時,它丟棄內存中有關會話、句柄和鎖的信息。有權力的(authoritative)會話租期計時器開始在master上運行。這樣,直到一個新的master被選舉出來,租期計時器才中止;這是合法的由於它等價於延長客戶端的租期。若是一個master選舉很快發生,客戶端能夠在它們本地(近似的)租期計時器過時以前與新的master聯繫。若是選舉用了很長時間,客戶端刷空它們的緩存並等待一個寬限期(grace period),同時嘗試尋找新的master。以這種方式,寬限期(grace period)使得會話在跨越超出正常租期的故障恢復期間被維持。
圖2展現了一個漫長的故障恢復事件中的事件序列,在這個恢復事件中客戶端必須使用它的寬限期來保留它的會話。時間從左到右延伸,可是時間是不能夠往回縮的。客戶端會話租期顯示爲粗箭頭,它既被新的、老的Master看到(M1-3,上方),也被客戶端都能看到(C1-3,下方)。傾斜向上的箭頭標示KeepAlive請求,傾斜向上的箭頭標示這些KeepAlive請求的應答。原來的Master有一個給客戶端的租期M1,而客戶端有一個(對租期的)保守的估計C1。在經過KeepAlive應答2通知客戶端以前,原Master允諾了一個新的租期M2;客戶端可以延長它的視線爲租期C2。原Master在應答下一個KeepAlive以前死掉了,過了一段時間後另外一個master被選出。最終客戶端的租期估計值(C2)過時了。而後客戶端清空它的緩存,併爲寬限期啓動一個計時器。
在這段時間裏,客戶端不能肯定它的租期在master上是否已通過期了。它沒有銷燬它的會話,可是它阻塞全部應用程序對它的API的調用,以防止應用程序觀測到不一致的數據。在寬限期開始時,Chubby的庫發送一個危險事件給應用程序,容許它停下來直到會話狀態可以肯定。最終一個新的master選舉完成了。Master最初使用對它的前任可能持有的給客戶端的租期的保守估算值M3。新Master的來自客戶端的第一個KeepAlive請求(4)被拒絕了,由於它的master代數不正確(下文有詳細描述)。重試請求(6)成功了,可是一般不去延長master租期,由於M3是保守的。而後它的回覆(7)容許客戶端延再一次長它的租期(C3),還能夠選擇通知應用程序其會話再也不處於危險狀態。由於寬限期足夠長,能夠覆蓋從租期C2的結束到租期C3的開始之間的間隔,客戶端除了觀測到延遲不會看到別的。但假如寬限期比這個間隔短,客戶端將丟棄會話,並將失敗報告給應用程序。
一旦客戶端與新的master聯繫上,客戶端庫和master協做提供給應用程序一種沒有故障發生過的假象。爲達到此目的,新的master必須從新構造一個前面的master擁有的內存狀態的保守估算(conservative approximation)。它經過讀取存儲在硬盤上的數據(經過常規的數據庫複製協議複製)來完成一部分,經過從客戶端獲取狀態完成一部分,經過保守的假設(conservative assumptions)完成一部分。數據庫記錄了每一個會話、被持有的鎖和臨時文件。
一個新選出來的master繼續處理:
1. 它先選擇一個新的代編號(epoch number),這個編號在客戶端每次請求時都要求出示。Master拒絕來自使用較早的代編號的客戶端的請求,並提供新的代編號給它們。這保證了新的master將不會響應一個很早的發給前一個master的包,即便此前的這個Master與如今的master在同一臺機器上。
2. 新的Master可能會響應對master的位置的請求(master-location requests),可是不會當即開始處理傳入的會話相關的操做。
3. 它爲記錄在數據庫中的會話和鎖構建內存數據結構。會話租期被延長至上一個master可能當時正使用的最大期限。
4. Master如今容許客戶端進行KeepAlive,但不能執行其餘會話相關的操做。
5. It emits a fail-over event to each session; this causes clients to flush their caches (because they may have missed invalidations), and to warn applications that other events may have been lost.
5. 它發出一個故障切換事件給每一個會話;這引發客戶端刷新它們的緩存(由於這些緩存可能錯過了過時信號(invalidations)),並警告應用程序別的事件可能已經丟失。
6. Master一直等待,直到每一個會話應答了故障切換事件或者使其會話過時。
7. Master容許全部的操做繼續處理。
8. 若是一個客戶端使用一個在故障切換以前建立的句柄(可依據句柄中的序號來判斷),master從新建立了這個句柄的內存印象(in-memory representation),並執行調用。若是一個這樣的重建後的句柄被關閉後,master在內存中記錄它,這樣它就不能在這個master代(epoch)中再次建立;這保證了一個延遲的或者重複的網絡包不能偶然地重建一個已經關閉的句柄。一個有問題的客戶端能在將來的master代中重建一個已關閉的句柄,但假若該客戶端已經有問題的話,則這樣不會有什麼危害。
9. 在通過某個間隔(如,一分鐘)後,master刪除沒有已打開的文件句柄的臨時文件。在故障切換後,客戶端應該在這個間隔期間刷新它們在臨時文件上的句柄。這種機制有一個不幸的效果是,若是該文件的最後一個客戶端在故障切換期間丟失了會話的話,臨時文件可能不會很快消失。
讀者將不意外地得知,遠遠不像系統的其餘部分那樣被頻繁使用的故障切換代碼,是一些有趣的bug的豐富來源。
初版的Chubby使用帶複製的Berkeley DB版本[20]做爲它的數據庫。Berkeley DB提供了映射字節串的鍵到任意的字節串值上B-樹。咱們設置了一個按照路徑名稱中的節數排序的鍵比較函數,這樣就容許節點用它們的路徑名做爲鍵,同時保證兄弟節點在排序順序中相鄰。因爲Chubby不使用基於路徑的權限,數據庫中的一次查找就能夠知足每次文件訪問。
Berkeley DB使用一種分佈式一致協議將它的數據庫日誌複製到一組服務器上。只要加上master租期,就吻合Chubby的設計,這使得實現很簡單。相比於在Berkeley DB的B-樹代碼被普遍使用而且很成熟,其複製相關的代碼是最近增長的,而且用戶較少。維護者必須優先維護和改進他們的最受歡迎的產品特性。
在Berkeley DB的維護者解決咱們遇到的問題期間,咱們以爲使用這些複製相關的代碼將咱們暴露在比咱們願負擔的更多的風險中。結果,咱們寫了一個簡單的,相似於Birrell et al.[2]的設計的,利用了日誌預寫和快照的數據庫。數據庫日誌仍是想之前同樣利用一致性協議在多個副本之間分佈。Chubby至少了Berkeley DB的不多的特性,這樣從新可以使整個系統大幅簡化;例如,咱們須要原子操做,可是咱們確實不須要通用的事務。
每隔幾個小時,Chubby單元的mster將它的數據庫快照寫到另外一棟樓裏的GFS文件服務器上 [7]。使用另外一棟樓(的文件服務器)既確保了備份會在樓宇損毀中保存下來,也確保備份不會在系統中引入循環依賴,同一棟樓中的GFS單元潛在地可能依賴於Chubby單元來選舉它的master。 備份既提供了災難恢復,也提供了一種初始化一個新建立的副本的數據庫的途徑,而不會將初始化的壓力放在其餘正在提供服務的副本上。
Chubby容許一組文件(a collection of files)從一個單元鏡像到另一個單元。鏡像是很快的,由於文件很小,而且事件機制(參見第2.5節)會在文件增長、刪除或修改時當即通知鏡像處理相關的代碼。在沒有網絡問題的前提下,變化會一秒以內便在世界範圍內的不少個鏡像中反映出來。若是一個鏡像不可到達,它保持不變直到鏈接恢復。而後經過比較校驗碼識別出更新了的文件。
鏡像被最多見地用於將配置文件複製到各類各樣的遍及全世界的計算集羣。一個叫global的特殊的Chubby單元,含有一個子樹 /ls/global/master,這個子樹被鏡像到每一個其餘的Chubby單元的子樹 /ls/cell/slave。這個Global單元很特別,由於它的五個副本分散在全球相隔很遠的多個地區,因此幾乎老是可以從大部分國家/地區訪問到它。
在這些從global單元鏡像的文件中,有Chubby本身的訪問控制列表,多種含有Chubby單元和其餘系統向咱們的監控服務廣告它們的存在的文件,容許客戶端定位巨大的數據集如BigTable單元的指針信息(的文件),以及許多其餘系統的配置文件。
由於Chubby的客戶端是獨立的進程,因此Chubby必須處理比人們預想的更多的客戶端;咱們曾經看見過90,000個客戶端直接與一個Chubby服務器通信 — 這遠大於涉及到的機器總數。由於在一個單元之中有一個master,並且它的機器跟那些客戶端是同樣的,因而客戶端們能以巨大的優點(margin)將master戰勝。所以,最有效的擴展技術減小與master的通訊經過一個重大的因數(Thus, the most effective scaling techniques reduce communication with the master by a significant factor)。假設master沒有嚴重的性能缺陷,master上的請求處理中的較小改進只有很小的(have little)效果。咱們使用了下面幾種方法:
咱們能夠建立任意數量的Chubby單元;客戶端幾乎老是使用一個鄰近的單元(經過DNS找出)以免對遠程機器的依賴。咱們的有表明性的發佈讓一個有幾千臺機器的數據中心使用一個Chubby單元。
Master可能會在負載很重時將租期(lease times)從默認的12秒到延長到最大60秒左右,這樣它就只須要處理更少的KeepAlive RPC調用。(KeepAlive是到目前爲止的佔統治地位的請求類型(參考第4.1節),而且未能及時處理它們是一個超負荷的服務器的表明性的失敗模式;客戶端對其餘調用中的延遲變化至關地不敏感。)
Chubby客戶端緩存文件數據,元數據,文件缺失(the absence of files)和打開的句柄,以減小它們在服務器上作的調用。
咱們使用轉換Chubby協議爲不那麼複雜協議如DNS等的協議轉換服務器。咱們後文描述其中的一些。
這裏咱們描繪兩種熟悉的機制,代理(proxies)和分區(partitioning),這兩種機制將容許Chubby擴展更多。咱們如今尚未在產品環境中使用它們,可是它們已經被設計出來了,而且可能很快被使用。咱們尚未顯示出考慮擴展到五倍之外的須要:第一,在一個數據中心想要放的或者依賴於單個實例服務的機器的數目是有限制的;第二,由於咱們爲Chubby的客戶端和服務器使用相似配置的機器,硬件上的提高增長了每臺機器上的客戶端的數量時,同時也增長了每臺服務器的容量。
Chubby的協議能夠由可信的進程代理(在兩邊都使用相同的協議),這個進程未來自其餘客戶端的請求傳遞給Chubby單元。一個代理能夠經過處理KeepAlive和讀請求減小服務器負載,它不能減小從代理的緩存中穿過的寫入流量。但即便有積極的客戶端緩存,寫入流量仍遠小於Chubby正常負載的百分之一(參見第4.1節),這樣代理容許大幅提高客戶端的數量。若是一個代理處理Nproxy個客戶端,KeepAlive流量將減小Nproxy倍,而Nproxy多是一萬甚至更大。A proxy cache can reduce read traffic by at most the mean amount of read-sharing–a factor of around 10 (參見第4.1節)。但由於讀只佔當前的Chubby負載的10%如下,KeepAlive流量上的節省仍然是到目前爲止更重要的成效。
代理增長了一個額外的RPC用於寫和第一次讀。人們可能會預期代理會使得Chubby單元的臨時不可用次數至少是之前的兩倍,由於每一個代理後的客戶端依賴於兩臺可能失效的機器:它的代理和Chubby的Master。
機敏的讀者會注意到2.9節描述的故障切換,對於代理機制不太理想。咱們將的第4.4節討論這個問題。
像第2.3節提到的,Chubby的接口被選擇爲Chubby單元的名字空間能在服務器之間劃分。雖然咱們還不須要它,可是如今的代碼(code)可以經過目錄劃分名字空間。 若是開啓劃分,一個Chubby單元將由 N 個分區 組成,每一個分區都有一組副本(replicas)和一個master。每一個在目錄 D 中的節點 P(D/C) 將被保存在分區 P(D/C)= hash(D) mod N 上。注意 D 上的元數據可能存儲在另外一個不一樣的分區 P(D) = hash(D’) mod N 上, D’ 是 D 的父目錄。
分區被計劃爲以很小的分區之間的通訊來啓用很大的Chubby單元集(Chubby cells)。儘管Chubby沒有硬連接(hard links),目錄修改時間,和跨目錄的重命名操做,一小部分操做仍然須要跨分區通訊:
ACL 自己是文件,因此一個分區可能會使用另外一個分區作權限校驗。 而後, ACL 文件能夠很快捷地緩存;只有Open() 和 Delete() 操做須要作 ACL 覈查;而且大部分客戶端讀公開可訪問的、不須要ACL的文件。
當一個目錄被刪除時,一個跨分區的調用可能被須要以確保這個目錄是空的。
由於每一個分區獨立地處理大部分調用,咱們預期這種通訊只會對性能和可用性形成不太大的影響。
除非分區數目 N 很大,咱們預期每一個客戶端將與大部分分區聯繫。 所以,分區將任意一個分區上的讀寫通信量(traffic)下降爲原來的N分之一,可是不會下降 KeepAlive 的通訊量。若是Chubby須要處理更多客戶端的話,咱們的應對方案將會聯合使用代理和分區。
下面的表給出了某個Chubby單元的快照的統計信息;其中的RPC頻率是從一個10分鐘的時間段裏計算出來的。這些數字在Google的Chubby單元中是很常見的。
從表中能夠看到下面幾點:
許多文件被用於命名(naming),參見第$4.3節。
參數配置,訪問控制和元數據文件(相似於文件系統的super-blocks)很廣泛。
Negative Caching很突出。
平均來看,每一個緩存文件由230k/24k≈10個客戶端使用。
較少的客戶端持有鎖,共享鎖不多;這與鎖定被用於primary選舉和在多個副本之間劃分數據的預期是相符合的。
RPC流量主要是會話 KeepAlive,有少許的讀(緩存未命中引發),只有不多的寫或鎖請求。
如今咱們簡要描述寫咱們的Chubby單元不可用的緣由。若是咱們假定(樂觀地)若是一個單元有一個master願意服務則是」在線的(up)」,在咱們的Chubby的採樣中,在數週內咱們記錄下合計61次不可用,合計共有700單元-天(??)。咱們排除了維護引發的關閉數據中心時的不可用。全部其餘的不可用的緣由包括:網絡擁塞,維護,超負荷和運營人員引發的錯誤,軟件,硬件。大部分不可用在15s之內或者更短,另外52次在30秒之內;咱們的大部分應用程序不會被Chubby的30秒內的不可用顯著地影響到。剩下的就此不可用由這些緣由引發:網絡維護(4),可疑的網絡鏈接問題(2),軟件錯誤(2),超負荷(1)。
在好幾打Chubby單元年(cell-years)的運行中,咱們有六次丟失了數據,由數據庫軟件錯誤(4)和運營人員錯誤(2)引發;與硬件錯誤沒有關係。具備諷刺意味的是,操做錯誤與爲避免軟件錯誤的升級有關。咱們有兩次糾正了由非master的副本的軟件引發的損壞。
Chubby的數據都在內存中,因此大部分操做的代價都很低。咱們生產服務器的中位請求延時始終保持在一毫秒之內(a small fraction of a millisecond),無論Chubby單元的負載如何,直到Chubby單元超出負荷,此時延遲大幅增長,會話掉線。超負荷一般發生在許多(> 90,000)的會話激活時,可是也能由異常條件引發:當客戶端們同時地發起幾百萬讀請求時(在4.3節有描述),或者當客戶端庫的一個錯誤禁用了某些讀的緩存,致使每秒成千上萬的請求。由於大部分RPC是KeepAlive,服務器能夠經過延長會話租期(參考第三章)跟許多客戶端維持一個急哦較低的中位請求延時。Group commit reduces the effective work done per request when bursts of writes arrive,可是這不多見。
客戶端觀測到的RPC讀延遲受限於RPC系統和網絡;對一個本地Chubby單元而言在1毫秒如下,但跨洲則須要250毫秒。RPC寫(包括鎖操做)被數據庫的日誌更新進一步地延遲了5-10毫秒,可是若是一個最近剛失敗過的客戶端要緩存該文件的話,最高能夠被延遲幾十秒。即便這種寫延遲的變化程度對服務器上的中位請求延遲也只有很小的影響,由於寫至關地罕見。
若是會話沒有丟棄的話,Chubby是至關不受延遲變化的影響的。在一個時間點上(At one point),咱們在Open()中增長了人爲的延時以抑制***性的客戶端(參考第4.5節);開發人員只會在延時超過十秒而且反覆地被延時時會注意到。咱們發現擴展Chubby的關鍵不是服務器的性能;下降到服務器端的通信能夠有遠遠大得多的做用。沒有重要的努力用在調優讀/寫相關的服務器代碼路徑上;咱們檢查了沒有極壞的缺陷(bug)存在,而後集中在更有效的擴展機制上。在另外一方面,若是一個性能缺陷影響到客戶端會每秒讀幾千次的本地Chubby緩存,開發者確定會注意到。
Google的基礎架構設施大部分是用C++寫的,可是一些正在不斷增長中的系統正在用Java編寫 [8]。這種趨勢呈現出Chubby的一個預料以外的問題,而Chubby有一個複雜的客戶端協議和不那麼簡單的(non-trival)客戶端庫。
Java經過讓它有些不厭其煩地與其餘語言鏈接,以漸進採用的損耗,來鼓勵整個程序的移植性。一般的用於訪問非原生(non-native)的庫的機制是JNI [15],但JNI被認爲很慢而且很笨重。咱們的Java程序員們是如此不喜歡JNI以致於爲了不使用JNI而傾向於將很大的庫轉換爲Java,並維護它們。
Chubby的客戶端庫有7000行代碼(跟服務器差很少),而且客戶端協議很精細。去維持這樣一個用Java寫的庫需當心和代價,同時一個沒有緩存的實現將埋葬Chubby服務器。所以,咱們的Java用戶跑着多份協議轉換服務器,這些服務器暴露一個很相似於Chubby客戶端API的簡單的RPC協議。即便過後回想,怎麼樣去避免編寫、運行並維護這些額外的服務器仍然是不那麼明顯的。
儘管Chubby被設計爲一種鎖服務,咱們發現它的最流行的應用是作爲名字服務器。
在常規的因特網命名系統 — DNS中,緩存是基於時間的。DNS條目有一個生存時間(TTL),DNS數據在這個時間內沒有獲得刷新的話,就將被丟棄。一般很容易選擇一個合適的TTL值,可是當但願對失敗的服務作快速替換時,這個TTL會很小,以致於使DNS服務器超載。
例如,很常見地,咱們的開發者運行有數千個進程的任務,且其中的每一個進程之間都要通訊,這引發了二次方級的DNS查詢。咱們可能但願使用一個60秒的TTL,這將容許行爲不正常的客戶端沒有過長的延遲就被換掉,同時也在咱們的環境中不被認爲是一個過長的替換時間。在這種狀況下,爲維持某一個小到3000個客戶端的任務的DNS緩存,可能須要每秒15萬次查找。(做爲比照,一個2-CPU 2.6GHz Xeon DNS服務器麼秒可能能處理5萬請求)。更大的任務產生更惡劣的問題,而且多個任務可能同時執行。在引入Chubby之前,DNS負載的波動對Google來時曾經是的一個嚴重問題。
相反,Chubby的緩存使用顯示地失效(invalidations),所以在沒有修改時,一個恆定頻率的會話KeepAlive請求能無限期地維護一個客戶端上的任意數目的緩存條目。 一個2-CPU 2.6GHz Xeon的Chubby Master 曾被見到過處理9萬個直接與它通信(沒有Proxy)的客戶端;這些客戶端包括了具備上文描述過的那種通信模式的龐大任務。不須要輪詢每一個名字的提供快速名字更新的能力是如此吸引人,以致於如今Google的大部分系統都由Chubby提供名字服務。
儘管Chubby的緩存容許一個單獨的單元能承受大量的客戶端,負載峯值仍多是一個問題。當咱們第一次發佈基於Chubby的名字服務時,啓動了一個3千進程的任務(其結果是產生了9百萬個請求)可能將Chubby的Master壓垮。爲了解決這個問題,咱們選擇將名字條目組成批次,於是一次查詢將返回同一個任務內的大量的(一般是100個)相關進程的名字映射,並緩存起來。
Chubby提供的緩存語義要比名字服務須要的緩存語義更加精確;名字解析只要求按期的通知,而非徹底的一致性。於是,這裏有一個時機,能夠經過引入特別地爲名字查找設計的簡單協議轉換服務器,來下降Chubby的負載。假如咱們預見到將Chubby用做名字服務的用法,咱們可能選擇實現完整的代理,而不是提供這種簡單但沒有必要的額外的服務器。
有一種更進一步的協議轉換服務器存在:Chubby DNS服務器。這中服務器將存儲在Chubby裏的命名數據提供給DNS客戶端。這種服務器很重要,它既能減小了DNS名字到Chubby名字之間的轉換,也能適應已經存在的不能輕易轉換的應用程序,例如瀏覽器。
原來的master故障切換(§2.9)的設計要求master在新的會話建立時將新會話寫入數據庫。在Berkeley DB版本的鎖服務器中,在許多進程同時啓動時建立會話的開銷成爲問題。爲了不超載,服務器被修改成不在會話建立時保存會話信息到數據庫,而是在會話的第一次修改、鎖請求或者打開臨時文件時寫入數據庫中。此外,活動會話在每次KeepAlive時以某種機率被記錄到數據庫中。這樣,只讀的會話信息的寫入在時間上分散開來。
儘管爲了不超載,這種優化是必要的,但它有一種負面效應:年輕的只讀的會話可能沒有被記錄在數據庫中,於是在故障切換髮生時可能被丟棄。雖然這種會話不持有鎖,這也是不安全的;若是全部已記錄的會話在被丟棄的會話的租期到期以前簽入到新的master中,被丟棄的會話可能在一段時間內讀到髒數據。這在實際中不多見,可是在大型系統中,幾乎能夠確定某些會話將簽入失敗,於是迫使新的master必須等待最大租期時間。儘管這樣,咱們修改了故障切換設計,既避免這個效應,也避開當前這種模式帶給代理的複雜性。
在新的設計下,咱們徹底避免在數據庫中記錄會話,而是以目前master重建句柄的方式重建它們(參見§2.9,8)。一個新的master如今必須等待一個完整的最壞狀況下的租期過時,才能容許操做繼續處理。由於它不能得知是否全部的會話都簽入(check in)了(參見§2.9,6)。再者,這在實際中只有很小的影響,由於極可能不是全部的會話都會簽入。
一旦會話能在沒有在磁盤上的狀態信息重建,代理服務器就能管理master不知情的會話。一個額外的只提供給代理的操做容許它們修改與鎖有關聯的會話。這容許在在代理失敗時,一個代理可從另外一個代理取得一個客戶端。Master上增長的惟一必要的修改是保證不放棄與代理會話關聯的鎖或者臨時文件句柄,直到一個新的代理有機會得到它們。
Google的項目團隊可自由地設置它們本身的Chubby單元,但這樣作增強了他們的維護負擔,並且消耗了額外的硬件資源。許多服務所以使用共享的Chubby單元,這使得隔離行爲不正常的客戶端變得很重要。Chubby是本是計劃用於在單個公司內部運行的,所以針對它的惡意的服務拒絕襲擊是很罕見的。然而,錯誤、誤解和開發者不同的預期都致使了相似於襲擊的效應。
咱們的某些解決方式是很粗暴的。例如,咱們檢查項目團隊計劃使用Chubby的方式,並在檢查令人滿意以前拒絕其訪問共享的Chubby名字空間。這種方式的一個問題是開發者常常不能預計他們的服務未來會被如何使用,以及使用量會怎麼樣增加。
咱們的檢查的最重要的方面是判斷任何的Chubby的資源(RPC頻率,硬盤空間,文件數目)是否會隨着該項目的用戶數量或處理的數據量線性(或者更壞)地增加。任何線性增加必須被由一個修正參數來調低,這個修正參數能夠調整爲下降Chubby上負載到一個合理邊界的值。儘管如此,咱們的早期檢查仍然不完全足夠。
一個相關的問題是大部分軟件文檔中缺乏性能建議。一個由某個團隊編寫的模塊,可能在一年後被另外一個團隊複用,並帶來極糟糕的後果。有時很難向接口設計者解釋他們必須修改他們的接口,不是由於它們很差,而是由於其餘開發者可能較少知道RPC的代價。
下面咱們列出了咱們遇到過的一些問題。
缺乏可應付衝擊的緩存(aggressive caching) 最初,咱們沒有意識到緩存不存在的文件和重用打開的文件句柄的關鍵性的須要。 儘管在培訓時進行了嘗試,咱們的開發人員一般在一個文件不存在時,仍會編寫無限重試的循環,或者經過重複地關閉/打開一個文件來輪詢文件(實際上只需打開一次就能夠)。
最初,咱們對付這些重試循環的方法是,在某個應用程序短期內作了許多Open()同一個文件的嘗試時,引入指數級遞增的延時。在某些情形下,這暴露了開發者接受了的漏洞,可是一般這須要咱們花更多的時間作培訓。最後,讓重複性的Open()調用廉價會更容易一些。
缺乏限額 Chubby從沒有被計劃用作存放大量數據的存儲系統,所以沒有存儲限額。過後反思,這是很弱智的。Google的某個項目寫了一個跟蹤數據上傳的模塊,存儲了某些元數據到Chubby中。這種上傳不多發生,而且只限於一小羣人,所以使用的空間是有上限的。然而,兩個其餘服務開始使用該模塊做爲追蹤來自於多得多的用戶羣的上傳的手段。不可避免地,他們的服務一直增加,直到對Chubby的使用到達極限:一個1.5M字節的文件在每次用戶動做時被整個重寫,被這些服務使用的空間超出了其餘全部Chubby客戶端加起來的空間需求。
咱們在文件大小上引入了一個限制(256KB),並推進這些服務遷移到更合適的存儲系統上去。但要在這些由很繁忙的人們維護着的生產系統上要取得重大變化是很困難的,—-花了大約一年將這些數據遷移到其餘地方。
發佈/訂閱 曾有數次將Chubby的事件機制做爲Zephyr[6]式的發佈/訂閱系統的嘗試。Chubby的超重量級的保證和它在維護緩存一致性時使用過時(invalidation)而不是更新,使得它除了無心義的訂閱/發佈示例以外,都很慢、效率很低。幸運地是,全部這樣的應用都在從新設計應用程序的成本變得太大之前被叫停(caught)。
這裏咱們列出教訓和多種若是有機會的話咱們可能會作出的設計變動:
開發者極少考慮可用性。 咱們發現開發者極少考慮失敗的可能性,並傾向於認爲像Chubby這樣的服務好像會用於可用。例如,開發者曾經構建了一個用了幾百臺機器的系統,這個系統在Chubby選舉出一個新的Master時就發起一個持續幾十分鐘的恢復處理過程。這將一個單一故障的後果放大到受影響的機器數與時間的乘積的倍數。咱們偏好開發者爲短期的Chubby不可用作好計劃,以便這樣的事件對他們的應用只有不多影響,甚至沒有影響。這是粗粒度鎖定的一個有爭議的地方,在第2.1節討論了這個問題。
開發者一樣未能成功地意識到服務上線和服務對他們的應用可用的之間的區別。例如,全局Chubby單元(參見 $2.12)基本上老是在線的,由於不多會有兩個物理距離遙遠的數據中心同時下線。而後,對於某個客戶端而言,它被觀測到的可用性一般低於這個客戶端的觀測到的本地Chubby單元的可用性。首先,本地單元較少可能與客戶端之間發生網絡斷開,另外,儘管本地Chubby單元可能會由於維護操做而下線,但一樣的維護操做也會直接影響到客戶端,所以Chubby的不可用就不會被客戶端看到。
咱們的API選擇一樣能影響到開發者處理Chubby不可用的方式。例如,Chubby提供了一個時間容許客戶端檢測何時發生了master的故障切換。原本這是用於客戶端檢查可能的變化的,由於別的時間可能丟失了。不幸的是,許多開發選擇在收到這個事件是關閉他們的程序,所以大幅下降了他們系統的可用性。咱們原本有可能經過給「文件改變(file-change)」事件以作得更好,甚或能夠保證在故障切換期間不丟失事件。
目前咱們用了三種機制防止開發者對Chubby的可用性過度樂觀,特別是對全局單元(Global cell) 的可用性過度樂觀。首先,如前文提到的那樣,咱們複查各個項目團隊打算如如何使用Chubby,並建議它們不要使用可能將它們的可用性與Chubby的可用性綁定的太緊密。第二,如今咱們提供了執行某些高層次任務的庫,所以開發者被自動地與Chubby中斷隔離。第三,咱們利用每次Chubby中斷的過後分析做爲一種手段,不只清除Chubby和運維過程當中的缺陷(bugs),還下降應用程序對Chubby的可用性的敏感性 — 兩方面都帶來系統的總體上更好的可用性。
差勁的API選擇致使了不可預料的後果 對於大部分而言,咱們的API演化得很好,可是有一個錯誤很突出。咱們的取消長期運行(long-running)的調用是Close()和Poison() RPC,它們也會丟棄服務器的分配給對應句柄的狀態 。這阻止了能請求鎖的句柄被 共享,例如,被多個線程共享。咱們可能會添加一個 Cancel() RPC以容許更多打開的句柄的共享。
RPC的使用影響了傳輸協議 KeepAlive被用於刷新客戶端的會話租期,也被用於從master端傳遞事件和緩存過時到客戶端。這個設計有自動的符合預期的效應:一個客戶端不能在沒有應答緩存過時的狀況下刷新會話狀態。
這彷佛很理想,除了這樣會在傳輸協議的選擇中引入緊張情形之外。TCP的擁塞時回退(back off)策略不關心更高層的超時,例如Chubby的租期,所以基於TCP的KeepAlive在發生高網絡擁塞時致使許多丟失的會話。咱們被迫經過UDP而不是TCP發送KeepAlive RPC調用;UDP沒有擁塞回避機制,所以咱們只有當高層的時間界限必須知足時才使用UDP協議。
咱們可能增長一個基於TCP的GetEvent() RPC來加強傳輸協議,這個RPC將用於在正常情形下傳遞事件和過時,它與KeepAlives的方式相同。KeepAlive回覆仍將包含一個未應答事件列表,於是事件最終都將被應答。
Chubby是基於長期穩定的思想(well-established ideas)之上的。Chubby的緩存設計源自於分佈式文件系統相關的成果[10]。它的會話和緩存標記(tokens)在行爲上與Echo的相應部分相似[17];會話下降租期壓力與V 系統(V System)相似。放出一個通常性的(general-purpose)鎖服務的理念最先出如今VMS[23]中,儘管該系統最初使用了一個特殊目的的許可低延遲交互的高速聯網系統。 像它的緩存模型,Chubby的API是創建在文件系統模型上的,其中包括相似於文件系統的名字空間要比單純的文件要方便不少的思想。 [18, 21, 22] (原文:Like its caching model, Chubby’s API is based on a file-system model, including the idea that a filesystem-like name space is convenient for more than just files [18, 21, 22].)
Chubby區別於一個像Echo或者AFS[10]這樣的文件系統,主要表如今在它的性能和存儲意圖上:客戶端不讀、寫或存儲大量數據,而且他們不期待很高的吞吐量,甚至若是數據不緩存的話也不預期低延時。它們卻期待一致性,可用性和可靠性,可是這些屬性在性能不那麼重要是比較容易達成。由於Chubby的數據庫很小,咱們能在線存儲它的多個拷貝(一般是五個副本和少數備份)。咱們天天作不少次完整備份,並經過數據庫狀態的校驗碼(checksums),咱們每隔幾個小時互相比較副本。對常規文件系統的性能和存儲需求的弱化(weakening)容許咱們用一個Chubby master服務數千個客戶端。咱們經過提供一箇中心點,許多客戶端在這個中心點共享信息和協同活動,解決了一類咱們的系統開發人員面對的問題。
各類文獻(literature)描述了大量的文件系統和鎖服務器,因此不可能作一個完全的比較。因而咱們選擇了其中一個作詳細比較:咱們選擇與Boxwood的鎖服務16比較,因它是最近設計的,而且設計爲運行在鬆耦合環境下,另外它的設計在多個方面與Chubby不一樣,某些頗有趣,某些是本質上的。(原文:some interesting and some incidental)
Chubby 實現了鎖,一個可靠的小文件存儲系統,以及在單個服務中的會話/租期機制。相反,Boxwood將這些劃分爲三部分:鎖服務,Paxos服務(一個可靠的狀態存儲庫),以及對應的失敗檢測服務。 Boxwood系統它自己使用了這三個組件,可是另外一個系統可能獨立地使用這些構建塊。咱們懷疑設計上的這個區別源自不一樣的目標客戶羣體(target audience)。Chubby被計劃用於多種多樣的目標客戶(audience) 和應用的混合體, 它的用戶有建立新分佈式系統的專家,也有編寫管理腳本的新手。對於咱們的環境,一個提供熟知的API的大規模(large-scale)的共享服務彷佛更有吸引力。相反,Boxwood提供了一個工具包(至少咱們看到的是這樣),這個工具包適合於一小部分經驗豐富、老練的開發者用在那些可能共享代碼但不須要一塊兒使用的項目中。
In many cases, Chubby provides a higher-level interface than Boxwood. For example, Chubby combines the lock and file names spaces, while Boxwood’s lock names are simple byte sequences. Chubby clients cache file state by default; a client of Boxwood’s Paxos service could implement caching via the lock service, but would probably use the caching provided by Boxwood itself.
The two systems have markedly different default parameters, chosen for different expectations: Each Boxwood failure detector is contacted by each client every 200ms with a timeout of 1s; Chubby’s default lease time is 12s and KeepAlives are exchanged every 7s. Boxwood’s subcomponents use two or three replicas to achieve availability, while we typically use five replicas per cell. However, these choices alone do not suggest a deep design difference, but rather an indication of how parameters in such systems must be adjusted to accommodate more client machines, or the uncertainties of racks shared with other projects.
A more interesting difference is the introduction of Chubby’s grace period, which Boxwood lacks. (Recall
that the grace period allows clients to ride out long Chubby master outages without losing sessions or locks.
Boxwood’s 「grace period」 is the equivalent of Chubby’s 「session lease」, a different concept.) Again, this difference is the result of differing expectations about scale and failure probability in the two systems. Although master fail-overs are rare, a lost Chubby lock is expensive for clients.
Finally, locks in the two systems are intended for different purposes. Chubby locks are heavier-weight, and
need sequencers to allow externals resources to be protected safely, while Boxwood locks are lighter-weight,
and intended primarily for use within Boxwood.
Chubby is a distributed lock service intended for coarsegrained synchronization of activities within Google’s
distributed systems; it has found wider use as a name service and repository for configuration information.
Its design is based on well-known ideas that have meshed well: distributed consensus among a few replicas
for fault tolerance, consistent client-side caching to reduce server load while retaining simple semantics, timely notification of updates, and a familiar file system interface. We use caching, protocol-conversion servers, and simple load adaptation to allow it scale to tens of thousands of client processes per Chubby instance. We expect to scale it further via proxies and partitioning.
Chubby has become Google’s primary internal name service; it is a common rendezvous mechanism for systems
such as MapReduce [4]; the storage systems GFS and Bigtable use Chubby to elect a primary from redundant
replicas; and it is a standard repository for files that require high availability, such as access control lists.
Many contributed to the Chubby system: Sharon Perl wrote the replication layer on Berkeley DB; Tushar
Chandra and Robert Griesemer wrote the replicated database that replaced Berkeley DB; Ramsey Haddad
connected the API to Google’s file system interface; Dave Presotto, Sean Owen, Doug Zongker and Praveen
Tamara wrote the Chubby DNS, Java, and naming protocol-converters, and the full Chubby proxy respectively; Vadim Furman added the caching of open handles and file-absence; Rob Pike, Sean Quinlan and Sanjay Ghemawat gave valuable design advice; and many Google developers uncovered early weaknesses.
[1] BIRMAN, K. P., AND JOSEPH, T. A. Exploiting virtual synchrony in distributed systems. In 11th SOSP (1987), pp. 123–138.
[2] BIRRELL, A., JONES, M. B., AND WOBBER, E. A simple and efficient implementation for small databases. In 11th SOSP (1987), pp. 149–154.
[3] CHANG, F., DEAN, J., GHEMAWAT, S., HSIEH, W. C., WALLACH, D. A., BURROWS, M., CHANDRA, T., FIKES, A., AND GRUBER, R. Bigtable: A distributed structured data storage system. In 7th OSDI (2006).
[4] DEAN, J., AND GHEMAWAT, S. MapReduce: Simplified data processing on large clusters. In 6th OSDI 2004), pp. 137–150.
[5] FISCHER, M. J., LYNCH, N. A., AND PATERSON, M. S. Impossibility of distributed consensus with one faulty process. J. ACM 32, 2 (April 1985), 374–382.
[6] FRENCH, R. S., AND KOHL, J. T. The Zephyr Programmer’s Manual. MIT Project Athena, Apr. 1989.
[7] GHEMAWAT, S., GOBIOFF, H., AND LEUNG, S.-T. The Google file system. In 19th SOSP (Dec. 2003), pp. 29–43.
[8] GOSLING, J., JOY, B., STEELE, G., AND BRACHA, G. Java Language Spec. (2nd Ed.). Addison-Wesley, 2000.
[9] GRAY, C. G., AND CHERITON, D. R. Leases: An efficient fault-tolerant mechanism for distributed file cache
consistency. In 12th SOSP (1989), pp. 202–210.
[10] HOWARD, J., KAZAR, M., MENEES, S., NICHOLS, D., SATYANARAYANAN, M., SIDEBOTHAM, R., AND WEST, M. Scale and performance in a distributed file system. ACM TOCS 6, 1 (Feb. 1988), 51–81.
[11] JEFFERSON, D. Virtual time. ACM TOPLAS, 3 (1985), 404–425.
[12] LAMPORT, L. The part-time parliament. ACM TOCS 16, 2 (1998), 133–169.
[13] LAMPORT, L. Paxos made simple. ACM SIGACT News 32, 4 (2001), 18–25.
[14] LAMPSON, B. W. How to build a highly available system using consensus. In Distributed Algorithms, vol. 1151 of LNCS. Springer–Verlag, 1996, pp. 1–17.
[15] LIANG, S. Java Native Interface: Programmer’s Guide and Reference. Addison-Wesley, 1999.
[16] MACCORMICK, J., MURPHY, N., NAJORK, M., THEKKATH, C. A., AND ZHOU, L. Boxwood: Abstractions as the foundation for storage infrastructure. In 6th OSDI (2004), pp. 105–120.
[17] MANN, T., BIRRELL, A., HISGEN, A., JERIAN, C., AND SWART, G. A coherent distributed file cache with directory write-behind. TOCS 12, 2 (1994), 123–164.
[18] MCJONES, P., AND SWART, G. Evolving the UNIX system interface to support multithreaded programs. Tech. Rep. 21, DEC SRC, 1987.
[19] OKI, B., AND LISKOV, B. Viewstamped replication: A general primary copy method to support highly-available distributed systems. In ACM PODC (1988).
[20] OLSON, M. A., BOSTIC, K., AND SELTZER, M. Berkeley DB. In USENIX (June 1999), pp. 183–192.
[21] PIKE, R., PRESOTTO, D. L., DORWARD, S., FLANDRENA, B., THOMPSON, K., TRICKEY, H., AND WINTERBOTTOM, P. Plan 9 from Bell Labs. Computing Systems 8, 2 (1995), 221–254.
[22] RITCHIE, D. M., AND THOMPSON, K. The UNIX timesharing system. CACM 17, 7 (1974), 365–375.
[23] SNAMAN, JR., W. E., AND THIEL, D. W. The VAX/VMS distributed lock manager. Digital Technical Journal 1, 5 (Sept. 1987), 29–44.
[24] YIN, J., MARTIN, J.-P., VENKATARAMANI, A., ALVISI, L., AND DAHLIN, M. Separating agreement from execution for byzantine fault tolerant services. In 19th SOSP (2003), pp. 253–267.
======================
cell Chubby 單元Chubby cell Chubby 單元session 會話bug 缺陷antipodes 洲際section $4.1 [P11]:but 250msbetweenantipodes.lock 鎖locking 鎖定