Dynamo: Amazon’s Highly Available Key-value Store

silentteller發表於2024-06-10

  《Dynamo: Amazon’s Highly Available Key-value Store》這篇發表於07年,雖然時間久遠但仍是一篇值得一讀的文章,當然現在 Amazon 的 DynamoDB跟這篇文章介紹的架構應該是完全不一樣了。感興趣的同學可以再去讀一下《Amazon DynamoDB: A Scalable, Predictably Performant, and Fully Managed NoSQL Database Service》

摘要

  本文介紹了Dynamo的設計和實現,Dynamo是一個高可用的鍵值儲存系統,Amazon的一些核心服務使用它來提供“永遠線上”的體驗。為了達到這種級別的可用性,Dynamo在某些特定的故障場景下犧牲了一致性。Dynamo 使用了物件版本化(object versioning)和應用協助的衝突解決(application-assisted conflict resolution)機制,給開發者提供了一種新穎的介面。

1.引言

  Amazon平臺在效能、可靠性和效率等方面有著很高的要求,同時為了支援持續增長,平臺需要具有高度的可擴充套件性。
系統的可靠性和可擴充套件性取決於如何管理它的應用狀態。Amazon使用由數百個服務組成的高度去中心化、松耦合、面向服務的架構。在這樣的環境中,特別需要永遠可用的儲存技術。
  任何時刻都會有比例小但數量不少(small but significant number)的伺服器和網路裝置出現故障。因此, 軟體系統要將故障視為正常的、可預測的行為(treat failure handling as the normal case),不影響可用性和效能。
  Dynamo 用於管理對可靠性要求非常高的服務的狀態,並且需要嚴格控制可用性、一致性、成本效益和效能之間的權衡。
  Amazon平臺上有許多服務只需要主鍵訪問資料儲存。例如暢銷書列表、購物車、客戶偏好、會話管理、銷售排名和產品目錄, 使用關係型資料庫會導致低效,而且限制了規模的擴充套件性和可用性。Dynamo提供了一個 簡單的主鍵介面來滿足這些應用程式的需求。
  Dynamo 基於一些熟知的技術實現了可擴充套件性和可用性:

  • 透過一致性雜湊對資料進行分割槽和複製(partitioned and replicated)
  • 透過物件版本化(object versioning)實現一致性
  • 副本之間的一致性由一種類似仲裁的技術(quorum-like technique)和一個去中心化的副本同步協議(replica synchroni protocol)保證
  • gossip-based 分散式故障檢測和成員檢測協議

  Dynamo 是一個完全去中心化的系統,很少需要人工管理。可以在Dynamo中新增和刪除儲存節點,而不需要任何手動分割槽或重新分配。

主要貢獻:

  • 評估如何將不同的技術結合起來提供一個單一的高可用性系統
  • 證明了最終一致的儲存系統可以用於要求苛刻的生產應用程式
  • 還提供了對這些技術進行調優以滿足具有非常嚴格的效能要求的生產系統的需求的見解。

2.背景

2.1 系統假設與需求

Query Model

  透過唯一的 key 對資料進行讀寫(key-value store)。狀態儲存為二進位制物件,以唯一的 key 索引。沒有跨多個資料項的操作,也不需要關係模式(大部分服務可以使用這個簡單的查詢模型)。目標是需要儲存相對較小的物件的應用程式 (通常小於1 MB)。

ACID

  ACID(Atomicity, Consistency, Isolation,Durability)是保證資料庫事務可靠執行的特性,在資料庫中,對資料的單次邏輯操作稱為一次事務。經驗表明,提供ACID保證的資料儲存的可用性往往是較差的。Dynamo 犧牲了 C (ACID中的“C”),用較弱的一致性換來了高可用性。Dynamo 不提供任何隔離性的保證,只允許單個鍵的更新。

Efficiency

  系統需要執行在通用硬體上。Amazon 的服務對延遲有著嚴格的要求,通常用百分位值P99.9衡量。同時儲存系統必須滿足那些嚴格的 SLA。另外,服務必須能夠配置Dynamo ,使它們始終如一地實現其延遲和吞吐量的要求,權衡效能、成本效率、可用性和永續性。

Other Assumptions

  Dynamo 只用在 Amazon 內部服務使用,假設其執行環境是非敵對的(nonhostile),不存在認證、授權等安全相關需求。因此可以不考慮安全性。此外,每個服務使用其不同的 Dynamo 例項,因此其初始設計目標是多達數百個儲存主機的規模。

2.2 服務等級協議(SLA)

  要保證一個應用完成請求所花的時間有一個上限,它所依賴的那些服 務就要有一個更低的上限。對於給定的系統特性,其中最主要的是客戶端期望的請求率分佈,客戶端和服務端會定義一個 SLA(服務等級協議)來達成一致。

SLA(Service-Level Agreement)服務等級協議,是指系統服務提供者(Provider)對客戶(Customer)的一個可量化的服務承諾,常見於大型分散式系統中,用於衡量系統服務是否穩定健康的常見方法。

  SLA 是一種服務承諾,因此指標具備多樣性,業界主流常用指標包含:可用性、準確性、系統容量和延遲。舉一個簡單的SLA例子:一個服務,它保證在每秒500個請求的峰值客戶端負載下,它將在300毫秒內為99.9%的請求提供響應。

image

  在 Amazon 去中心化的、面向服務的基礎設施中,SLA 扮演著重要的角色。例如:購物網站的每個頁面的渲染通常會涉及到上百個服務。為了保證使用者體驗,必須對每個服務的延遲做嚴格限制。
  業界形成面向效能的 SLA 的常用方法是使用平均、中位數和預期方差來描述它。在Amazon,我們發現,如果目標是建立一個讓所有客戶都有良好體驗的系統,而不僅僅是大多數客戶,那麼這些指標就不夠好。Dynamo 使用了 P99.9 分佈。這個精度是經過大量實驗分析,權衡了成本和效能之後得到的。 我們在生產環境的實驗顯示,這比基於均值或中位數的 SLA 有更好的使用者體驗。
  儲存系統通常在建立服務的 SLA 中扮演重要角色,特別是當業務邏輯相對於輕量級時,就像許多 Amazon 服務的情況一樣。狀態管理隨後成為服務的 SLA 的主要元件。Dynamo 的主要設計考慮之一是讓服務控制其系統屬性,例如永續性和一致性,並讓服務在功能、效能和成本效益之間做出自己的權衡。

2.3 設計考慮

  商業系統中傳統的資料複製演算法為了提供強一致性的資料介面,往往犧牲了在某些故障情況下的資料可用性。強一致性與高可用性在網路故障下不能同時得到滿足。
  為了提升系統在伺服器和網路故障時的可用性,可以採用樂觀複製(optimistic replication ),非同步地進行副本同步,並容忍併發操作和斷聯等。但這種方法可能會導致資料衝突,需要檢測並解決衝突。但同時解決衝突的過程引入了兩個問題:什麼時候解決,誰來解決。Dynamo 被設計成一個最終一致的資料儲存,也就是說所有的更新最終都會到達所有的副本。

when resolve

  許多傳統的資料儲存在寫過程中解決衝突,來保持簡單的讀複雜度,如果資料儲存無法在給定時間到達所有(或大部分)的副本,則寫操作可能會被拒絕。Dynamo 的設計目標是一個“始終可寫”的資料儲存(即資料儲存高度可寫),這個需求迫使我們將衝突解決的複雜性推到讀操作上,以確保寫操作永遠不會被拒絕。

