Dynamo 譯文 Dynamo: Amazon's Highly Available Key-value Store

Dynamo: Amazon’s Highly Available Key-value Store

Dynamo:Amazon的高可用性的鍵-值存儲系統

摘要

巨大規模系統的可靠性是我們在Amazon.com,這個世界上最大的電子商務公司之一,面對最大的挑戰之一,即使最輕微的系統中斷都有顯着的經濟後果並且影響到客戶的信賴。Amazon.com平臺,它爲全球許多網站服務,是實現在位於世界各地的許多數據中心中的成千上萬的服務器和網絡基礎設施之上。在這一規模中,各種大大小小的部件故障持續不斷髮生,管理持久化狀態的方法在面對這些故障時,驅使軟件系統的可靠性和可擴展性。

本文介紹Dynamo的設計和實現,一個高度可用的key-value存儲系統,一些Amazon的核心服務使用它用以提供一個“永遠在線”的用戶體驗。爲了達到這個級別的可用性,Dynamo在某些故障的場景中將犧牲一致性。它大量使用對象版本和應用程序協助的衝突協調方式以提供一個開發人員可以使用的新穎接口。

1簡介

Amazon運行一個全球性的電子商務服務平臺,在繁忙時段使用位於世界各地的許多數據中心的數千臺服務器爲幾千萬的客戶服務。Amazon平臺有嚴格的性能,可靠性和效率方面操作要求,並支持持續增長,因此平臺需要高度可擴展性。可靠性是最重要的要求之一,因爲即使最輕微的系統中斷都有顯著的經濟後果和影響客戶的信賴。此外,爲了支持持續增長,平臺需要高度可擴展性。

我們組織在運營Amazon平臺時所獲得的教訓之一是,一個系統的可靠性和可擴展性依賴於它的應用狀態如何管理。Amazon採用一種高度去中心化(decentralized),鬆散耦合,由數百個服務組成的面向服務架構。在這種環境中特別需要一個始終可用存儲技術。例如,即使磁盤故障,網絡路線狀態搖擺不定,或數據中心被龍捲風摧毀時,客戶應該能夠查看和添加物品到自己的購物車。因此,負責管理購物車的服務,它可以隨時寫入和讀取數據,並且數據需要跨越多個數據中心。

在一個由數百萬個組件組成的基礎設施中進行故障處理是我們的標準運作模式;在任何給定的時間段,總有一些小的但相當數量的服務器和網絡組件故障,因此Amazon的軟件系統需要將錯誤處理當作正常情況下來建造,而不影響可用性或性能。

爲了滿足可靠性和可伸縮性的需要,Amazon開發了許多存儲技術,其中Amazon簡單存儲服務(一個Amazon之外也可獲得的服務,即廣爲人知的AmazonS3–SimpleStorageService)大概是最爲人熟知。本文介紹了Dynamo的設計和實現,另一個爲Amazon的平臺上構建的高度可用和可擴展的分佈式數據存儲系統。Dynamo被用來管理服務狀態並且要求非常高的可靠性,而且需要嚴格控制可用性,一致性,成本效益和性能的之間的權衡。Amazon平臺的不同應用對存儲要求非常差異非常高。一部分應用需要存儲技術具有足夠的靈活性,讓應用程序設計人員配置適當的數據存儲來達到一種平衡,以實現高可用性和最具成本效益的方式保證性能。

Amazon服務平臺中的許多服務只需要主鍵訪問數據存儲。對於許多服務,如提供最暢銷書排行榜,購物車,客戶的偏好,會話管理,銷售等級,產品目錄,常見的使用關係數據庫的模式會導致效率低下,有限的可擴展性和可用性。Dynamo提供了一個簡單的主鍵唯一的接口,以滿足這些應用的要求。

Dynamo綜合了一些著名的技術來實現可伸縮性和可用性:數據劃分(data partitioned)和使用一致性哈希的複製(replicated)[10],並通過對象版本(object versioning)提供一致性[12]。在更新時,副本之間的一致性是由仲裁般(quorum-like)的技術和去中心化的副本同步協議來維持的。Dynamo採用了基於gossip(不知道怎樣譯過來好,流言蜚語?或許保留成外來語爲好)的分佈式故障檢測及成員(membership)協議(也即token環上的節點在響應節點加入(join)/離開(leaving)/移除(removing)/消亡(dead)等所採取的動作以維持DHT/Partitioning的正確語義)。Dynamo是一個只需要很少的人工管理,去中心化的系統。存儲節點可以添加和刪除,而不需要任何手動劃分(partitioning - partitioner controls how the data are distributed over the nodes)或重新分配(redistribution)。

在過去的一年,Dynamo已經成爲Amazon電子商務平臺的核心服務的底層存儲技術。它能夠有效地擴展到極端高峯負載,在繁忙的假日購物季節沒有任何的停機時間。例如,維護購物車(購物車服務)的服務,在一天內承擔數千萬的請求,並因此導致超過300萬checkouts(結算?),以及管理十萬計的併發活動會話的狀態。

這項工作對研究社區的主要貢獻是評估不同的技術如何能夠結合在一起,並最終提供一個單一的高可用性的系統。它表明一個最終一致(eventually-consistent)的存儲系統可以在生產環境中被苛刻的應用程序所採用。它也對這些技術的調整的進行深入的分析,以滿足性能要求非常嚴格的生產系統的需求。

本文的結構如下:第2節背景知識,第3節闡述有關工作,第4節介紹了系統設計,第5描述了實現,第6條詳述經驗和在生產運營Dynamo時取得的經理,第7節結束本文。在這其中,有些地方也許可以適當有更多的信息,但出於適當保護Amazon的商業利益,要求我們在文中降低詳細程度。由於這個原因,第6節數據中心內和跨數據中心之間的延時,第6.2節的相對請求速率,以及第6.3節系統中斷(outage)的時長,系統負載,都只是總體測量而不是絕對的詳細。

2背景

Amazon電子商務平臺由數百個服務組成,它們協同工作,提供的功能包括建議,完成訂單到欺詐檢測。每個服務是通過一個明確的公開接口,通過網絡訪問。這些服務運行在位於世界各地的許多數據中心的數萬臺服務器組成的基礎設施之上。其中一些服務是無狀態(比如,一些服務只是收集(aggregate)其他服務的response),有些是有狀態的(比如,通過使用持久性存儲區中的狀態,,執行業務邏輯,進而生成response)。

傳統的生產系統將狀態存儲在關係數據庫中。對於許多更通用的狀態存儲模式,關係數據庫方案是遠不夠理想。因爲這些服務大多隻通過數據的主鍵存儲和檢索數據,並且不需要RDBMS提供的複雜的查詢和管理功能。這多餘的功能,其運作需要昂貴的硬件和高技能人才,使其成爲一個非常低效的解決方案。此外,現有的複製(replication)技術是有限的,通常選擇一致性是以犧牲可用性爲代價(typically choose consistency over availability)。雖然最近幾年已經提出了許多進展,但數據庫水平擴展(scaleout)或使用負載平衡智能劃分方案仍然不那麼容易。

本文介紹了Dynamo,一個高度可用的數據存儲技術,能夠滿足這些重要類型的服務的需求。Dynamo有一個簡單的鍵/值接口,它是高可用的並同時具有清晰定義的一致性滑動窗口,它在資源利用方面是高效的,並且在解決規模增長或請求率上升時具有一個簡單的水平擴展(scaleout)方案。每個使用Dynamo的服務運行它自己的Dynamo實例。

2.1系統假設和要求

這種類型的服務的存儲系統具有以下要求:

查詢模型:對數據項簡單的讀,寫是通過一個主鍵唯一性標識。狀態存儲爲一個由唯一性鍵確定二進制對象(即blob,呵呵,java就是ByteBuffer啦)。沒有橫跨多個數據項的操作,也不需要關係方案(relational schema)。這項規定是基於觀察,相當一部分Amazon的服務可以使用這個簡單的查詢模型,並不需要任何關係模式。Dynamo的目標應用程序需要存儲的對象都比較小(通常小於1MB)。

ACID屬性:ACID(原子性,一致性,隔離性,持久性)是一種保證數據庫事務可靠地處理的屬性。在數據庫方面的,對數據的單一的邏輯操作被稱作所謂的交易。Amazon的經驗表明,在保證ACID的數據存儲提往往有很差的可用性。這已被業界和學術界所公認[5]。Dynamo的目標應用程序是高可用性,弱一致性(ACID中的“C”)。Dynamo不提供任何數據隔離(Isolation)保證,只允許單一的關鍵更新。

效率:系統需運作在“日用品”(commodity,非常喜歡這個詞,因爲可以在家做試驗!)級的硬件基礎設施上。Amazon平臺的服務都有着嚴格的延時要求,一般延時所需要度量到分佈的99.9百分位。在服務操作中鑑於對狀態的訪問起着至關重要的作用,存儲系統必須能夠滿足那些嚴格的SLA(見以下2.2),服務必須能夠通過配置Dynamo,使他們不斷達到延時和吞吐量的要求。因此,必須在成本效率,可用性和耐用性保證之間做權衡。

