異常處理
Dynamo中把異常分為兩種類型,臨時性的異常和永久性異常。服務器程序運行時一般通過類似supervise的監控daemon啟動,出現core dump等異常情況時自動重啟。這種異常是臨時性的,其它異常如硬盤報修或機器報廢等由于其持續時間太長,稱之為永久性的?;仡橠ynamo的設計,一份數據被寫到N, N+1, ... N+K-1這K臺機器上,如果機器N+i (0 <= i <= K-1)宕機,原本寫入該機器的數據轉移到機器N+K,機器N+K定時ping機器N+i,如果在指定的時間T內N+i重新提供服務,機器N+K將啟動傳輸任務將暫存的數據發送給機器N+i;如果超過了時間T機器N+i還是處于宕機狀態,這種異常被認為是永久性的,這時需要借助Merkle Tree機制進行數據同步。這里的問題在于時間T的選擇,所以Dynamo的開發人員后來干脆把所有程序檢測出來的異常認為是臨時性的,并提供給管理員一個utility工具,用來顯示指定一臺機器永久性下線。由于數據被存儲了K份,一臺機器下線將導致后續的K臺機器出現數據不一致的情況。這是因為原本屬于機器N的數據由于機器下線可能被臨時寫入機器N+1, ... N+K。如果機器N出現永久性異常,后續的K臺機器都需要服務它的部分數據,這時它們都需要選擇冗余機器中較為空閑的一臺進行同步。Merkle Tree同步的原理很簡單,每個非葉子節點對應多個文件,為其所有子節點值組合以后的Hash值,葉子節點對應單個數據文件,為文件內容的Hash值。這樣,任何一個數據文件不匹配都將導致從該文件對應的葉子節點到根節點的所有節點值不同。每臺機器維護K棵Merkle Tree,機器同步時首先傳輸Merkle Tree信息,并且只需要同步從根到葉子的所有節點值均不相同的文件。
讀/寫流程
客戶端的讀/寫請求首先傳輸到緩存的一臺機器,根據預先配置的K、W和R值,對于寫請求,根據DHT算法計算出數據所屬的節點后直接寫入后續的K個節點,等到W個節點返回成功時返回客戶端,如果寫請求失敗將加入retry_list不斷重試。如果某臺機器發生了臨時性異常,將數據寫入后續的備用機器并在備用機器中記錄臨時異常的機器信息。對于讀請求,根據DHT算法計算出數據所屬節點后根據負載策略選擇R個節點,從中讀取R份數據,如果數據一致,直接返回客戶端;如果數據不一致,采用vector clock的方法解決沖突。Dynamo系統默認的策略是選擇最新的數據,當然用戶也可以自定義沖突處理方法。每個寫入系統的<key, value>對都記錄一個vector lock信息,vector lock就是一系列<機器節點號, 版本號/時間戳>對,記錄每臺機器對該數據的最新更新版本信息。如下圖:
讀取時進行沖突解決,如果一臺機器讀到的數據的vector lock記錄的所有版本信息都小于另一臺機器,直接返回vector lock較大的數據;如果二者是平行版本,根據時間戳選擇最新的數據或者通過用戶自定義策略解決沖突。讀請求除了返回數據<key, value>值以外還返回vector lock信息,后續的寫操作需要帶上該信息。
問題1:垃圾數據如何回收?
Dynamo的垃圾回收機制主要依賴每個節點上的存儲引擎,如Berkely db存儲引擎,merge-dump存儲引擎等。其它操作,如Merkle Tree同步產生的垃圾文件回收可以和底層存儲引擎配合完成。
問題2:Dynamo有沒有可能丟數據?
關鍵在于K, W, R的設置。假設一個讀敏感應用設置K=3, W=3, R=1,待處理的數據原本屬于節點A, B, C,節點B出現臨時性故障的過程中由節點D代替。在節點B出現故障到節點B同步完成節點D暫存的修改這段時間內,如果讀請求落入節點B或者D都將出現丟數據的問題。這里需要適當處理下,對于B節點下線的情況,由于其它機器要么緩存了B節點已下線信息,要么讀取時將發現B節點處于下線狀態,這是只需要將請求轉發其它節點即可;對于B節點上線情況,可以等到B節點完全同步以后才開始提供讀服務。對于設置W<K的應用,Dynamo讀取時需要解決沖突,可能丟數據。總之,Dynamo中可以保證讀取的機器都是有效的(處于正常服務狀態),但W != K時不保證所有的有效機器均同步了所有更新操作。
問題3:Dynamo的寫入數據有沒有順序問題?
假設要寫入兩條數據"add item"和"delete item",如果寫入的順序不同,將導致完全不同的結果。如果設置W=K,對于同一個客戶端,由于寫入所有的機器以后才返回,可以保證順序;而多個客戶端的寫操作可能被不同的節點處理,不能保證順序性。如果設置W < K,Dynamo不保證順序性。
問題4:沖突解決后是否需要將結果值更新存儲節點?
讀操作解決沖突后不需要將結果值更新存儲節點。產生沖突的情況一般有機器下線或者多個客戶端導致的順序問題。機器下線時retry_list中的操作將丟失,某些節點不能獲取所有的更新操作。對于機器暫時性或者永久性的異常,Dynamo中內部都有同步機制進行處理,但是對于retry_list中的操作丟失或者多個客戶端引發的順序問題,Dynamo內部根本無法分辨數據是否正確。唯一的沖突解決機器在讀操作,Dynamo可以設計成讀操作將沖突解決結果值更新存儲節點,但是這樣會使讀操作變得復雜和不高效。所以,比較好的做法是每個寫操作都帶上讀操作返回的多個版本數據,寫操作將沖突處理的結果更新存儲節點。