who resolve

  可以由資料儲存和應用程式來解決衝突。資料儲存側可以使用簡單的策略,如“last write wins”等來解決。另一方面,由於應用程式知道資料模式,因此它可以決定最適合其客戶體驗的衝突解決方法。例如購物車的應用程式可以選擇“合併”衝突的版本資料,並返回一個統一的結果。

設計中包含的其他關鍵原則:
Incremental scalability

  支援節點擴充套件,同時對系統和運維的營銷儘可能減少

Symmetry

  Dynamo 中每個節點的職責應該是相同的,沒有特殊節點或者說沒有節點承擔特殊責任或特殊角色(no master)。對稱性簡化了系統的配置和運維過程。

Decentralization

  去中心化是對稱性的擴充套件,設計應該支援去中心化的、點對點的(peer-to-peer),而不是集中的控制。這使得系統更具擴充套件性和可用性。

Heterogeneity

  系統需要能夠利用其執行的基礎設施中的異構性。例如,工作負載必須與單個節點的能力成比例。這對於新增具有更大儲存空間和處理能力的新節點而無需立即升級所有節點來說是非常重要。

3.相關工作

3.1 點對點系統

  一些點對點(peer-to-peer, P2P)系統關注了資料儲存和分散(data storage and distribution)的問題。
  第一代 P2P 系統如 Freenet 和 Gnutella ,在檔案共享方面廣泛應用,它們建立在非受信的網路上,透過隨機的覆蓋網路鏈路連線節點,並使用泛洪查詢來查詢資料。
  隨後,發展出結構化 P2P 網路,這種網路利用全域性一致性協議高效地路由查詢到儲存特定資料的節點。系統如 Pastry 和 Chord 透過路由機制保證了查詢在有限跳數內得到響應。為了減少路由帶來的延遲,一些系統採用了 O(1) 路由機制,使得節點可以在常量跳數內將請求路由到目標節點。
  在這些路由覆蓋之上,構建瞭如 Oceanstore 和 PAST 等儲存系統。Oceanstore 提供了全球分散式、事務型、持久的儲存服務,並採用衝突解決的方法來處理併發更新,從而避免了廣域鎖帶來的問題。它按順序應用一組更新作為原子操作到所有副本上,適用於在不受信基礎設施上的資料複製。
  相比之下,PAST 是建立在 Pastry 之上的一個簡單抽象層,它提供了持久和不可變物件的儲存,並假設應用可以在其上構建所需的儲存語義,如可變檔案。

3.2 分散式檔案系統與資料庫

  檔案系統和資料庫系統領域已經對透過分發資料(distributing data)來獲得效能、可用性和永續性已經進行了廣泛研究。與只支援扁平名稱空間(flat namespaces)的 P2P 儲存系統相比,分散式檔案系統通常支援層級名稱空間(hierarchicalnamespaces)。
  Ficus 和 Coda 這樣的系統以犧牲一致性為代價複製檔案以獲得高可用性。更新衝突通常使用專門的衝突 解決過程進行管理。Farsite系統是一個分散式檔案 系統,它不使用任何中心化伺服器(像NFS),Farsite 使用複製實現高可用性和可伸縮性。Google File System 是為託管 Google 內部應用狀態而構建的另一個分散式檔案系統。GFS 使用簡單的設計,用一個主伺服器來託管整個後設資料,其中資料被分成塊並儲存在不同的塊服務(chunkservers )中。Bayou 是一個分散式的關係型資料庫系統 ,它允許失聯操作(disconnected operations),並提供最終一致性。

3.3 討論

  Dynamo 與上述去中心化的儲存系統的不同之處在於它的目標需求。

  • Dynamo 被設計為需要“始終可寫”資料儲存的應用程式,其中不會因失敗或併發寫而拒絕更新資料。
  • Dynamo 是為單個管理域中的基礎設施構建的,其中假定所有節點都是受信任的。
  • 使用 Dynamo 的應用程式不需要支援分層名稱空間或複雜的關係模式。
  • Dynamo 是為對延遲敏感的應用程式而構建的,這些應用程式要求至少99.9%的讀寫操作在幾百毫秒內完成。為了滿足這些嚴格的延遲要求,我們必須避免透過多個節點路由請求,Dynamo 可以表徵為零跳分散式雜湊表(zero-hop DHT),其中每個節點在本地維護足夠的路由資訊,以便將請求直接路由到適當的節點。

4.系統架構

  生產環境中執行的儲存系統架構是比較複雜。除了實際的資料持久元件外,系統還需要具有可擴充套件和健壯的解決方案:用於負載均衡(load balancing)、成員管理(membership)和故障檢測(failure detection)、故障恢復(failure recovery)、副本同步(replica synchronization)、過載處理(overloadhandling)、狀態轉移(state transfer)、併發(concurrency)和作業排程(job scheduling)、請求編組(request marshalling)、請求路由(request routing)、系統監控(system monitoring)、 告警(alarming)、 和配置管理(configuration management)。

  本文主要關注 Dynamo 中使用的核心分散式系統技術:

• 分割槽(partitioning)
• 複製(replication)
• 版本化(versioning)
• 成員管理
• 故障處理
• 規模擴充套件(scaling)

image

4.1 系統介面

  Dynamo 透過簡單的介面儲存與鍵相關的物件,它暴露兩個操作 get()put()
  get(key) 操作會在儲存系統中定位與該鍵關聯的物件副本,並返回單個物件或具有衝突版本的物件列表以及上下文(context) put(key,context,object) 操作會根據關聯的鍵確定物件的副本應該放在哪裡,並將副本寫入磁碟。上下文編碼了系統中物件的後設資料,包含了物件版本等資訊,對呼叫者是不透明的(opaque)。上下文是和物件儲存在一起的,系統可以驗證 put 請求中提供的上下文物件的有效性。
  Dynamo 將呼叫者提供的鍵和物件都視為不透明的位元組陣列。對 key 使用 MD5 以生成128位識別符號,該識別符號用於確定儲存節點。

4.2 分割槽演算法

  Dynamo 一個關鍵設計要求是它必須支援增量擴充套件,這就需要一種機制來動態地在系統中的節點集合中對資料進行分割槽。Dynamo的分割槽方案採用於一致性雜湊(consistenthashing)來在多個儲存節點上進行負載分配。

In computer science, consistent hashing is a special kind of hashing technique such that when a hash table is resized, only n/m keys need to be remapped on average where n is the number of keys and m is the number of slots. In contrast, in most traditional hash tables, a change in the number of array slots causes nearly all keys to be remapped because the mapping between the keys and the slots is defined by a modular operation