其他假設:Dynamo僅被Amazon內部的服務使用。它的操作環境被假定爲不懷惡意的(non-hostile),沒有任何安全相關的身份驗證和授權的要求。此外,由於每個服務使用其特定的Dynamo實例,它的最初設計目標的規模高達上百的存儲主機。我們將在後面的章節討論Dynamo可擴展性的限制和相關可能的擴展性的延伸。

2.2服務水平協議(SLA)

爲了保證應用程序可以在限定的(bounded)時間內遞送(deliver)其功能,一個平臺內的任何一個依賴都在一個更加限定的時間內遞送其功能。客戶端和服務端採用服務水平協議(SLA),其爲客戶端和服務端在幾個系統相關的特徵上達成一致的一個正式協商合約,其中,最突出的包括客戶對特定的API的請求速率分佈的預期要求,以及根據這些條件,服務的預期延時。一個簡單的例子是一個服務的SLA保證:在客戶端每秒500個請求負載高峯時,99.9%的響應時間爲300毫秒。

在Amazon的去中心化的面向服務的基礎設施中,服務水平協議發揮了重要作用。例如,一個頁面請求某個電子商務網站,通常需要頁面渲染(rendering)引擎通過發送請求到150多個服務來構造其響應。這些服務通常有多個依賴關係,這往往是其他服務,因此,有一層以上調用路徑的應用程序通常並不少見。爲了確保該網頁渲染引擎在遞送頁面時可以保持明確的時限,調用鏈內的每個服務必須履行合約中的性能指標。

這裏寫圖片描述
圖1:面向服務的Amazon平臺架構。

圖1顯示了Amazon平臺的抽象架構,動態網頁的內容是由頁面呈現組件生成,該組件進而查詢許多其他服務。一個服務可以使用不同的數據存儲來管理其狀態,這些數據存儲僅在其服務範圍才能訪問。有些服務作爲聚合器使用其他一些服務,可產生合成(composite)響應。通常情況下,聚合服務是無狀態,雖然他們利用廣泛的緩存。

行業中,表示面向性能的SLA的共同做法是使用平均數(average),中值(median)和預期變化(expected variance)。在Amazon,我們發現,這些指標不夠好,如果目標是建立一個對所有,而不是大多數客戶都有着良好體驗的系統。例如,如果個性化(personalization)技術被廣泛使用,那麼有很長的歷史的客戶需要更多的處理,性能影響將表現在分佈的高端。前面所述的基於平均或中值響應時間的SLA不能解決這一重要客戶段的性能問題。爲了解決這個問題,在Amazon,SLA是基於分佈的99.9百分位來表達和測量的。選擇百分位99.9的而不是更高是根據成本效益分析,其顯示出在99.9之後,要繼續提高性能,成本將大幅增加。系統的經驗與Amazon的生產表明,相比於那些基於平均或中值定義的SLA的系統,該方法提供了更好的整體體驗。

本文多次提到這種99.9百分位分佈,這反映了Amazon工程師從客戶體驗角度對性能不懈追求。許多論文統計平均數,所以在本論文的一些地方包括它可以用來作比較。然而,Amazon的工程和優化沒有側重於平均數。幾種技術,如作爲寫協調器(coordinators)的負載均衡的選擇,純粹是針對控制性能在99.9百分位的。

存儲系統在建立一個服務的SLA中通常扮演重要角色,特別是如果業務邏輯是比較輕量級時,正如許多Amazon的服務的情況。狀態管理就成爲一個服務的SLA的主要組成部分。對dynamo的主要設計考慮的問題之一就是給各個服務控制權,通過系統屬性來控制其耐用性和一致性,並讓服務自己在功能,性能和成本效益之間進行權衡。

2.3設計考慮

在商業系統中,數據複製(Data replication)算法傳統上執行同步的副本(replica)協調,以提供一個強一致性的數據訪問接口。爲了達到這個水平的一致性,在某些故障情況下,這些算法被迫犧牲了數據可用性。例如,與其不能確定答案的正確性與否,不如讓該數據一直不可用直到它絕對正確時。從最早期的備份(replicated)數據庫,衆所周知,當網絡故障時,強一致性和高可用性不可能性同時實現[2,11]。因此,系統和應用程序需要知道在何種情況下可以達到哪些屬性。

對於容易出現的服務器和網絡故障的系統,可使用樂觀複製技術來提高系統的可用性,其變化可以在後臺傳播到副本,同時,併發和斷開(disconnected)是可以容忍的。這種方法的挑戰在於,它會導致更改衝突,而這些衝突必須檢測並協調解決。這種協調衝突的過程引入了兩個問題:何時協調它們,誰協調它們。Dynamo被設計成最終一致性(eventually consistent)的數據存儲,即所有的更新操作,最終達到所有副本。

一個重要的設計考慮的因素是決定何時去協調更新操作衝突,即是否應該在讀或寫過程中協調衝突。許多傳統數據存儲在寫的過程中執行協調衝突過程,從而保持讀的複雜度相對簡單[7]。在這種系統中,如果在給定的時間內數據存儲不能達到所要求的所有或大多數副本數,寫入可能會被拒絕。另一方面,Dynamo的目標是一個“永遠可寫”(always writable)的數據存儲(即數據存儲的“寫”是高可用)。對於Amazon許多服務來講,拒絕客戶的更新操作可能導致糟糕的客戶體驗。例如,即使服務器或網絡故障,購物車服務必須讓客戶仍然可向他們的購物車中添加和刪除項。這項規定迫使我們將協調衝突的複雜性推給“讀”,以確保“寫”永遠不會拒絕。

下一設計選擇是誰執行協調衝突的過程。這可以通過數據存儲或客戶應用程序。如果衝突的協調是通過數據存儲,它的選擇是相當有限的。在這種情況下,數據存儲只可能使用簡單的策略,如”最後一次寫入獲勝“(last write wins)[22],以協調衝突的更新操作。另一方面,客戶應用程序,因爲應用知道數據方案,因此它可以基於最適合的客戶體驗來決定協調衝突的方法。例如,維護客戶的購物車的應用程序,可以選擇“合併”衝突的版本,並返回一個統一的購物車。儘管具有這種靈活性,某些應用程序開發人員可能不希望寫自己的協調衝突的機制,並選擇下壓到數據存儲,從而選擇簡單的策略,例如“最後一次寫入獲勝”。

設計中包含的其他重要的設計原則是:

  1. 增量的可擴展性:Dynamo應能夠一次水平擴展一臺存儲主機(以下,簡稱爲“節點“),而對系統操作者和系統本身的影響很小。

  2. 對稱性:每個Dynamo節點應該與它的對等節點(peers)有一樣的責任;不應該存在有區別的節點或採取特殊的角色或額外的責任的節。根據我們的經驗,對稱性(symmetry)簡化了系統的配置和維護。

  3. 去中心化:是對對稱性的延伸,設計應採用有利於去中心化而不是集中控制的技術。在過去,集中控制的設計造成系統中斷(outages),而本目標是儘可能避免它。這最終造就一個更簡單,更具擴展性,更可用的系統。

  4. 異質性:系統必須能夠利用異質性的基礎設施運行。例如,負載的分配必須與各個獨立的服務器的能力成比例。這樣就可以一次只增加一個高處理能力的節點,而無需一次升級所有的主機。

3 相關工作

3.1點對點系統

已經有幾個點對點(P2P)系統研究過數據存儲和分配的問題。如第一代P2P系統Freenet和Gnutella,被主要用作文件共享系統。這些都是由鏈路任意建立的非結構化P2P的網絡的例子。在這些網絡中,通常是充斥着通過網絡的搜索查詢以找到儘可能多地共享數據的對等節點。P2P系統演進到下一代是廣泛被稱爲結構化P2P。這些網絡採用了全局一致的協議,以確保任何節點都可以有效率地傳送一個搜索查詢到那些需要數據的節點。系統,如Pastry[16]和Chord[20]使用路由機制,以確保查詢可以在有限的跳數(hops)內得到回答。爲了減少多跳路由引入的額外延時,有些P2P系統(例如,[14])採用O(1)路由,每個節點保持足夠的路由信息,以便它可以在常數跳數下從本地路由請求(到要訪問的數據項)到適當的節點。

其他的存儲系統,如Oceanstore[9]和PAST[17]是建立在這些交錯的路由基礎之上的。Oceanstore提供了一個全局性的,事務性,持久性存儲服務,支持對廣闊的(widely)複製的數據進行序列化更新。爲允許併發更新的同時避免與廣域鎖定(wide-area locking)固有的許多問題,它使用了一個協調衝突的更新模型。[21]介紹了在協調衝突,以減少交易中止數量。Oceanstore協調衝突的方式是:通過處理一系列的更新,爲他們選擇一個最終的順序,然後利用這個順序原子地進行更新。它是爲數據被複制到不受信任的環境的基礎設施之上而建立。相比之下,PAST提供了一個基於Pastry和不可改變的對象的簡單的抽象層。它假定應用程序可以在它之上建立必要的存儲的語義(如可變文件)。

