轉自:http://www.letiantian.me/2014-06-16-dynamo-algorithm-protocol/
Dynamo是Amazon的一個分布式的鍵值系統,P2P架構,沒有主從的概念,數據一致性做到了最終一致。Apache Cassandra參考了它的實現方法。
一致性哈希
關於一致性哈希的具體內容,可以參考一致性哈希。
容錯
由於一致性哈希的使用,Dynamo集群中的節點在邏輯上可以認為是一個圓環。假設有M個節點,我們從某個節點開始順時針地依次為每個節點標號為1、2、3、…、M。出於容錯的需要,假設一份數據存3份。如果某份數據通過一致性哈希被存儲到了節點2中,那么這份數據的另外兩個副本存儲在節點3和節點4中。如果節點3臨時性的宕機了,那么在節點3恢復之前,會把增量數據存入節點5中;待節點3恢復后,節點5通過Gossip協議發現節點3恢復了,節點5會將那些暫存的數據“數據回傳”給節點5。判斷節點3的宕機是臨時性的還是永久性的方法比較簡單,就是看它宕(dang)機的時間長短。如果節點3永久性宕機了,那么需要使用有效的方式將這份數據的完整版本同步到節點5中。
Gossip協議
Gossip協議,也就是閑話協議。主要用來讓每個節點知道集群的最新狀態。這個協議其實就是:
with a given frequency, each machine picks another machine at random and shares any hot rumors.
節點之間以固定的時間頻率交換信息。在交換信息時,一節點隨機選取集群中的其他某個節點交換各自對集群的掌握情況,並據此更新到最新(或者較新)的集群狀態信息。
NWR
N表示一份數據的副本數量。W代表寫操作成功所至少需要的副本數,即在一次寫入操作中至少W個副本寫成功了,這次寫操作才算成功。R代表讀操作成功所至少需要的副本數。Dynamo認為只要R+W>N,可以保證集群的可用性。N、W、R的值是可以設定的。如果注重讀的效率,可以把R的值設置小些;如果注重寫的效率,可以把W的值設置小些。NWR並不能保證數據一致。如果R=N且W=N,那么可以保證一致性。
向量時鍾
對於小型的或者要求不高的分布式系統而言,可以使用時間戳的方式保證副本之間數據的一致性,在時間戳方式下,多使用NTP協議同步時鍾,節點之間的時鍾有較小的誤差。不過在大型分布式系統中,還是換種方式比較好。
向量時鍾(Vector Clock),Amazon Dynamo使用的解決數據一致性問題的方法。這是一個邏輯上的時鍾。假設一份數據三個副本,這三個副本分別命名為n1
、n2
、n3
,每個副本都會記錄所有副本的時鍾(包括自身的),一個副本一個向量,三個副本則共有三個向量。所謂時鍾,其實就是所存儲數據的版本號,一般從0遞增即可。更新時鍾的規則如下:
- 初始化所有時鍾,即全部置0。
- 某副本有數據更新時,將其自身的向量中自身的時鍾的值加一個步長,一般步長設置為1。
- 當一副本向其他副本發送消息時(一般是為了同步數據),這個副本會把自身的向量一起發送給其他副本。
- 若一副本接收到消息,比較自身的向量和發送來的向量,如果發送來的消息是希望同步數據,那么需要判斷是否更新數據。對每個向量的元素比較並取最大值,以此更新自身的向量。那么,如何更新數據? 該副本自身存儲的向量的每一個值都小於發送來的向量的每一個值,說明發送來的數據比較新,那么更新數據。如果都大於,則不需要更新數據。當然,第三種情況是既有大於的關系,也有小於的關系;還有一種情況是向量相同,但是數據不同。這種情況下,需要進行沖突的解決,比如再比較時間戳。
舉個例子。
假設,n1
、n2
、n3
要存儲的用戶id為1的用戶的昵稱。
最開始,三個副本的向量時鍾以及數據如下表示:
n1: { vector: {n1:0, n2:0, n3:0}, data: null } n2: { vector: {n1:0, n2:0, n3:0}, data: null } n3: { vector: {n1:0, n2:0, n3:0}, data: null }
時刻1,n1將用戶昵稱更新為john,向量時鍾以及數據更新后如下:
n1: { vector: {n1:1, n2:0, n3:0}, data: 'jian' } n2: { vector: {n1:0, n2:0, n3:0}, data: null } n3: { vector: {n1:0, n2:0, n3:0}, data: null }
此時對系統進行讀操作,結果應是’jian’。n1給n2、n3發送了消息,更新后如下:
n1: { vector: {n1:1, n2:0, n3:0}, data: 'jian' } n2: { vector: {n1:1, n2:0, n3:0}, data: 'jian' } n3: { vector: {n1:1, n2:0, n3:0}, data: 'jian' }
此時對系統進行讀操作,結果應是’jian’。
時刻2,n3將用戶昵稱改為’fan’,更新后如下:
n1: { vector: {n1:1, n2:0, n3:0}, data: 'jian' } n2: { vector: {n1:1, n2:0, n3:0}, data: 'jian' } n3: { vector: {n1:1, n2:0, n3:1}, data: 'fan' }
此時對系統進行讀操作,結果應是’fan’。n3先給n2發送了消息,更新后如下:
n1: { vector: {n1:1, n2:0, n3:0}, data: 'jian' } n2: { vector: {n1:1, n2:0, n3:1}, data: 'fan' } n3: { vector: {n1:1, n2:0, n3:1}, data: 'fan' }
當n3要給n1發消息之前,n1卻對數據進行了修改,例如將用戶昵稱改為’ ruan’,更新后如下:
n1: { vector: {n1:2, n2:0, n3:0}, data: 'ruan' } n2: { vector: {n1:1, n2:0, n3:1}, data: 'fan' } n3: { vector: {n1:1, n2:0, n3:1}, data: 'fan' }
此后,可能會出現下面兩種沖突:
- 對系統進行讀操作,發現n2、n3與n1的向量沒有偏序關系(即不小於也不大於),而且存的數據的值是不同的。此時需要解決沖突。
- n1收到了n3發送來的消息,比較了兩者的向量,發現了沖突,於是想辦法解決。
資料
《大規模分布式存儲系統——原理解析與架構實踐》第五章 楊傳輝 著
《深入NoSQL》 Shashank Tiwari 著 巨成 譯