image

  在一致性雜湊中,雜湊函式的輸出範圍被視為一個固定的迴圈空間或“環”,系統中的每個節點在這個空間內被分配一個隨機值,代表它在環上的“位置”。由 key 標識的每個資料項都被分配給一個節點,方法是對資料項的 key 進行雜湊以產生其在環上的位置,然後順時針走環以找到位置大於該專案位置的第一個節點。
  因此,每個節點負責環中它與其在環上的前任節點之間的區域。 一致性雜湊的主要優點是節點的離開或加入僅影響其直接鄰居,而其他節點不受影響。
  基本的一致性雜湊演算法存在一定的挑戰:

  • 環上每個節點的位置隨機分配導致資料和負載分佈不均勻。
  • 其次,基本演算法忽略了節點效能的異構性。

  Dynamo 使用了“虛擬節點”的概念。 虛擬節點看起來就像系統中的單個節點,但每個節點可以負責多個虛擬節點。每個節點都被分配到環中的多個點,而不是將節點對映到環中的單個點。 也就是說,當一個新節點新增到系統中時,它會在環中被分配多個位置(以下稱為“令牌”)。
  使用虛擬節點有以下優點:

  • 如果某個節點變得不可用(由於故障或日常維護),則該節點處理的負載將均勻地分散到其餘可用節點上。
  • 當某個節點再次可用,或者向系統新增新節點時,新的可用節點會接受來自其他每個可用節點的大致相等的負載量。
  • 節點負責的虛擬節點的數量可以根據其容量來決定,並考慮到物理基礎設施的異構性。

4.3 複製

  為了實現高可用性和永續性,Dynamo 在多個主機上覆制其資料。 每個資料項都在N 個主機上覆制,其中 N 是“每個例項”配置的引數。 每個 key 都分配給一個協調者(coordinator)節點(上面一致性雜湊分配的節點)。 coordinator 負責複製其範圍內的資料項。 除了在本地儲存其範圍內的每個 key 之外,協調器還在環中的 N-1 個順時針後繼節點處複製這些 key。 這導致系統中每個節點負責它與其第 N 個前驅節點之間的環區域。

image

  如上圖所示,節點 B 除在本地儲存之外,還作為 coordinator 複製 key 至節點 C 和節點 D,節點 D 實際儲存範圍為(A, B],(B, C]和(C, D]的 key。
  負責儲存某個特定 key 的節點列表稱為偏好列表(preference list)。4.8節指出,系統的設計使得系統中的每個節點都可以確定對於任何特定的 key,哪些節點應該在此列表中。考慮到節點會存在故障,偏好列表可能包含超過N個節點。考慮到使用了虛擬節點,特定 key 的前 N 個後繼位置可能由少於 N 個不同的物理節點擁有(即,一個節點可能擁有前 N 個位置中的多個位置)。為了解決這個問題,透過跳過環中的位置來構造key 的偏好列表,以確保該列表僅包含不同的物理節點。

4.4 資料版本

  Dynamo 提供最終一致性,允許更新非同步地傳遞給所有的副本。 put() 操作允許更新應用到所有副本上之前就給予呼叫返回,這可能導致後續 get() 操作可能無法返回最新的資料。如果更新過程沒有發生失敗,那麼 更新傳播的時間就有一個界限。但是,在某些故障場景下(例如,伺服器中斷或網路分割槽),更新可能在很長一段時間內無法到達所有副本。
  Amazon 有一類應用程式可以容忍這種不一致,在這些情況下可以繼續執行。以購物車應用為例,“新增到購物車”的請求永遠不能被丟失或拒絕。如果購物車最新的狀態不可用,那麼,對一箇舊版本的購物車資料進行了修改也是有意義的,需要保留。但是這不能直接覆蓋最新狀態,因為最新狀態中有一些修改也需要進行保留。當客戶希望將商品新增到(或從)購物車中刪除時,如果最新的版本不可用,則將該商品新增到(或從) (較舊的版本),並稍後協調不同的版本。
  為了提供這種保證,Dynamo 將每次修改的結果視為資料的一個新的、不可變版本。它允許一個物件的多個版本同時出現在系統中。

Dynamo treats the result of each modification as a new and immutable version of the data

衝突協調方式

基於句法的協調(syntactic reconciliation)
基於語義的協調(semantic reconciliation)

  大多數情況下,新版本包含了以前的版本,系統本身可以確定權威版本(句法協調)。但是,如果存在失敗和併發更新,則可能發生版本分支,從而導致物件的版本衝突。在這些情況下,系統無法協調同一物件的多個版本,客戶端必須執行協調,以便將資料演變的多個分支合併回一個(語義協調)。

vector clock

  Dynamo 使用向量時鐘(vector clock)來記錄同一物件的不同版本之間的因果關係

A vector clockis adata structure used for determining thepartial orderingof events in adistributed systemand detecting causality violations. Just as in Lamport timestamps, inter-process messages contain the state of the sending process'slogical clock. A vector clock of a system of N processes is an array/vector of N logical clocks, one clock per process; a local "largest possible values" copy of the global clock-array is kept in each process.

  一個向量時鐘就是一個 (node, counter) 列表。一個向量時鐘關聯了一個物件的所有版本,可以透過向量時鐘來判斷物件的兩個版本是否在並行的分支上,還是具有因果順序的。如果第一個物件時鐘上的counter 小於或等於第二個時鐘中的所有 counter,則第一個物件是第二個物件的祖先(ancestor),可以被忽略或刪除。否則,這兩個修改是衝突的,需要協調合並。
  如果A1[<node1, 1>],A2[<node1,1>,<node2,2>],那麼A1是A2的祖先,兩者存在因果順序。A1可以被忽略或刪除。如果A1[<node1, 1>],B1[<node2,1>],則兩個版本存在衝突。
  在 Dynamo 中,當客戶端希望更新物件時,它必須指定要更新的版本。這是透過傳遞它從先前的讀取操作獲得的 context 來實現的,context 中包含向量時鐘的資訊。
  在處理讀請求的時候,如果 Dynamo 能夠訪問到多個分支版本,並且無法協調(syntactically reconciled),那它就會返回所有版本的物件,並在 context 中附帶各自的向量時鐘資訊。基於 context 的更新認為協調了版本(semantic reconciliation),解決了衝突,將多個分支重新合併為一個新分支。
  下面來看一下論文中的例子:

image

  客戶端寫一個新物件。處理該 key 寫操作的節點(Sx)增加其序列號(counter),並使用它來建立資料的向量時鐘。那麼現在,系統有物件 D1 和它關聯的時鐘:[<Sx,1>]。
  客戶端更新這個物件。假設同樣的節點(Sx)也處理這個請求。系統現在還有物件 D2 及其關聯時鐘:[<Sx, 2>]。D2 來自於 D1,因此 D1 可以被覆蓋掉。但是 D2 的這次更新可能還沒同步到其他節點上,其他節點上可能還是隻有 D1 的資訊。
  假設相同的客戶端再次更新了物件,而這次由另一個節點(Sy)處理請求。系統現在有資料 D3 和它的 關聯時鐘:[<Sx, 2>,<Sy, 1>]。
  接下來,假設另一個客戶端讀取 D2 並嘗試更新它,另一個節點(Sz)處理這次寫入操作。系統現在有D4 (D2的後代),其時鐘為:[<Sx, 2>,<Sz, 1>]。知道 D1 或 D2 的節點可以在接收到 D4 及其時鐘後確定 D1 和 D2 被新資料覆蓋並且可以被刪除。 一個知道 D3 並接收到 D4 的節點會發現它們之間不存在因果關係。 換句話說,D3 和 D4 的改動沒有反映在對方之中。 這兩個版本的資料都必須保留並呈現給客戶端(在讀取時)以進行語義協調。
  現在假設某個客戶端讀取了 D3 和 D4(context 將反映讀取找到了這兩個值)。讀取的 context 包含了 D3 和 D4 的向量時鐘,即[<Sx, 2>,<Sy, 1>, <Sz, 1>]。 如果客戶端執行協調並且由節點 Sx 協調寫入,Sx 將更新其在時鐘中的序列號。 新資料 D5 將具有以下時鐘:[<Sx, 3>,<Sy, 1>, <Sz, 1>]。