3.2分佈式文件系統和數據庫

出於對性能,可用性和耐用性考慮,數據分發已被文件系統和數據庫系統的社區廣泛研究。對比於P2P存儲系統的只支持平展的命名空間,分佈式文件系統通常支持分層的命名空間。系統象Ficus[15]和Coda[19]其文件複製是以犧牲一致性爲代價而達到高可用性。更新衝突管理通常使用專門的協調衝突程序。Farsite系統[1]是一個分佈式文件系統,不使用任何類似NFS的中心服務器。Farsite使用複製來實現高可用性和可擴展性。谷歌文件系統[6]是另一個分佈式文件系統用來承載谷歌的內部應用程序的狀態。GFS使用簡單的設計並採用單一的中心(master)服務器管理整個元數據,並且將數據被分成塊存儲在chunkservers上。Bayou是一個分佈式關係數據庫系統允許斷開(disconnected)操作,並提供最終的數據一致性[21]。

在這些系統中,Bayou,Coda和Ficus允許斷開操作和有從如網絡分裂和中斷的問題中復原的彈性。這些系統的不同之處在於協調衝突程序。例如,Coda和Ficus執行系統級協調衝突方案,Bayou允許應用程序級的解決方案。不過,所有這些都保證最終一致性。類似這些系統,Dynamo允許甚至在網絡被分裂(partition - network partition, which is a break in the network that prevents one machine in one data center from interacting directly with another machine in other data center)的情況下繼續進行讀,寫操作,以及使用不同的機制來協調有衝突的更新操作。分佈式塊存儲系統,像FAB[18]將大對象分成較小的塊,並以高度可用的方式存儲。相對於這些系統中,一個key-value存儲在這種情況下更合適,因爲:(a)它就是爲了存放相對小的物體(大小<1M)和 (b)key-value存儲是以每個應用更容易配置爲基礎的。Antiquity是一個廣域分佈式存儲系統,專爲處理多種服務器故障[23]。它使用一個安全的日誌來保持數據的完整性,複製日誌到多個服務器以達到耐久性,並使用Byzantine容錯協議來保證數據的一致性。相對於antiquity,Dynamo不太注重數據完整性和安全問題,併爲一個可信賴的環境而建立的。BigTable是一個管理結構化數據的分佈式存儲系統。它保留着稀疏的,多維的有序映射,並允許應用程序使用多個屬性訪問他們的數據[2]。相對於Bigtable中,Dynamo的目標應用程序只需要key/value並主要關注高可用性,甚至在網絡分裂或服務器故障時,更新操作都不會被拒絕。

傳統備份(replicated)關係數據庫系統強調保證複製數據的一致性。雖然強一致性給應用編寫者提供了一個更方便的應用程序編程模型,但這些系統都只有有限的可伸縮性和可用性[7]。這些系統不能處理網絡分裂(partition),因爲它們通常提供強的一致性保證。

3.3討論

與上述去中心化的存儲系統相比,Dynamo有着不同的目標需求:首先,Dynamo主要是針對應用程序需要一個“永遠可寫”數據存儲,不會由於故障或併發寫入而導致更新操作被拒絕。這是許多Amazon應用的關鍵要求。其次,如前所述,Dynamo是建立在一個所有節點被認爲是值得信賴的單個管理域的基礎設施之上。第三,使用Dynamo的應用程序不需要支持分層命名空間(許多文件系統採用的規範)或複雜的(由傳統的數據庫支持)關係模式的支持。第四,Dynamo是爲延時敏感應用程序設計的,需要至少99.9%的讀取和寫入操作必須在幾百毫秒內完成。爲了滿足這些嚴格的延時要求,這促使我們必須避免通過多個節點路由請求(這是被多個分佈式哈希系統如Chord和Pastry採用典型的設計)。這是因爲多跳路由將增加響應時間的可變性,從而導致百分較高的延時的增加。Dynamo可以被定性爲零跳(zero-hop)的DHT,每個節點維護足夠的路由信息從而直接從本地將請求路由到相應的節點。

4系統架構

一個操作在生產環境裏的存儲系統的架構是複雜的。除了實際的數據持久化組件,系統需要有負載平衡,成員(membership)和故障檢測,故障恢復,副本同步,過載處理,狀態轉移,併發性和工作調度,請求marshaling,請求路由,系統監控和報警,以及配置管理等可擴展的且強大的解決方案。描述解決方案的每一個細節是不可能的,因此本文的重點是核心技術在分佈式系統中使用Dynamo:劃分(partitioning),複製(replication),版本(versioning),會員(membership),故障處理(failure handling)和伸縮性(scaling)。表1給出了簡要的Dynamo使用的技術清單和各自的優勢。

表1:Dynamo使用的技術概要和其優勢。
這裏寫圖片描述

4.1系統接口

Dynamo通過一個簡單的接口將對象與key關聯,它暴露了兩個操作:get()和put()。get(key)操作在存儲系統中定位與key關聯的對象副本,並返回一個對象或一個包含衝突的版本和對應的上下文對象列表。put(key,context,object)操作基於關聯的key決定將對象的副本放在哪,並將副本寫入到磁盤。該context包含對象的系統元數據並對於調用者是不透明的(opaque),並且包括如對象的版本信息。上下文信息是與對象一起存儲,以便系統可以驗證請求中提供的上下文的有效性。
Dynamo將調用者提供的key和對象當成一個不透明的字節數組。它使用MD5對key進行Hash以產生一個128位的標識符,它是用來確定負責(responsible for)那個key的存儲節點。

4.2劃分算法

Dynamo的關鍵設計要求之一是必須增量可擴展性。這就需要一個機制來將數據動態劃分到系統中的節點(即存儲主機)上去。Dynamo的分區方案依賴於一致哈希將負載分發到多個存儲主機。在一致的哈希中[10],一個哈希函數的輸出範圍被視爲一個固定的圓形空間或“環”(即最大的哈希值繞到(wrap)最小的哈希值)。系統中的每個節點被分配了這個空間中的一個隨機值,它代表着它的在環上的“位置”。每個由key標識的數據項通過計算數據項的key的hash值來產生其在環上的位置。然後沿順時針方向找到第一個其位置比計算的數據項的位置大的節點。因此,每個節點變成了環上的一個負責它自己與它的前身節點間的區域(region)。一致性哈希的主要優點是節點的進進出出(departure or arrival)隻影響其最直接的鄰居,而對其他節點沒影響。

這對基本的一致性哈希算法提出了一些挑戰。首先,每個環上的任意位置的節點分配導致非均勻的數據和負荷分佈。二,基本算法無視於節點的性能的異質性。爲了解決這些問題,Dynamo採用了一致的哈希(類似於[10,20]中使用的)的變體:每個節點被分配到環多點而不是映射到環上的一個單點。爲此,Dynamo使用了“虛擬節點”的概念。系統中一個虛擬節點看起來像單個節點,但每個節點可對多個虛擬節點負責。實際上,當一個新的節點添加到系統中,它被分配環上的多個位置(以下簡稱“標記” Token )。對Dynamo的劃分方案進一步細化在第6部分討論。
使用虛擬節點具有以下優點:

如果一個節點不可用(由於故障或日常維護),這個節點處理的負載將均勻地分散在剩餘的可用節點。當一個節點再次可用,或一個新的節點添加到系統中,新的可用節點接受來自其他可用的每個節點的負載量大致相當。一個節點負責的虛擬節點的數目可以根據其處理能力來決定,顧及到物理基礎設施的異質性。

4.3複製

爲了實現高可用性和耐用性,Dynamo將數據複製到多臺主機上。每個數據項被複制到N臺主機,其中N是“每實例”(“per-instance)的配置參數。每個鍵,K,被分配到一個協調器(coordinator)節點(在上一節所述)。協調器節點掌控其負責範圍內的複製數據項。除了在本地存儲其範圍內的每個key外,協調器節點複製這些key到環上順時針方向的N-1後繼節點。這樣的結果是,系統中每個節點負責環上的從其自己到第N個前繼節點間的一段區域。在圖2中,節點B除了在本地存儲鍵K外,在節點C和D處複製鍵K。節點D將存儲落在範圍(A,B],(B,C]和(C,D]上的所有鍵。

這裏寫圖片描述
圖2:Dynamo的劃分和鍵的複製。

一個負責存儲一個特定的鍵的節點列表被稱爲首選列表(preference list)。該系統的設計,如將4.8節中解釋,讓系統中每一個節點可以決定對於任意key哪些節點應該在這個清單中。出於對節點故障的考慮,首選清單可以包含起過N個節點。請注意,因使用虛擬節點,對於一個特定的key的第一個N個後繼位置可能屬於少於N個物理所節點(即節點可以持有多個第一個N個位置)。爲了解決這個問題,一個key首選列表的構建將跳過環上的一些位置,以確保該列表只包含不同的物理節點。