image

可能存在的問題

  如果較多的 coordinator 節點寫入物件,會使得向量時鐘較長,但正如上面說到的,通常來講都是由偏好列表中的前 N 個節點作為 coordinator 寫入資料,因此不會過長。但由於可能存在網路分割槽或多個節點故障的情況出現,這種情況應該限制向量時鐘的長度。為此 ,Dynamo 採用了以下時鐘截斷方案: 與每個(node,counter)對一起,Dynamo 儲存一個時間戳 ,該時間戳表示節點最後一次更新資料項的時間。當節點對達到一個閾值時(10),就將最老的刪除。

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

  Dynamo 中的任何儲存節點都有資格接收任何 key 的客戶端 get 和 put 操作。在本節中,將描述如何在無故障的環境中執行這些操作(下一節介紹有故障情況)。
  客戶端可以使用兩種策略來選擇節點

  1. 透過一個通用負載均衡器路由其請求,該負載均衡器將根據負載資訊選擇一個節點
  2. 使用能分割槽感知的客戶端,直接將請求路由到合適的 coordinator 節點

  第一種方法的優點是客戶端不必在其應用程式中連結任何特定於 Dynamo 的程式碼,而第二種策略可以實現更低的延遲,因為它跳過了潛在的轉發步驟。
  處理讀或寫操作的節點稱為 coordinator。通常是偏好列表中前 N 個節點中的第一個。如果透過負載均衡器接收請求,則訪問 key 的請求可能被路由到環中的任意節點。在這種情況下,如果接收請求的節點不在所請求 key 的偏好列表的前 N 箇中,則該節點不會處理該請求。 相反,該節點會將請求轉發到偏好列表中前 N 個節點中的第一個。
  讀/寫操作通常涉及到偏好列表中的前 N 個節點(跳過那些當機或者無法訪問的節點)。如果所有的節點都是健康的,前 N 個節點將會被訪問,如果出現了節點故障或網路分割槽的情況,偏好列表中排序較低的節點將會被訪問。

quorum

  為了保持副本之間的一致性,Dynamo 使用類似於 quorum systems 中使用的一致性協議。該協議有兩個關鍵可配置值 R 和 W。

  • R:參與成功讀操作所需的最小節點數
  • W:參與成功寫操作所需的最小節點數

  quorum systems 通常設定 R 和 W 使得 R + W > N (N 為分散式系統中節點的個數)。 在此模型中,get/put 操作的延遲由最慢的 R/W 副本決定。 因此,R 和 W 通常配置為小於 N,以提供更好的延遲。

image

  • 如果 W < N,當節點不可用時,我們仍然可以處理寫入。
  • 如果 R < N,當節點不可用時,我們仍然可以處理讀取。
  • 對於 N=3,W=2,R=2,我們可以容忍一個不可用的節點。
  • 對於 N=5,W=3,R=3,我們可以容忍兩個不可用的節點。 這個案例如上圖所示。
  • 通常,讀取和寫入操作始終並行傳送到所有 N 個副本。引數 W 和 R 決定我們等待多少個節點,即在我們認為讀或寫成功之前,有多少個節點需要返回成功

  Dynamo 中在收到對 key 的 put 請求後,coordinator 會生成新版本的向量時鐘並在本地寫入新版本。 然後,coordinator 將新版本(以及新的向量時鐘)傳送到 N 個排名最高的可到達節點。 如果至少有 W-1 個節點響應則認為該次寫入是成功的。
  對於 get 請求也是類似的,coordinator 從該 key 的偏好列表中前 N 個健康節點請求該 key 的所有資料版本,然後等待 R 個響應,然後將結果返回給客戶端。 如果coordinator 最終收集了資料的多個版本,它將返回它認為無因果關係的所有版本。 然後客戶端協調不同的版本,並將最新的版本資料寫回。

4.6 故障處理:間接移交(Handling Failures: Hinted Handoff)

  如果 Dynamo 採用傳統的 quorum 機制,那麼當服務出現故障或者網路分割槽時無法保持可用,即便是最簡單的故障出現時,永續性也會降低。
  為了解決這個問題,Dynamo 並不嚴格執行 quorum 機制,而是採用一個較為寬鬆的 quorum 機制(sloppy quorum),也就上節提到的,偏好列表中前 N 個節點負責處理讀寫操作(實際分散式系統中儲存的節點數量時要超過 N 的,所以稱其為 sloppy quorum)。當然也不一定就是雜湊還中的前 N 個節點,因為可能會出現節點不可用或網路分割槽,此時要跳過該問題節點。

image

  以上圖為例,N=3,如果節點 A 在寫入操作期間暫時故障或無法訪問,則通常本應傳送給節點 A 上的副本會傳送到節點 D。這樣做的目的是為了維持可用性和永續性。 傳送到 D 的副本將在其後設資料中包含一個提示,表明哪個節點是副本的預期接收者,本例即為節點 A。 接收提示副本的節點會將它們儲存在定期掃描的單獨本地資料庫中。 一旦檢測到節點 A 恢復,節點 D 將嘗試將副本傳送給節點 A。一旦傳送成功,節點 D 就可以從其本地儲存中刪除該物件,而不會減少系統中的副本總數。
  由於使用了該機制,Dynamo 可確保讀/寫操作不會因節點或網路臨時出現故障而失敗。 需要最高階別可用性的應用程式可以將 W 設定為1,這確保只要系統中的一個節點將 key 持久寫入成功,寫入就會被系統所接受。 因此,只有當系統中的所有節點都不可用時,寫入請求才會被拒絕。 然而在實踐中,大多數生產服務設定了更高的W來滿足所需的永續性水平。 第 6 節對配置 N、R 和 W 進行了更詳細的討論。
  高度可用的儲存系統必須能夠處理整個資料中心的故障 。Dynamo的配置使得每個物件都跨多個資料中心複製。實質上,key 的偏好列表的構造使得儲存節點分佈在多個資料中心。這些資料中心透過高速網路鏈路連線,在整個資料中心掛掉的情況下仍然可以提供服務。

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

  如果系統節點變動不頻繁並且節點故障是暫時性的,則 Hinted Handoff 效果很好。在某些情況下,執行 hinted 的節點在副本返回到原始副本節點就不可用了(上節,在 A 恢復前,D 先不可用了)。為了處理這個問題和其他對永續性的威脅,Dynamo 實現了一個反熵(anti-entropy)副本同步協議來保證副本同步。

Merkle tree/Hash tree

  為了更快地檢測副本之間的不一致性並最小化傳輸的資料量,Dynamo 使用了 Merkle tree 這一資料結構,其中葉子是各個 key 對應 value 的雜湊值。父節點則是子節點的雜湊值。

image

  Merkle tree 的主要優點:

  • 樹中的每個分支都可以獨立檢查 ,不需要節點下載整個樹或整個資料集。
  • 在檢查副本之間的不一致性時,Merkle tree 可以減少資料傳輸量。

  例如,如果兩棵樹的根的雜湊值相等,則樹中的葉節點的值相等,並且節點不需要同步。 如果不是,則意味著某些副本的值不同。 在這種情況下,節點(node)可以交換子節點的雜湊值,並且該過程將繼續,直到到達樹的葉子,此時主機可以識別“不同步”的keys。 Merkle tree 最大限度地減少了同步所需傳輸的資料量,並減少了反熵過程中執行的磁碟讀取次數。
  每個節點為其託管的每個 key 範圍(虛擬節點覆蓋的 key)維護一個單獨的 Merkle tree。 這允許節點比較 key 範圍內的 key 是否是最新的。 在此方案中,兩個節點交換與它們共同託管的 key 範圍相對應的 Merkle tree 的根。 隨後,使用上述樹遍歷方案,節點確定它們是否有任何差異並執行適當的同步操作。 該方案的缺點是,當節點加入或離開系統時,許多 key 範圍會發生變化,從而需要重新計算。 不過,這個問題已透過第 6.2 節中描述的細化分割槽方案得到解決。

4.8 成員資格與故障檢測

4.8.1 Ring Membership

  在 Amazon 的環境中,節點中斷(故障或維護)通常是暫時的,但也可能會持續很長時間。一個節點服務中斷並不能說明這個節點永久性的離開了系統,因此不應該導致分割槽再平衡,或者修復無法訪問的副本。同樣地,人工失誤也可能無意中啟動新的 Dynamo 節點。由於這些原因,使用顯式機制(explicit mechanism)來對 Dynamo 環中節點進行新增和刪除是合適的。 管理員使用命令列工具或瀏覽器連線到 Dynamo 節點併發出成員資格更改以將節點加入環或從環中刪除。 服務請求的節點將成員資格更改及其釋出時間寫入持久儲存。 成員資格更改形成歷史記錄,因為節點可以多次刪除和新增回來。gossip-based 協議傳播成員資格變更並維護成員資格的最終一致檢視。 每個節點每秒都會聯絡一個隨機選擇的節點,並且兩個節點有效地協調其持久化的成員資格更改歷史。
  當節點第一次啟動時,它會選擇其令牌集(一致雜湊空間中的虛擬節點)並將節點對映到各自的令牌集。 該對映持久化儲存在磁碟上,最初僅包含本地節點和令牌集。 儲存在不同 Dynamo 節點的對映在交換成員資格變更歷史期間進行協調。 因此,分割槽和資料放置資訊也透過 gossip-based 協議傳播,並且每個儲存節點都知道其它節點處理的令牌範圍。 這允許每個節點將 key 的讀/寫操作直接轉發到正確的節點集。

4.8.2 External Discovery

  上述機制可能暫時導致 ring 邏輯分割槽。例如,管理員可以先將節點 A 加入到環中,然後再將 B 加入到環中。在這種情況下,節點 A 和節點 B 都認為自己是環的成員,但是並不會立即感知到對方。為了避免邏輯分割槽,將一些 Dynamo 節點作為種子節點。種子節點是透過外部機制所發現的,所有節點都感知其存在。因為所有節點最終都會和種子節點協調成員資訊,所以邏輯分割槽就幾乎不可能發生。種子從靜態配置檔案中獲取,或者從一個配置服務中獲取。通常情況下,種子節點具有普通節點的全部功能。

4.8.3 Failure Detection

  Dynamo 中的故障檢測用於避免在讀/寫操作期間以及傳輸分割槽和 hinted 複製時嘗試與無法訪問的節點通訊。為了避免嘗試通訊失敗,一個本地的故障檢測機制就足夠了,即:如果節點 B 沒有響應節點 A 的訊息(即使節點 B 響應了節點 C 的訊息),那麼節點A 可認為節點 B 不可訪問。在客戶端請求的穩定存在,此時Dynamo 環中節點之間通訊也是正常的。當節點 B 無法響應訊息時,節點 A 可以快速的發現節點 B 此時已經是無法訪問的了,節點 A 會將本應傳送給節點 B 的請求傳送給備用節點(偏好列表)。同時定期檢測節點 B 是否恢復。在沒有客戶端請求驅動兩個節點之間的流量的情況下,節點之間不需要真正知道另一個是否可達和能否響應。
  去中心化的故障檢測協議使用簡單的 gossip 風格協議,該協議使系統中的每個節點能夠了解其他節點的 到達(或離開)。Dynamo 的早期設計使用這種協議來維護全域性一致的故障狀態檢視。後來確定了顯式機制來增加和刪除節點不在需要故障狀態全域性檢視了。

4.9 新增/移除儲存節點

  當一個新節點 X 被新增到系統中時,它會被分配一些隨機分散在環上的令牌。對於分配給節點 X 負責的每個 key 範圍(每個虛擬節點負責處理的那段範圍),當前可能已經有多個節點(小於或等於 N)在負責處理了。由於將這些 key 範圍分配給了節點 X,那麼一些現有節點則不再需要處理這部分 key 範圍了,這些節點將這些 key 轉移到節點 X 中。

image

  考慮一個簡單的場景,節點 X 加入到節點 A 和節點 B 之間。當節點 X 新增到系統中時,它負責儲存 (F, G]、(G, A] 和 (A, X] 範圍內的 key(N = 3)。那麼,節點 B、C 和 D 不再需要將 key 儲存在各自的範圍中,也就是說節點 B 不再負責 (F, G],節點 C 不再負責(G, A],節點 D 不再負責(A, X] 範圍內的 key 了。 因此,節點 B、C 和 D 在得到節點 X的確認後將向 X 傳輸適當的 key set。 當一個節點從系統中刪除時,重新分配過程與上面描述的相反。

5.實現

  在 Dynamo 中,每個儲存節點都具有三個主要元件,均使用 Java 實現:

  • 請求協調(request coordination)
  • 成員資格和故障檢測(membership and failuredetection)
  • 本地持久化引擎(local persistence engine)

local persistence engine

  Dynamo 的本地持久化元件允許插入不同的儲存引擎。正在使用的引擎包括

  • Berkeley Database (BDB) Transactional Data Store
  • BDB Java Edition
  • MySQL
  • In-memory buffer with persistent backing store

  設計可插拔持久化元件的主要原因是可以選擇最適合應用程式訪問模式的儲存引擎。例如,BDB 通常可以處理數十 KB 數量級的物件,而 MySQL 可以處理更大的物件。 應用程式根據其物件大小分佈選擇 Dynamo 的本地持久化引擎。 Dynamo 的大多數生產例項都使用 BDB Transactional Data Store。

request coordination

  request coordination 元件構建在基於事件驅動架構的 Messaging Substrate 之上,其中訊息處理管道被分為多個階段,類似於 SEDA 架構。所有通訊都基於 Java NIO channel 實現。
  客戶端的讀取和寫入請求實際是透過 coordinator 來執行的,讀操作時從一個或多個節點收集資料,寫操作時同樣在一個或多個節點儲存資料。每個客戶端請求都會在接收請求的節點上建立狀態機。 狀態機包含用於識別負責 key 的節點、傳送請求、等待響應、重試、處理響應以及封裝響應返回給客戶端的所有邏輯。 每個狀態機例項只處理一個客戶端請求。
  讀操作實現以下狀態機(省略了失敗處理和重試的狀態)

  1. 向節點傳送讀請求
  2. 等待返回請求最小響應數量(R)
  3. 如果在給定的時間範圍內收到的響應太少,則請求失敗
  4. 收集所有資料版本並確定要返回的版本
  5. 如果啟用了版本控制,則執行語法協調並生成一個context,包含所有剩餘版本的向量時鐘

  在 coordinator 將讀操作響應返回給客戶端後,狀態機還會等待一小段時間來接收那些未完成的響應(最小讀取數量外的或超時響應的)。如果這些響應中存在過期的版本物件,那麼 coordinator 會將最新的版本物件更新到這些節點,這一過程稱為讀修復,因為這是在取巧的時間(opportunistic time)修復了錯過最近更新的副本(replicas that have missed a recent update),相當於替代了反熵協議同步資料。
  寫請求由偏好列表的前 N 個節點中的一個來協調處理,如果總是由第一個節點協調,雖然有一定的好處,比如寫入操作可以保證一定的順序,但是這會導致不均勻的負載分佈,損害了 SLA。為了解決這個問題,允許偏好列表中前 N 個節點中的任何一個節點協調寫入。通常,一個寫操作之前還會有一個讀操作,因此,前一次讀操作返回最快的那個節點,來作為寫操作的 coordinator ,這個資訊儲存在讀操作返回的 context 中。這種最佳化能夠選擇具有前一次讀取操作並協調寫入的節點更容易被選中,從而增加 read-yourwrites 資料一致的機會。 它還減少了請求處理的抖動,從而提高了P99.9的效能。