4.4版本的數據

Dynamo提供最終一致性,從而允許更新操作可以異步地傳播到所有副本。put()調用可能在更新操作被所有的副本執行之前就返回給調用者,這可能會導致一個場景:在隨後的get()操作可能會返回一個不是最新的對象。如果沒有失敗,那麼更新操作的傳播時間將有一個上限。但是,在某些故障情況下(如服務器故障或網絡partitions),更新操作可能在一個較長時間內無法到達所有的副本。

在Amazon的平臺,有一種類型的應用可以容忍這種不一致,並且可以建造並操作在這種條件下。例如,購物車應用程序要求一個“添加到購物車“動作從來沒有被忘記或拒絕。如果購物車的最近的狀態是不可用,並且用戶對一個較舊版本的購物車做了更改,這種變化仍然是有意義的並且應該保留。但同時它不應取代當前不可用的狀態,而這不可用的狀態本身可能含有的變化也需要保留。請注意在Dynamo中“添加到購物車“和”從購物車刪除項目“這兩個操作被轉成put請求。當客戶希望增加一個項目到購物車(或從購物車刪除)但最新的版本不可用時,該項目將被添加到舊版本(或從舊版本中刪除)並且不同版本將在後來協調(reconciled)。

爲了提供這種保證,Dynamo將每次數據修改的結果當作一個新的且不可改變的數據版本。它允許系統中同一時間出現多個版本的對象。大多數情況,新版本包括(subsume)老的版本,且系統自己可以決定權威版本(語法協調 syntactic reconciliation)。然而,版本分支可能發生在併發的更新操作與失敗的同時出現的情況,由此產生衝突版本的對象。在這種情況下,系統無法協調同一對象的多個版本,那麼客戶端必須執行協調,將多個分支演化後的數據崩塌(collapse)成一個合併的版本(語義協調)。一個典型的崩塌的例子是“合併”客戶的不同版本的購物車。使用這種協調機制,一個“添加到購物車”操作是永遠不會丟失。但是,已刪除的條目可能會”重新浮出水面”(resurface)。

重要的是要了解某些故障模式有可能導致系統中相同的數據不止兩個,而是好幾個版本。在網絡分裂和節點故障的情況下,可能會導致一個對象有不同的分歷史,系統將需要在未來協調對象。這就要求我們在設計應用程序,明確意識到相同數據的多個版本的可能性(以便從來不會失去任何更新操作)。

Dynamo使用矢量時鐘[12]來捕捉同一不同版本的對象的因果關係。矢量時鐘實際上是一個(node,counter)對列表(即(節點,計數器)列表)。矢量時鐘是與每個對象的每個版本相關聯。通過審查其向量時鐘,我們可以判斷一個對象的兩個版本是平行分枝或有因果順序。如果第一個時鐘對象上的計數器在第二個時鐘對象上小於或等於其他所有節點的計數器,那麼第一個是第二個的祖先,可以被人忽略。否則,這兩個變化被認爲是衝突,並要求協調。

在dynamo中,當客戶端更新一個對象,它必須指定它正要更新哪個版本。這是通過傳遞它從早期的讀操作中獲得的上下文對象來指定的,它包含了向量時鐘信息。當處理一個讀請求,如果Dynamo訪問到多個不能語法協調(syntactically reconciled)的分支,它將返回分支葉子處的所有對象,其包含與上下文相應的版本信息。使用這種上下文的更新操作被認爲已經協調了更新操作的不同版本並且分支都被倒塌到一個新的版本。

這裏寫圖片描述
圖3:對象的版本隨時間演變。

爲了說明使用矢量時鐘,讓我們考慮圖3所示的例子。

  1. 客戶端寫入一個新的對象。節點(比如說Sx),它處理對這個key的寫:序列號遞增,並用它來創建數據的向量時鐘。該系統現在有對象D1和其相關的時鐘[(Sx,1)]。
  2. 客戶端更新該對象。假定也由同樣的節點處理這個要求。現在該系統有對象D2和其相關的時鐘[(Sx,2)]。D2繼承自D1,因此覆寫D1,但是節點中或許存在還沒有看到D2的D1的副本。
  3. 讓我們假設,同樣的客戶端更新這個對象但不同的服務器(比如Sy)處理了該請求。目前該系統具有數據D3及其相關的時鐘[(Sx,2),(Sy,1)]。
  4. 接下來假設不同的客戶端讀取D2,然後嘗試更新它,並且另一個服務器節點(如Sz)進行寫操作。該系統現在具有D4(D2的子孫),其版本時鐘[(Sx,2),(Sz,1)]。一個對D1或D2有所瞭解的節點可以決定,在收到D4和它的時鐘時,新的數據將覆蓋D1和D2,可以被垃圾收集。一個對D3有所瞭解的節點,在接收D4時將會發現,它們之間不存在因果關係。換句話說,D3和D4都有更新操作,但都未在對方的變化中反映出來。這兩個版本的數據都必須保持並提交給客戶端(在讀時)進行語義協調。
  5. 現在假定一些客戶端同時讀取到D3和D4(上下文將反映這兩個值是由read操作發現的)。讀的上下文包含有D3和D4時鐘的概要信息,即[(Sx,2),(Sy,1),(Sz,1)]的時鐘總結。如果客戶端執行協調,且由節點Sx來協調這個寫操作,Sx將更新其時鐘的序列號。D5的新數據將有以下時鐘:[(Sx,3),(Sy,1),(Sz,1)]。

關於向量時鐘一個可能的問題是,如果許多服務器協調對一個對象的寫,向量時鐘的大小可能會增長。實際上,這是不太可能的,因爲寫入通常是由首選列表中的前N個節點中的一個節點處理。在網絡分裂或多個服務器故障時,寫請求可能會被不是首選列表中的前N個節點中的一個處理的,因此會導致矢量時鐘的大小增長。在這種情況下,值得限制向量時鐘的大小。爲此,Dynamo採用了以下時鐘截斷方案:伴隨着每個(節點,計數器)對,Dynamo存儲一個時間戳表示最後一次更新的時間。當向量時鐘中(節點,計數器)對的數目達到一個閾值(如10),最早的一對將從時鐘中刪除。顯然,這個截斷方案會導至在協調時效率低下,因爲後代關係不能準確得到。不過,這個問題還沒有出現在生產環境,因此這個問題沒有得到徹底研究。

4.5執行get()和put()操作

Dynamo中的任何存儲節點都有資格接收客戶端的任何對key的get和put操作。在本節中,對簡單起見,我們將描述如何在一個從不失敗的(failure-free)環境中執行這些操作,並在隨後的章節中,我們描述了在故障的情況下讀取和寫入操作是如何執行。

GET和PUT操作都使用基於Amazon基礎設施的特定要求,通過HTTP的處理框架來調用。一個客戶端可以用有兩種策略之一來選擇一個節點:(1)通過一個普通的負載平衡器路由請求,它將根據負載信息選擇一個節點,或(2)使用一個分區(partition)敏感的客戶端庫直接路由請求到適當的協調程序節點。第一個方法的優點是,客戶端沒有鏈接(link)任何Dynamo特定的代碼在到其應用中,而第二個策略,Dynamo可以實現較低的延時,因爲它跳過一個潛在的轉發步驟。

處理讀或寫操作的節點被稱爲協調員。通常,這是首選列表中躋身前N個節點中的第一個。如果請求是通過負載平衡器收到,訪問key的請求可能被路由到環上任何隨機節點。在這種情況下,如果接收到請求節點不是請求的key的首選列表中前N個節點之一,它不會協調處理請求。相反,該節點將請求轉發到首選列表中第一個躋身前N個節點。

讀取和寫入操作涉及到首選清單中的前N個健康節點,跳過那些癱瘓的(down)或者不可達(inaccessible)的節點。當所有節點都健康,key的首選清單中的前N個節點都將被訪問。當有節點故障或網絡分裂,首選列表中排名較低的節點將被訪問。

爲了保持副本的一致性,Dynamo使用的一致性協議類似於仲裁(quorum)。該協議有兩個關鍵配置值:R和W. R是必須參與一個成功的讀取操作的最少數節點數目。W是必須參加一個成功的寫操作的最少節點數。設定R和W,使得R+W>N產生類似仲裁的系統。在此模型中,一個get(or out)操作延時是由最慢的R(或W)副本決定的。基於這個原因,R和W通常配置爲小於N,爲客戶提供更好的延時。

當收到對key的put()請求時,協調員生成新版本向量時鐘並在本地寫入新版本。協調員然後將新版本(與新的向量時鐘一起)發送給首選列表中的排名前N個的可達節點。如果至少W-1個節點返回了響應,那麼這個寫操作被認爲是成功的。

同樣,對於一個get()請求,協調員爲key從首選列表中排名前N個可達節點處請求所有現有版本的數據,然後等待R個響應,然後返回結果給客戶端。如果最終協調員收集的數據的多個版本,它返回所有它認爲沒有因果關係的版本。不同版本將被協調,並且取代當前的版本,最後寫回。

4.6故障處理:暗示移交(Hinted Handoff)

Dynamo如果使用傳統的仲裁(quorum)方式,在服務器故障和網絡分裂的情況下它將是不可用,即使在最簡單的失效條件下也將降低耐久性。爲了彌補這一點,它不嚴格執行仲裁,即使用了“馬虎仲裁”(“sloppy quorum”),所有的讀,寫操作是由首選列表上的前N個健康的節點執行的,它們可能不總是在散列環上遇到的那前N個節點。

考慮在圖2例子中Dynamo的配置,給定N=3。在這個例子中,如果寫操作過程中節點A暫時Down或無法連接,然後通常本來在A上的一個副本現在將發送到節點D。這樣做是爲了保持期待的可用性和耐用性。發送到D的副本在其原數據中將有一個暗示,表明哪個節點纔是在副本預期的接收者(在這種情況下A)。接收暗示副本的節點將數據保存在一個單獨的本地存儲中,他們被定期掃描。在檢測到了A已經復甦,D會嘗試發送副本到A。一旦傳送成功,D可將數據從本地存儲中刪除而不會降低系統中的副本總數。

使用暗示移交,Dynamo確保讀取和寫入操作不會因爲節點臨時或網絡故障而失敗。需要最高級別的可用性的應用程序可以設置W爲1,這確保了只要系統中有一個節點將key已經持久化到本地存儲 , 一個寫是可以接受(即一個寫操作完成即意味着成功)。因此,只有系統中的所有節點都無法使用時寫操作纔會被拒絕。然而,在實踐中,大多數Amazon生產服務設置了更高的W來滿足耐久性極別的要求。對N, R和W的更詳細的配置討論在後續的第6節。

一個高度可用的存儲系統具備處理整個數據中心故障的能力是非常重要的。數據中心由於斷電,冷卻裝置故障,網絡故障和自然災害發生故障。Dynamo可以配置成跨多個數據中心地對每個對象進行復制。從本質上講,一個key的首選列表的構造是基於跨多個數據中心的節點的。這些數據中心通過高速網絡連接。這種跨多個數據中心的複製方案使我們能夠處理整個數據中心故障。

4.7處理永久性故障:副本同步

Hinted Handoff在系統成員流動性(churn)低,節點短暫的失效的情況下工作良好。有些情況下,在hinted副本移交回原來的副本節點之前,暗示副本是不可用的。爲了處理這樣的以及其他威脅的耐久性問題,Dynamo實現了反熵(anti-entropy,或叫副本同步)協議來保持副本同步。

爲了更快地檢測副本之間的不一致性,並且減少傳輸的數據量,Dynamo採用MerkleTree[13]。MerkleTree是一個哈希樹(Hash Tree),其葉子是各個key的哈希值。樹中較高的父節點均爲其各自孩子節點的哈希。該merkleTree的主要優點是樹的每個分支可以獨立地檢查,而不需要下載整個樹或整個數據集。此外,MerkleTree有助於減少爲檢查副本間不一致而傳輸的數據的大小。例如,如果兩樹的根哈希值相等,且樹的葉節點值也相等,那麼節點不需要同步。如果不相等,它意味着,一些副本的值是不同的。在這種情況下,節點可以交換children的哈希值,處理直到它到達了樹的葉子,此時主機可以識別出“不同步”的key。MerkleTree減少爲同步而需要轉移的數據量,減少在反熵過程中磁盤執行讀取的次數。

Dynamo在反熵中這樣使用MerkleTree:每個節點爲它承載的每個key範圍(由一個虛擬節點覆蓋 key 集合)維護一個單獨的MerkleTree。這使得節點可以比較key range中的key是否是最新。在這個方案中,兩個節點交換MerkleTree的根,對應於它們承載的共同的鍵範圍。其後,使用上面所述樹遍歷方法,節點確定他們是否有任何差異和執行適當的同步行動。方案的缺點是,當節點加入或離開系統時有許多key rangee變化,從而需要重新對樹進行計算。通過由6.2節所述的更精煉partitioning方案,這個問題得到解決。

4.8會員和故障檢測

4.8.1環會員(Ring Membership)

Amazon環境中,節點中斷(由於故障和維護任務)常常是暫時的,但持續的時間間隔可能會延長。一個節點故障很少意味着一個節點永久離開,因此應該不會導致對已分配的分區重新平衡(rebalancing)和修復無法訪問的副本。同樣,人工錯誤可能導致意外啓動新的Dynamo節點。基於這些原因,應當適當使用一個明確的機制來發起節點的增加和從環中移除節點。管理員使用命令行工具或瀏覽器連接到一個節點,併發出成員改變(membership change)指令指示一個節點加入到一個環或從環中刪除一個節點。接收這一請求的節點寫入成員變化以及適時寫入持久性存儲。該成員的變化形成了歷史,因爲節點可以被刪除,重新添加多次。一個基於Gossip的協議傳播成員變動,並維持成員的最終一致性。每個節點每間隔一秒隨機選擇隨機的對等節點,兩個節點有效地協調他們持久化的成員變動歷史。

當一個節點第一次啓動時,它選擇它的Token(在虛擬空間的一致哈希節點) 並將節點映射到各自的Token集(Token set)。該映射被持久到磁盤上,最初只包含本地節點和Token集。在不同的節點中存儲的映射(節點到token set 的映射)將在協調成員的變化歷史的通信過程中一同被協調。因此,劃分和佈局信息也是基於Gossip協議傳播的,因此每個存儲節點都瞭解對等節點所處理的標記範圍。這使得每個節點可以直接轉發一個key的讀/寫操作到正確的數據集節點。

4.8.2外部發現

上述機制可能會暫時導致邏輯分裂的Dynamo環。例如,管理員可以將節點A加入到環,然後將節點B加入環。在這種情況下,節點A和B各自都將認爲自己是環的一員,但都不會立即瞭解到其他的節點(也就是A不知道B的存在,B也不知道A的存在,這叫邏輯分裂)。爲了防止邏輯分裂,有些Dynamo節點扮演種子節點的角色。種子的發現(discovered)是通過外部機制來實現的並且所有其他節點都知道(實現中可能直接在配置文件中指定seed node的IP,或者實現一個動態配置服務,seed register)。因爲所有的節點,最終都會和種子節點協調成員關係,邏輯分裂是極不可能的。種子可從靜態配置或配置服務獲得。通常情況下,種子在Dynamo環中是一個全功能節點。

4.8.3故障檢測

Dynamo中,故障檢測是用來避免在進行get()和put()操作時嘗試聯繫無法訪問節點,同樣還用於分區轉移(transferring partition)和暗示副本的移交。爲了避免在通信失敗的嘗試,一個純本地概念的失效檢測完全足夠了:如果節點B不對節點A的信息進行響應(即使B響應節點C的消息),節點A可能會認爲節點B失敗。在一個客戶端請求速率相對穩定併產生節點間通信的Dynamo環中,一個節點A可以快速發現另一個節點B不響應時,節點A則使用映射到B的分區的備用節點服務請求,並定期檢查節點B後來是否後來被複蘇。在沒有客戶端請求推動兩個節點之間流量的情況下,節點雙方並不真正需要知道對方是否可以訪問或可以響應。

去中心化的故障檢測協議使用一個簡單的Gossip式的協議,使系統中的每個節點可以瞭解其他節點到達(或離開)。有關去中心化的故障探測器和影響其準確性的參數的詳細信息,感興趣的讀者可以參考[8]。早期Dynamo的設計使用去中心化的故障檢測器以維持一個失敗狀態的全局性的視圖。後來認爲,顯式的節點加入和離開的方法排除了對一個失敗狀態的全局性視圖的需要。這是因爲節點是是可以通過節點的顯式加入和離開的方法知道節點永久性(permanent)增加和刪除,而短暫的(temporary)節點失效是由獨立的節點在他們不能與其他節點通信時發現的(當轉發請求時)。

4.9添加/刪除存儲節點

當一個新的節點(例如X)添加到系統中時,它被分配一些隨機散落在環上的Token。對於每一個分配給節點X的key range,當前負責處理落在其key range中的key的節點數可能有好幾個(小於或等於N)。由於key range的分配指向X,一些現有的節點不再需要存儲他們的一部分key,這些節點將這些key傳給X,讓我們考慮一個簡單的引導(bootstrapping)場景,節點X被添加到圖2所示的環中A和B之間,當X添加到系統,它負責的key範圍爲(F,G],(G,A]和(A,X]。因此,節點B,C和D都各自有一部分不再需要儲存key範圍(在X加入前,B負責(F,G], (G,A], (A,B]; C負責(G,A], (A,B], (B,C]; D負責(A,B], (B,C], (C,D]。而在X加入後,B負責(G,A], (A,X], (X,B]; C負責(A,X], (X,B], (B,C]; D負責(X,B], (B,C], (C,D])。因此,節點B,C和D,當收到從X來的確認信號時將供出(offer)適當的key。當一個節點從系統中刪除,key的重新分配情況按一個相反的過程進行。