6.學習到的經驗與教訓

  Dynamo 在不同型別的服務中,配置也不相同。主要體現在版本協調邏輯和讀/寫仲裁特點上,幾種主要的模式如下。

Business logic specific reconciliation

  這是 Dynamo 的一個流行用例。 每個資料物件都跨多個節點複製。 如果版本不同,客戶端應用程式將執行自己的協調邏輯。 前面討論的購物車服務就是一個典型例子。 其業務邏輯透過合併客戶購物車的不同版本來協調物件。

Timestamp based reconciliation

  該模式與上面的協調機制不同,如果物件版本不同,Dynamo 會執行簡單的基於時間戳的協調邏輯“last write wins”; 即選擇具有最大物理時間戳值的物件作為正確版本。維護客戶會話資訊的服務是使用此模式的服務的一個很好的示例。

High performance read engine

  雖然 Dynamo 被構建為“始終可寫”的資料儲存,但一些服務可以調整其仲裁特性並將其用作高效能讀取引擎。 通常,這些服務讀請求的頻率遠大於寫。 在此配置中,通常 R 設定為 1,W 設定為 N。對於這些服務,Dynamo 提供跨多個節點分割槽和複製資料的能力,從而提供增量可擴充套件性。 其中一些例項在更重量級後備儲存中作為權威永續性快取來用於資料儲存。 維護產品目錄和促銷品的服務屬於此類模式。
  Dynamo 的主要優點是,客戶端應用程式可以調整 N、R 和 W 的值,以達到效能、可用性和永續性所需的等級。例如,N 的值決定了每個物件的永續性。Dynamo 使用者通常使用的 N 是3。
  W 和 R 的值影響物件的可用性、永續性和一致性。 例如,如果 W 設定為1,那麼只要系統中只要有一個節點可以成功處理寫請求,那麼寫請求就永遠不會被拒絕。 但是,較低的 W 和 R 可能會增加不一致的風險,因為即使大多數副本未處理寫入請求,寫入請求也會被視為成功並返回給客戶端。 這也會引入一個永續性上的隱患視窗(vulnerability window),即使它只在少數節點上進行了持久化,寫請求也會成功返回客戶端。
  傳統觀點認為,永續性和可用性是攜手並進的(go hand in-hand)。 然而這裡不一定如此。 例如,可以透過增加 W 來減少永續性的漏洞視窗。這可能會增加拒絕請求的機率(降低了可用性),因為更多的儲存節點需要處於存活時才能處理寫入請求。
  幾個 Dynamo 例項使用的常見(N,R,W)配置是(3,2,2)。選擇這些值是為了滿足必要等級的 SLA,即效能、永續性、一致性和可用性。
  本節中介紹的所有資料測量都是基於(3,2,2)配置的,並且在數百個具有同質硬體配置的節點的實時系統上進行的。Dynamo 中每個例項中的節點都跨多個資料中心部署的。這些資料中心通常透過高速網路鏈路連線。 回想一下,一次成功的讀/寫操作需要 R/W 個節點響應 coordinator。 顯然,資料中心之間的網路延遲會影響響應時間,並且節點的選擇(及其資料中心位置)應滿足應用程式目標的SLA。

6.1 平衡效能與永續性

  雖然 Dynamo 的主要設計目標是構建一個高可用的資料儲存,但效能也是一個同樣重要的指標。Amazon的服務效能通常使用P99.9或P99.99來衡量。使用 Dynamo 的服務的典型SLA要求是 P99.9 的讀寫請求在300ms內執行完成。
  由於 Dynamo 執行在標準商用硬體裝置上,其I/O吞吐量遠低於高階企業伺服器,因此提供一致的高效能讀寫服務是一項非常重要的任務。讀寫操作中涉及多個儲存節點使其更具挑戰性,因為這些操作的效能受到最慢的R或W副本的限制。

image

  上圖顯示了30天內 Dynamo 的讀操作和寫操作的平均值和P99.9的延遲。 延遲呈現出明顯的日變化模式(diurnal pattern),白天和夜間存在明顯的差異,這和每天請求頻率的趨勢是一致的。此外,寫延遲明顯要高於讀延遲,因為寫操作總是會訪問磁碟。其P99.9延遲約為200ms,比平均值高一個數量級。這是因為P99.9受到多個因素的影響,例如請求負載的變化、以及物件大小和位置模式等。
  雖然這種效能級別對於許多服務來說是可以接受的,但一些面向客戶的服務需要更高的效能。 對於這些服務,Dynamo 提供了在永續性保證和效能之間進行權衡的能力。 在最佳化中,每個儲存節點在其主記憶體中維護一個物件緩衝區。 每個寫操作都寫入緩衝區中,並由獨立的寫入執行緒定期寫入磁碟中。 在此方案中,讀操作首先檢查請求的 key 是否存在於緩衝區中。 如果是,則從緩衝區中獲取物件而不是從儲存引擎來讀取物件。
  這種最佳化使得在高峰流量期間將P99.9的延遲降低了5倍,即使對於只有1000個物件的非常小的緩衝區效果也很明顯。

image

  寫緩衝可以降低較高的百分位數延遲。 顯然,該方案犧牲了永續性來換取效能上的提升。 在此方案中,伺服器崩潰可能會導致緩衝區中排隊的寫入丟失。 為了避免這種風險,對寫操作進行了細化,coordinator 會從 N 個副本中選擇一個來執行“持久化寫”,也就是寫入儲存引擎中。 由於 coordinator 僅等待 W 個節點響應,就會返回寫成功,因此寫操作的效能不會受到單個副本執行的持久化寫入的效能影響(持久化寫入的節點響應可能會比較慢,但當其餘寫緩衝區節點響應完成後,就寫成功了)。

6.2 確保均勻負載分佈

  Dynamo 使用一致性雜湊對 key 空間跨多副本進行分割槽,確保負載分佈的均衡。假設 key 的訪問分佈不是高度傾斜的,統一的 key 的分佈可以幫助我們實現均勻的負載。特別的,即使在訪問分佈中存在顯著的傾斜,只要活躍的 key 的數量足夠多,那麼就可以透過分割槽(partitioning)在多個節點之間均勻地分配處理這些活躍的 key 的負載。也就是說,它的設計旨在確保系統能夠透過在節點間分散儲存和處理請求,來均勻處理高負載的活躍的 key,從而避免某個單一節點因處理過資料而成為系統瓶頸。
  本節討論 Dynamo 中出現的負載不平衡以及不同分割槽策略對負載分配的影響。
  為了研究負載不平衡及其與請求負載的相關性,測量了24小時內每個節點收到的請求總數,每30分鐘作為一個時間視窗。 在給定的時間視窗內,如果節點的請求負載與平均負載的偏差小於某個閾值(此處為 15%),則該節點被視為“平衡”。否則該節點被視為“失衡”。

image

  上圖顯示了這段時間內“失衡”節點的比例(虛線),作為參考,還繪製了整個系統在此時間段內接收到的相應請求負載(實線),可以觀察到“失衡”節點的比例隨著請求負載的增加而減小。例如,在低負載期間,失衡比例高達 20%,在高負載期間,不平衡率接近 10%。 直觀上,這可以透過以下事實來解釋:在高負載下,會訪問大量活躍的key,請求會均勻分發到各個節點上,整個系統負載均衡。低負載下(峰值1/8),活躍key 訪問的數量較少,會導致負載不均衡。
  本節討論 Dynamo的分割槽方案是如何隨著時間的推移而演變的,以及它對負載分配的影響。

Strategy 1: T random tokens per node and partition by token value

  這是在生產環境中部署的初始策略(4.2節)。在這個方案中,每個節點被分配 T 個令牌(從雜湊空間中隨機均勻選擇)。所有節點的令牌按照它們在雜湊空間中的值排序。每兩個連續的令牌定義一個範圍。 最後一個令牌和第一個令牌相連,形成一個環空間。由於令牌是隨機選擇的,因此範圍大小各不相同。 當節點加入和離開系統時,令牌集會發生變化,因此範圍也會發生變化。 請注意,維護每個節點的成員資格所需的空間隨著系統中節點的數量線性增加的。
  使用中出現的問題,首先,當一個新節點加入系統時,它需要從其他節點竊取(steal”)它應負責的 key 範圍。但是,將 key 範圍傳遞給新節點的節點必須掃描其本地持久化儲存以檢索適當的資料項集。在生產節點 上執行這樣的掃描操作很棘手,因為掃描是高度資源密集型(磁碟I/O)的操作,並且它們需要在後臺執行,而不影響效能。這就要求我們以最低優先順序執行該任務。 然而,這顯著減慢了節點上線的過程,並且在繁忙的購物季節,當節點每天處理數百萬個請求時,幾乎需要一天才能完成節點上線。
  其次,當節點加入/離開系統時,許多節點處理的鍵範圍發生變化,並且需要重新計算新範圍的 Merkle tree,這是在生產系統上執行同樣是一項重要操作。
  最後,由於 key 範圍的隨機性,沒有簡單的方法來獲取整個 key 空間的快照,這使得歸檔過程變得複雜。 在該方案中,歸檔整個 key 空間需要我們分別從每個節點檢索 key,效率非常低。
  該策略的根本問題是資料分割槽和資料放置的方案是相耦合的。 例如,在某些情況下,最好向系統新增更多節點,以便處理請求負載的增加。 但是,在這種情況下,不可能在不影響資料分割槽的情況下新增節點。 理想情況下,最好使用獨立的方案進行分割槽和放置。為此,評估了以下策略。

Strategy 2: T random tokens per node and equal sized partitions

  該策略將雜湊空間被劃分為 Q 個大小相等的分割槽/範圍(partition/range),每個節點分配 T 個隨機令牌。Q 通常設定的值要遠大於 N 以及,遠大於 ST(Q >> N和Q >>ST),其中 S 為系統節點數。在此策略中,令牌僅用於構建將雜湊空間中的值對映到有序節點列表的函式,而不用於決定資料分割槽。分割槽資料被放置在從分割槽末尾順時針遍歷一致性雜湊環時遇到的前 N 個唯一節點上。

image

  在這個示例中,從包含 key k1 的分割槽的末端遍歷環時,會遇到節點A、B、C。該策略的主要優點是

  • 分割槽和放置解耦
  • 允許在執行時更改放置方案

Strategy 3: Q/S tokens per node, equal-sized partitions

  與策略2類似,該策略將雜湊空間劃分為 Q 個大小相等的分割槽,並且分割槽的放置與分割槽方案解耦。 此外,每個節點都分配有 Q/S 令牌,其中 S 是系統中節點的數量。 當一個節點離開系統時,它的令牌會隨機分配給剩餘的節點,以便保留這些性質(Q/S tokens per node)。 類似地,當節點加入系統時,它會以保留這些性質的方式從系統中的節點“竊取”令牌。這種方法在一定程度上確保了每個節點對儲存和管理資料負有平等的責任。
  使用 S=30 和 N=3 的系統 ,評估了這三種策略的效率。當然,公平比較這三種策略是困難的,策略1的 負載分佈屬性取決於令牌的數量(T),而策略3取決於分割槽的數量(Q)。較公平的方式採用了,所有的策略都使用相同大小的空間儲存成員資格資訊時,測量它們的負載分佈傾斜度。例如,策略1中每個節點需要為環上的全部節點維護各自的令牌的位置,而策略3中每個節點需要維護系統分配給每個節點的分割槽資訊。
  透過改變相關引數(T和Q)來評估這些策略。針對每個節點需要維護的成員資格資訊的不同大小,測量每種策略的負載均衡效率 ,其中負載均衡效率(load balancing efficiency)定義為每個節點平均請求數與負載最高的節點的最大請求數的比值。

image

  策略3的負載均衡效率最好,策略2的負載均衡效率最差。從使用策略1遷移到策略3的過程中,策略2曾短暫地用作臨時設定過度策略。與策略1相比,策略3的效率更高,並且減少了每個節點所需維護的成員資訊。雖然儲存這些資訊不是主要問題,但是節點之間會週期性的交換成員資訊(gossip),所以還是要儘可能的保持這些資訊的緊湊性。此外,策略3的部署更容易:

Faster bootstrapping/recovery

  由於分割槽範圍是固定的,因此它們可以儲存在單獨的檔案中,這意味著只需傳輸檔案即可將分割槽作為一個單元來重新放置(relocated)到新節點處(增加/刪除節點時,可以將對應的分割槽進行傳輸,避免定位特定專案所需的隨機訪問)。 這簡化了 bootstrapping 和 recovery 的過程。

Ease of archival

  定期歸檔資料集是大多數 Amazon 儲存服務的強制性要求。在策略3中,歸檔 Dynamo 儲存的整個資料集更簡單,因為分割槽檔案可以單獨歸檔。
  在策略1中,由於令牌是隨機選擇的,儲存的資料歸檔時需要分別從各個節點檢索 key,通常效率低下且緩慢。 策略3當節點加入(擴容)或離開(縮容)叢集時,為了保持其屬性(Q/S tokens per node),需要進行一定的協調工作。