實際經驗表明,這種方法可以將負載均勻地分佈到存儲節點,其重要的是滿足了延時要求,且可以確保快速引導。最後,在源和目標間增加一輪確認(confirmation round)以確保目標節點不會重複收到任何一個給定的key range轉移。

5實現

在dynamo中,每個存儲節點有三個主要的軟件組件:請求協調,成員(membership)和故障檢測,以及本地持久化引擎。所有這些組件都由Java實現。

Dynamo的本地持久化組件允許插入不同的存儲引擎,如:Berkeley數據庫(BDB版本)交易數據存儲,BDB Java版,MySQL,以及一個具有持久化後備存儲的內存緩衝。設計一個可插拔的持久化組件的主要理由是要按照應用程序的訪問模式選擇最適合的存儲引擎。例如,BDB可以處理的對象通常爲幾十千字節的數量級,而MySQL能夠處理更大尺寸的對象。應用根據其對象的大小分佈選擇相應的本地持久性引擎。生產中,Dynamo多數使用BDB事務處理數據存儲。

請求協調組成部分是建立在事件驅動通訊基礎上的,其中消息處理管道分爲多個階段類似SEDA的結構[24]。所有的通信都使用Java NIO Channels。協調員執行讀取和寫入:通過收集從一個或多個節點數據(在讀的情況下),或在一個或多個節點存儲的數據(寫入)。每個客戶的請求中都將導致在收到客戶端請求的節點上一個狀態機的創建。每一個狀態機包含以下邏輯:標識負責一個key的節點,發送請求,等待迴應,可能的重試處理,加工和包裝返回客戶端響應。每個狀態機實例只處理一個客戶端請求。例如,一個讀操作實現了以下狀態機:
(i)發送讀請求到相應節點,
(ii)等待所需的最低數量的響應,
(iii)如果在給定的時間內收到的響應太少,那麼請求失敗,
(iv)否則,收集所有數據的版本,並確定要返回的版本
(v)如果啓用了版本控制,執行語法協調,併產生一個對客戶端不透明寫上下文,其包括一個涵括所有剩餘的版本的矢量時鐘。爲了簡潔起見,沒有包含故障處理和重試邏輯。

在讀取響應返回給調用方後,狀態機等待一小段時間以接受任何懸而未決的響應。如果任何響應返回了過時了的(stale)的版本,協調員將用最新的版本更新這些節點(當然是在後臺了)。這個過程被稱爲讀修復(read repair),因爲它是用來修復一個在某個時間曾經錯過更新操作的副本,同時read repair可以消除不必的反熵操作。

如前所述,寫請求是由首選列表中某個排名前N的節點來協調的。雖然總是選擇前N節點中的第一個節點來協調是可以的,但在單一地點序列化所有的寫的做法會導致負荷分配不均,進而導致違反SLA。爲了解決這個問題,首選列表中的前N的任何節點都允許協調。特別是,由於寫通常跟隨在一個讀操作之後,寫操作的協調員將由節點上最快答覆之前那個讀操作的節點來擔任,這是因爲這些信息存儲在請求的上下文中(指的是write操作的請求)。這種優化使我們能夠選擇那個存有同樣被之前讀操作使用過的數據的節點,從而提高“讀你的寫”(read-your-writes)一致性(譯:我不認爲這個描述是有道理的,因爲作者這裏描述明明是write-follows-read,要了解read-your-writes一致性的讀者參見作者另一篇文章:eventually consistent)。它也減少了爲了將處理請求的性能提高到99.9百分位時性能表現的差異。

6經驗與教訓

Dynamo由幾個不同的配置的服務使用。這些實例有着不同的版本協調邏輯和讀/寫仲裁(quorum)的特性。以下是Dynamo的主要使用模式:

業務邏輯特定的協調:這是一個普遍使用的Dynamo案例。每個數據對象被複制到多個節點。在版本發生分岔時,客戶端應用程序執行自己的協調邏輯。前面討論的購物車服務是這一類的典型例子。其業務邏輯是通過合併不同版本的客戶的購物車來協調不同的對象。

基於時間戳的協調:此案例不同於前一個在於協調機制。在出現不同版本的情況下,Dynamo執行簡單的基於時間戳的協調邏輯:“最後的寫獲勝”,也就是說,具有最大時間戳的對象被選爲正確的版本。一些維護客戶的會話信息的服務是使用這種模式的很好的例子。

高性能讀取引擎:雖然Dynamo被構建成一個“永遠可寫”數據存儲,一些服務通過調整其仲裁的特性把它作爲一個高性能讀取引擎來使用。通常,這些服務有很高的讀取請求速率但只有少量的更新操作。在此配置中,通常R是設置爲1,且W爲N。對於這些服務,Dynamo提供了劃分和跨多個節點的複製能力,從而提供增量可擴展性(incremental scalability)。一些這樣的實例被當成權威數據緩存用來緩存重量級後臺存儲的數據。那些保持產品目錄及促銷項目的服務適合此種類別。
Dynamo的主要優點是它的客戶端應用程序可以調的N,R和W的值,以實現其期待的性能, 可用性和耐用性的水平。例如,N的值決定了每個對象的耐久性。Dynamo用戶使用的一個典型的N值是3。

W和R影響對象的可用性,耐用性和一致性。舉例來說,如果W設置爲1,只要系統中至少有一個節點活就可以成功地處理一個寫請求,那麼系統將永遠不會拒絕寫請求。不過,低的W和R值會增加不一致性的風險,因爲寫請求被視爲成功並返回到客戶端,即使他們還未被大多數副本處理。這也引入了一個耐用性漏洞(vulnerability)窗口:即使它只是在少數幾個節點上持久化了但寫入請求成功返回到客戶端。

傳統的觀點認爲,耐用性和可用性關係總是非常緊密(hand-in-hand手牽手^-^)。但是,這並不一定總是真的。例如,耐用性漏洞窗口可以通過增加W來減少,但這將增加請求被拒絕的機率(從而減少可用性),因爲爲處理一個寫請求需要更多的存儲主機需要活着。

被好幾個Dynamo實例採用的(n,R,W)配置通常爲(3,2,2)。選擇這些值是爲滿足性能,耐用性,一致性和可用性SLAs的需求。

所有在本節中測量的是一個在線系統,其工作在(3,2,2)配置並運行在幾百個同質硬件配置上。如前所述,每一個實例包含位於多個數據中心的Dynamo節點。這些數據中心通常是通過高速網絡連接。回想一下,產生一個成功的get(或put)響應,R(或W)個節點需要響應協調員。顯然,數據中心之間的網絡延時會影響響應時間,因此節點(及其數據中心位置)的選擇要使得應用的目標SLAs得到滿足。

6.1平衡性能和耐久性

雖然Dynamo的主要的設計目標是建立一個高度可用的數據存儲,性能是在Amazon平臺中是一個同樣重要的衡量標準。如前所述,爲客戶提供一致的客戶體驗,Amazon的服務定在較高的百分位(如99.9或99.99),一個典型的使用Dynamo的服務的SLA要求99.9%的讀取和寫入請求在300毫秒內完成。

由於Dynamo是運行在標準的日用級硬件組件上,這些組件的I/O吞吐量遠比不上高端企業級服務器,因此提供一致性的高性能的讀取和寫入操作並不是一個簡單的任務。再加上涉及到多個存儲節點的讀取和寫入操作,讓我們更加具有挑戰性,因爲這些操作的性能是由最慢的R或W副本限制的。圖4顯示了Dynamo爲期30天的讀/寫的平均和99.9百分位的延時。正如圖中可以看出,延時表現出明顯的晝夜模式這是因爲進來的請求速率存在晝夜模式的結果造成的(即請求速率在白天和黑夜有着顯着差異)。此外,寫延時明顯高於讀取延時,因爲寫操作總是導致磁盤訪問。此外,99.9百分位的延時大約是200毫秒,比平均水平高出一個數量級。這是因爲99.9百分位的延時受幾個因素,如請求負載,對象大小和位置格局的變化影響。

這裏寫圖片描述
圖4:讀,寫操作的平均和99.9百分點延時,2006年12月高峯時的請求。

在X軸的刻度之間的間隔相當於連續12小時。延時遵循晝夜模式類似請求速率99.9百分點比平均水平高出一個數量級。

雖然這種性能水平是可以被大多數服務所接受,一些面向客戶的服務需要更高的性能。針對這些服務,Dynamo能夠犧牲持久性來保證性能。在這個優化中,每個存儲節點維護一個內存中的對象緩衝區(BigTable 中的memtable)。每次寫操作都存儲在緩衝區,“寫”線程定期將緩衝寫到存儲中。在這個方案中,讀操作首先檢查請求的 key 是否存在於緩衝區。如果是這樣,對象是從緩衝區讀取,而不是存儲引擎。

這種優化的結果是99.9百位在流量高峯期間的延時降低達5倍之多,即使是一千個對象(參見圖5)的非常小的緩衝區。此外,如圖中所示,寫緩衝在較高百分位具有平滑延時。顯然,這個方案是平衡耐久性來提高性能的。在這個方案中,服務器崩潰可能會導致寫操作丟失,即那些在緩衝區隊列中的寫(還未持久化到存儲中的寫)。爲了減少耐用性風險,更細化的寫操作要求協調員選擇N副本中的一個執行“持久寫”。由於協調員只需等待W個響應(譯,這裏討論的這種情況包含W-1個緩衝區寫,1個持久化寫),寫操作的性能不會因爲單一一個副本的持久化寫而受到影響。

這裏寫圖片描述

6.2確保均勻的負載分佈

Dynamo採用一致性的散列將key space(鍵空間)分佈在其所有的副本上,並確保負載均勻分佈。假設對key的訪問分佈不會高度偏移,一個統一的key分配可以幫助我們達到均勻的負載分佈。特別地, Dynamo設計假定,即使訪問的分佈存在顯着偏移,只要在流行的那端(popular end)有足夠多的keys,那麼對那些流行的key的處理的負載就可以通過partitioning均勻地分散到各個節點。本節討論Dynamo中所出現負載不均衡和不同的劃分策略對負載分佈的影響。

爲了研究負載不平衡與請求負載的相關性,通過測量各個節點在24小時內收到的請求總數-細分爲30分鐘一段。在一個給定的時間窗口,如果該節點的請求負載偏離平均負載沒有超過某個閾值(這裏15%),認爲一個節點被認爲是“平衡的”。否則,節點被認爲是“失去平衡”。圖6給出了一部分在這段時間內“失去平衡”的節點(以下簡稱“失衡比例”)。作爲參考,整個系統在這段時間內收到的相應的請求負載也被繪製。正如圖所示,不平衡率隨着負載的增加而下降。例如,在低負荷時,不平衡率高達20%,在高負荷高接近10%。直觀地說,這可以解釋爲,在高負荷時大量流行鍵(popular key)訪問且由於key的均勻分佈,負載最終均勻分佈。然而,在(其中負載爲高峯負載的八分之一)低負載下,當更少的流行鍵被訪問,將導致一個比較高的負載不平衡。

這裏寫圖片描述

X軸刻度間隔相當於一個30分鐘的時間。
本節討論Dynamo的劃分方案(partitioning scheme)是如何隨着時間和負載分佈的影響進行演化的。

策略1:每個節點T個隨機Token和基於Token值進行分割:這是最早部署在生產環境的策略(在4.2節中描述)。在這個方案中,每個節點被分配T 個Tokens(從哈希空間隨機均勻地選擇)。所有節點的token,是按照其在哈希空間中的值進行排序的。每兩個連續的Token定義一個範圍。最後的Token與最開始的Token構成一區域(range):從哈希空間中最大值繞(wrap)到最低值。由於Token是隨機選擇,範圍大小是可變的。節點加入和離開系統導致Token集的改變,最終導致ranges的變化,請注意,每個節點所需的用來維護系統的成員的空間與系統中節點的數目成線性關係。

在使用這一策略時,遇到了以下問題。首先,當一個新的節點加入系統時,它需要“竊取”(steal)其他節點的鍵範圍。然而,這些需要移交key ranges給新節點的節點必須掃描他們的本地持久化存儲來得到適當的數據項。請注意,在生產節點上執行這樣的掃描操作是非常複雜,因爲掃描是資源高度密集的操作,他們需要在後臺執行,而不至於影響客戶的性能。這就要求我們必須將引導工作設置爲最低的優先級。然而,這將大大減緩了引導過程,在繁忙的購物季節,當節點每天處理數百萬的請求時,引導過程可能需要幾乎一天才能完成。第二,當一個節點加入/離開系統,由許多節點處理的key range的變化以及新的範圍的MertkleTree需要重新計算,在生產系統上,這不是一個簡單的操作。最後,由於key range的隨機性,沒有一個簡單的辦法爲整個key space做一個快照,這使得歸檔過程複雜化。在這個方案中,歸檔整個key space 需要分別檢索每個節點的key,這是非常低效的。

這個策略的根本問題是,數據劃分和數據安置的計劃交織在一起。例如,在某些情況下,最好是添加更多的節點到系統,以應對處理請求負載的增加。但是,在這種情況下,添加節點(導致數據安置)不可能不影響數據劃分。理想的情況下,最好使用獨立劃分和安置計劃。爲此,對以下策略進行了評估:

策略2:每個節點T個隨機token和同等大小的分區:在此策略中,節點的哈希空間分爲Q個同樣大小的分區/範圍,每個節點被分配T個隨機Token。Q是通常設置使得Q>>N和Q>>S*T,其中S爲系統的節點個數。在這一策略中,Token只是用來構造一個映射函數該函數將哈希空間的值映射到一個有序列的節點列表,而不決定分區。分區是放置在從分區的末尾開始沿着一致性hash環順時針移動遇到的前N個獨立的節點上。圖7說明了這一策略當N=3時的情況。在這個例子中,節點A,B,C是從分區的末尾開始沿着一致性hash環順時針移動遇到的包含key K1的節點。這一策略的主要優點是:(i)劃分和分區佈局脫耦 (ii)使得在運行時改變安置方案成爲可能。

這裏寫圖片描述
圖7:三個策略的分區和key的位置。

甲,乙,丙描述三個獨立的節點,形成keyk1在一致性哈希環上的首選列表(N=3)。陰影部分表示節點A,B和C形式的首選列表負責的keyrangee。黑色箭頭標明各節點的Token的位置。

策略3:每個節點Q/S個Token,大小相等的分區:類似策略2,這一策略空間劃分成同樣大小爲Q的散列分區,以及分區佈局(placement of partition)與劃分方法(partitioning scheme)脫鉤。此外,每個節點被分配Q/S個Token其中S是系統的節點數。當一個節點離開系統,爲使這些屬性被保留,它的Token隨機分發到其他節點。同樣,當一個節點加入系統,新節點將通過一種可以保留這種屬性的方式從系統的其他節點“偷”Token。

對這三個策略的效率評估使用S=30和N=3配置的系統。然而,以一個比較公平的方式這些不同的策略是很難的,因爲不同的策略有不同的配置來調整他們的效率。例如,策略1取決於負荷的適當分配(即T),而策略3信賴於分區的個數(即Q)。一個公平的比較方式是在所有策略中使用相同數量的空間來維持他們的成員信息時,通過評估負荷分佈的偏斜. 例如,策略1每個節點需要維護所有環內的Token位置,策略3每個節點需要維護分配到每個節點的分區信息。

在我們的下一個實驗,通過改變相關的參數(T 和 Q),對這些策略進行了評價。每個策略的負載均衡的效率是根據每個節點需要維持的成員信息的大小的不同來測量,負載平衡效率是指每個節點服務的平均請求數與最忙(hottest)的節點服務的最大請求數之比。

結果示於圖8。正如圖中看到,策略3達到最佳的負載平衡效率,而策略2最差負載均衡的效率。一個短暫的時期,在將Dynamo實例從策略1到策略3的遷移過程中,策略2曾作爲一個臨時配置。相對於策略1,策略3達到更好的效率並且在每個節點需要維持的信息的大小規模降低了三個數量級。雖然存儲不是一個主要問題,但節點間週期地Gossip成員信息,因此最好是儘可能保持這些信息緊湊。除了這個,策略3有利於且易於部署,理由如下:(i)更快的bootstrapping/恢復:由於分區範圍是固定的,它們可以被保存在單獨的文件,這意味着一個分區可以通過簡單地轉移文件並作爲一個單位重新安置(避免隨機訪問需要定位具體項目)。這簡化了引導和恢復過程。(ii)易於檔案:對數據集定期歸檔是Amazon存儲服務提出的強制性要求。Dynamo在策略3下歸檔整個數據集很簡單,因爲分區的文件可以被分別歸檔。相反,在策略1,Token是隨機選取的,歸檔存儲的數據需要分別檢索各個節點的key,這通常是低效和緩慢的。策略3的缺點是,爲維護分配所需的屬性改變節點成員時需要協調,。

這裏寫圖片描述

系統的規模和副本的數量的值是按照我們部署的大多數服務的典型配置。

6.3不同版本:何時以及有多少?