6.3 分支版本: 何時?有多少?

  上文提及了 Dynamo 被設計為在一致性和可用性間權衡(tradeoff consistency for availability),要了解不同故障對一致性的確切影響,需要多個因素的詳細資料,包括停機時間、故障型別、元件可靠性、工作負載等,詳細介紹這些數字超出了本文的範圍。然而,本節將討論一個很好的總結指標: 應用程式在實時生產環境中看到的不同版本的數量。
  存在不同版本的資料會在兩種情況下出現。 第一種是當系統面臨節點故障、資料中心故障、網路分割槽等故障場景時。 第二種情況是當系統正在處理單個資料項的大量併發寫入,並且多個節點最終同時協調更新時。 從可用性(usability)和效率的角度來看,最好在任何時間都保持不同版本的資料的數量儘可能低。 如果無法根據向量時鐘進行句法協調,則必須將它們返回給客戶端,在業務邏輯上進行語義協調。 語義協調會給服務帶來額外的負擔,因此最好減少。
  對返回到購物車服務的版本數進行了24小時的分析。在此期間,99.94%的請求只看到一個版本,0.00057%的請求看到2個版本,0.00047%的請求看到3個版本,0.00009%的請求看到4個版本。這表明很少產生不同的版本。
  經驗表明,不同版本數量的增加通常不是由故障造成的,而是由於併發寫入數量的增加造成的。 併發寫入數量的增加通常是由繁忙的機器人(自動化客戶端程式)觸發的,很少人為觸發的。 由於案例的敏感性,這個問題不進行詳細討論。

6.4 客戶端驅動or服務端驅動協調

  第5節提到,Dynamo 有一個請求協調元件,它使用狀態機來處理傳入請求。 客戶端請求由負載均衡器統一分配到環上的節點。 任何 Dynamo 節點都可以充當讀取請求的 coordinator。 另一方面,寫入請求將由 key當前偏好列表中的節點作為 coordinator 來協調寫入。 這是由於偏好列表中的節點具有建立新版本標記的額外責任,寫入請求更新的版本就自然而然的由這些節點來標記上。 注意,如果 Dynamo 的版本控制方案基於物理時間戳,則任何節點都可以作為 coordinator 來協調寫入請求。
  考慮由客戶端驅動協調寫入,客戶端應用使用庫(library)在本地執行請求協調。客戶端定期選擇一個隨機的 Dynamo 節點並下載其當前的 Dynamo 系統成員狀態檢視。使用此資訊,客戶端就可以確定任意 key 的偏好列表中的節點集合。讀請求可以在客戶端節點協調,從而避免了負載均衡器將請求分配給隨機的 Dynamo節點時產生的額外網路轉發跳數(hop)。寫操作要麼轉發給 key 對應的偏好列表裡面的一個節點,那麼如果使用的是基於物理時間戳的版本化方式 ,可以在本地協調寫入。
  客戶端驅動的一個重要優點是:不再需要負載均衡器統一分配客戶端的請求負載。 在儲存節點上近乎均勻分佈的 key,也隱約保障了負載的均勻分佈(一致性雜湊,使得系統節點的負載已經是均勻的了)。顯然,該方案的效率取決於系統成員資訊在客戶端的新舊程度。目前客戶端每10秒輪詢一個隨機的 Dynamo 節點來獲取成員資訊的更新。這裡選擇了基於拉的方法,而不是基於的推的方法,因為前者更適合大量的客戶端,並且只需要在伺服器上維護較少的關於客戶端的狀態資訊。然而,在最壞的情況下,客戶端可能會在10秒的時間內持有過時的狀態資訊。 如果客戶端檢測到其成員資格表已過時(例如,當某些成員無法訪問時),它將立即更新其成員資格資訊。

image

  不難發現,客戶端驅動的方式比服務端方式P99.9減少了至少30ms,平均延遲減少了3~4ms。延遲降低的主要原因就是客戶端驅動的方式避免了負載均衡器的開銷,減少了網路跳數,延遲自然就降低了。平均延遲 顯著低於P99.9,是因為 Dynamo 的儲存引擎快取和寫緩衝區有很好的命中率。此外,由於負載均衡器和網路會給延遲引入額外的可變因素(variability),因此P99.9的效能提升要比平均效能提升更明顯。

6.5 平衡後臺與前臺任務

  除了執行正常的前臺讀/寫操作外,每個節點還執行不同型別的後臺任務,用於副本同步和資料切換(hinting or adding/removing nodes)。在早期的生產環境中,這些後臺任務觸發了資源競爭問題,並影響了 常規讀/寫操作的效能。因此,有必要確保後臺任務只在正常關鍵操作不受顯著影響的情況下執行。為此,將後臺任務與流量控制機制(admission control mechanism)整合在一起。每個後臺任務都使用這個控制器來保留資源(例如資料庫)的執行時時間片(reserve runtime slices),這些資源是在所有後臺任務之間共享的。基於前臺任務效能的監控會透過反饋機制來改變後臺任務可以使用的時間片的數量。
  在執行讀/寫操作時,控制器將不斷監視資源訪問的行為。監控的方面包括磁碟操作的延遲、 鎖爭用和事務超時導致的資料庫訪問失敗以及請求佇列等待時間等。這些資訊用於檢查在給定的跟蹤時間視窗中延遲(或失敗)的效能是否接近所需的閾值。控制器檢查資料庫(60s)的讀延遲P99效能是否與預設的閾值(50ms)足夠近。控制器使用這種比較來評估前臺操作的資源可用性。隨後,它決定有多少時間片可用於後臺任務,從而使用反饋迴路(feedback loop)來限制後臺任務。

6.6 討論

  本節總結了在實現和維護 Dynamo 過程中獲得的一些經驗。許多 Amazon 內部服務在過去兩年中都在使用 Dynamo,並且它為應用程式提供了相當高的可用性。特別地,應用程式有99.9995%請求的收到了成功響應(不包括超時),並且到目前為止沒有發生資料丟失的事件。

  • Dynamo 提供了必要的配置能力,調整N,R,W引數來權衡永續性和可用性。
  • 同時,也資料一致性和協調邏輯開放給開發人員(應用程式設計就應考慮各種故障情況和資料不一致)。
  • 採用完全成員關係模型(full membership model),其中每個節點都知道其對等節點儲存了哪些資料(幾百上千個節點透過 gossip 機制完全沒問題,但上萬個就有些困難了,需要引入新的機制來協調,例如DHT systems)。

7.結論

  本文描述了 Dynamo,一個高度可用且可擴充套件的資料儲存,用於儲存 Amazon 電子商務平臺的許多核心服務的狀態。 Dynamo 提供了滿足需要的可用性和效能等級,並正確處理伺服器故障、資料中心故障和網路分割槽等。 Dynamo 具有增量可擴充套件性,允許服務所有者根據當前請求負載進行擴容和縮減。 Dynamo 允許服務所有者調整引數 N、R和 W,從而定製其儲存系統,以滿足他們所需的效能、永續性和一致性 SLA 需求。
  過去一年 Dynamo 的生產使用表明,可以組合分散的技術來提供單一的高可用性系統。 它在最具挑戰性的應用程式環境之一中的成功表明,最終的一致性儲存系統可以成為高可用應用程式的基礎。

參考

相關文章

1.Dynamo: Amazon’s Highly Available Key-value Store
2.Amazon DynamoDB: A Scalable, Predictably Performant, and Fully Managed NoSQL Database Service
3.https://zhuanlan.zhihu.com/p/342178343
4.https://en.wikipedia.org/wiki/Consistent_hashing
5.https://queue.acm.org/detail.cfm?id=2917756
6.https://en.wikipedia.org/wiki/Vector_clock
7.https://en.wikipedia.org/wiki/Merkle_tree
8.https://zhuanlan.zhihu.com/p/556601917
9.Designing Data-Intensive Applications

相關文章