如前所述,Dynamo被設計成爲獲得可用性而犧牲了一致性。爲了解不同的一致性失敗導致的確切影響,多方面的詳細的數據是必需的:中斷時長,失效類型,組件可靠性,負載量等。詳細地呈現所有這些數字超出本文的範圍。不過,本節討論了一個很好的簡要的度量尺度:在現場生產環境中的應用所出現的不同版本的數量。

不同版本的數據項出現在兩種情況下。首先是當系統正面臨着如節點失效故障的情況下, 數據中心的故障和網絡分裂。二是當系統的併發處理大量寫單個數據項,並且最終多個節點同時協調更新操作。無論從易用性和效率的角度來看,都應首先確保在任何特定時間內不同版本的數量儘可能少。如果版本不能單獨通過矢量時鐘在語法上加以協調,他們必須被傳遞到業務邏輯層進行語義協調。語義協調給服務應用引入了額外的負擔,因此應儘量減少它的需要。

在我們的下一個實驗中,返回到購物車服務的版本數量是基於24小時爲週期來剖析的。在此期間,99.94%的請求恰好看到了1個版本。0.00057%的請求看到2個版本,0.00047%的請求看到3個版本和0.00009%的請求看到4個版本。這表明,不同版本創建的很少。

經驗表明,不同版本的數量的增加不是由於失敗而是由於併發寫操作的數量增加造成的。數量遞增的併發寫操作通常是由忙碌的機器人(busy robot-自動化的客戶端程序)導致而很少是人爲觸發。由於敏感性,這個問題還沒有詳細討論。

6.4客戶端驅動或服務器驅動協調

如第5條所述,Dynamo有一個請求協調組件,它使用一個狀態機來處理進來的請求。客戶端的請求均勻分配到環上的節點是由負載平衡器完成的。Dynamo的任何節點都可以充當一個讀請求協調員。另一方面,寫請求將由key的首選列表中的節點來協調。此限制是由於這一事實--這些首選節點具有附加的責任:即創建一個新的版本標識,使之與寫請求更新的版本建立因果關係(譯:嗚嗚,這個很難!Causally subsumes)。請注意,如果Dynamo的版本方案是建基於物理時間戳(譯:一個在本文中沒解釋的概念:[Timestamp Semantics and Representation]Many database management systems and operating systems provide support for time values. This support is present at both the logical and physical levels. The logical level is the user’s view of the time values and the query level operations permitted on those values, while the physical level concerns the bit layout of the time values and the bit level operations on those values. The physical level serves as a platform for the logical level but is inaccessible to the average user.)的話,任何節點都可以協調一個寫請求。

另一種請求協調的方法是將狀態機移到客戶端節點。在這個方案中,客戶端應用程序使用一個庫在本地執行請求協調。客戶端定期隨機選取一個節點,並下載其當前的Dynamo成員狀態視圖。利用這些信息,客戶端可以從首選列表中爲給定的key選定相應的節點集。讀請求可以在客戶端節點進行協調,從而避免了額外一跳的網絡開銷(network hop),比如,如果請求是由負載平衡器分配到一個隨機的Dynamo節點,這種情況會招致這樣的額外一跳。如果Dynamo使用基於時間戳的版本機制,寫要麼被轉發到在key的首選列表中的節點,也可以在本地協調。

一個客戶端驅動的協調方式的重要優勢是不再需要一個負載平衡器來均勻分佈客戶的負載。公平的負載分佈隱含地由近乎平均的分配key到存儲節點的方式來保證的。顯然,這個方案的有效性是信賴於客戶端的成員信息的新鮮度的。目前客戶每10秒隨機地輪循一Dynamo節點來更新成員信息。一個基於抽取(pull)而不是推送(push)的方被採用,因爲前一種方法在客戶端數量比較大的情況下擴展性好些,並且服務端只需要維護一小部分關於客戶端的狀態信息。然而,在最壞的情況下,客戶端可能持有長達10秒的陳舊的成員信息。如果客戶端檢測其成員列表是陳舊的(例如,當一些成員是無法訪問)情況下,它會立即刷新其成員信息。

表2顯示了24小時內觀察到的,對比於使用服務端協調方法,使用客戶端驅動的協調方法,在99.9百分位延時和平均延時的改善。如表所示,客戶端驅動的協調方法,99.9百分位減少至少30毫秒的延時,以及降低了3到4毫秒的平均延時。延時的改善是因爲客戶端驅動的方法消除了負載平衡器額外的開銷以及網絡一跳,這在請求被分配到一個隨機節點時將導致的開銷。如表所示,平均延時往往要明顯比99.9百分位延時低。這是因爲Dynamo的存儲引擎緩存和寫緩衝器具有良好的命中率。此外,由於負載平衡器和網絡引入額外的對響應時間的可變性,在響應時間方面,99.9th百分位這這種情況下(即使用負載平衡器)獲得好處比平均情況下要高。

表二:客戶驅動和服務器驅動的協調方法的性能。
這裏寫圖片描述

6.5權衡後臺和前臺任務

每個節點除了正常的前臺put/get操作,還將執行不同的後臺任務,如數據的副本的同步和數據移交(handoff)(由於暗示(hinting)或添加/刪除節點導致)。在早期的生產設置中,這些後臺任務觸發了資源爭用問題,影響了正常的put和get操作的性能。因此,有必要確保後臺任務只有在不會顯著影響正常的關鍵操作時運行。爲了達到這個目的,所有後臺任務都整合了管理控制機制。每個後臺任務都使用此控制器,以預留所有後臺任務共享的時間片資源(如數據庫)。採用一個基於對前臺任務進行監控的反饋機制來控制用於後臺任務的時間片數。

管理控制器在進行前臺put/get操作時不斷監測資源訪問的行爲,監測數據包括對磁盤操作延時,由於鎖爭用導致的失敗的數據庫訪問和交易超時,以及請求隊列等待時間。此信息是用於檢查在特定的後沿時間窗口延時(或失敗)的百分位是否接近所期望的閥值。例如,背景控制器檢查,看看數據庫的99百分位的讀延時(在最後60秒內)與預設的閾值(比如50毫秒)的接近程度。該控制器採用這種比較來評估前臺業務的資源可用性。隨後,它決定多少時間片可以提供給後臺任務,從而利用反饋環來限制背景活動的侵擾。請注意,一個與後臺任務管理類似的問題已經在[4]有所研究。

6.6討論

本節總結了在實現和維護Dynamo過程中獲得的一些經驗。很多Amazon的內部服務在過去二年中已經使用了Dynamo,它給應用提供了很高級別的可用性。特別是,應用程序的99.9995%的請求都收到成功的響應(無超時),到目前爲止,無數據丟失事件發生。

此外,Dynamo的主要優點是,它提供了使用三個參數的(N,R,W),根據自己的需要來調整它們的實例。不同於流行的商業數據存儲,Dynamo將數據一致性與協調的邏輯問題暴露給開發者。開始,人們可能會認爲應用程序邏輯會變得更加複雜。然而,從歷史上看,Amazon平臺都爲高可用性而構建,且許多應用內置了處理不同的失效模式和可能出現的不一致性。因此,移植這些應用程序到使用Dynamo是一個相對簡單的任務。對於那些希望使用Dynamo的應用,需要開發的初始階段做一些分析,以選擇正確的衝突的協調機制以適當地滿足業務情況。最後,Dynamo採用全成員(full membership)模式,其中每個節點都知道其對等節點承載的數據。要做到這一點,每個節點都需要積極地與系統中的其他節點Gossip完整的路由表。這種模式在一個包含數百個節點的系統中運作良好,然而,擴展這樣的設計以運行成千上萬節點並不容易,因爲維持路由表的開銷將隨着系統的大小的增加而增加。克服這種限制可能需要通過對 Dynamo引入分層擴展。此外,請注意這個問題正在積極由O(1)DHT的系統解決(例如,[14])。

7結論

本文介紹了Dynamo,一個高度可用和可擴展的數據存儲系統,被Amazon.com電子商務平臺用來存儲許多核心服務的狀態。Dynamo已經提供了所需的可用性和性能水平,並已成功處理服務器故障,數據中心故障和網絡分裂。Dynamo是增量擴展,並允許服務的擁有者根據請求負載按比例增加或減少。Dynamo讓服務的所有者通過調整參數N,R和W來達到他們渴求的性能,耐用性和一致性的SLA。

在過去的一年生產系統使用Dynamo表明,分散技術可以結合起來提供一個單一的高可用性系統。其成功應用在最具挑戰性的應用環境之一中表明,最終一致性的存儲系統可以是一個高度可用的應用程序的構建塊。

鳴謝

作者在此要感謝PatHelland,他貢獻了Dynamo的初步設計。我們還要感謝MarvinTheimer和RobertvanRenesse的評註。最後,我們要感謝我們的指路人(shepherd),JeffMogul,他的詳細的評註和input(不知道怎樣說了?詞窮)在準備camera ready版本時,大大提高了本文的質量。

發佈了118 篇原創文章 · 獲贊 16 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章