談談分佈式系統中的複製

談談分佈式系統中的複製

談談分佈式系統中的複製

複製幾乎是構成分佈式系統,尤其是分佈式存儲和分佈式數據庫的關鍵所在,那麼本文就來綜合談論下複製技術。

簡單說複製本身可以分爲同步複製和異步複製,兩者的區別在於前者需要等待所有副本返回寫入確認,而後者只需要一個返回確認即可。從用途上,複製可以分爲兩類,一類用於確保不同副本的表現行爲一致(避免divergence),另一類則用於允許不同副本之間的數據差異(divergence不可避免,如Dynomo),先來看看前者。

有若干種手段用於確保不同副本之間的狀態一致:

第一種叫主從複製。主從之間可以是異步複製,也可以是同步複製。例如MySQL,在默認情況下采用異步複製,異步複製容易引起數據丟失,比如主從結構中,主節點的寫入請求還沒有複製到從節點就掛了,當從節點被選爲新的主節點之後,在這之前寫入沒有同步的數據就會被丟失。即便採用了同步複製,也只能提供相對較弱的基本保障,考慮如下情形:主接收寫入請求然後發到從節點,從節點寫入成功後併發送確認給主,如果此時主節點正準備發送確認信息給客戶端時掛了,那麼客戶端就會認爲提交失敗,可是從節點已經提交成功了,如果這是從節點被提升爲主,那麼就出現問題了。

在主從複製結構裏,異步複製相比同步複製具備更高的吞吐量和更低延遲,因此,結合同步和異步複製是一個常見選項。

比如Kafka,根據它的聲稱,這是一個CA系統,也就是同時達到數據一致和高可用。Kafka的複製設計同時包含異步複製和同步複製,同步複製節點組成的集合稱爲ISR(In-Sync Replicas),只有ISR內的所有節點都對寫入確認之後,纔算做寫成功。當一個節點失效,Leader會通過ZooKeeper感知並把它從ISR 中移除。不過Kafka有一個問題,因爲它聲稱F個節點可以容忍F-1個節點失效,這跟其他系統不同,通常類似的設計只能容忍F/2-1個節點失效,也就是說要確保大多數節點都能正常運行,而Kafka這把這個條件弱化成爲只有Leader運行也可以。這樣做是有問題的:假設ISR只剩下一個Leader在運行,如果此時Leader跟ZooKeeper的網絡連接中斷,就會產生一次選舉,讓ISR之外的節點(那些異步複製節點)加入ISR,通常它會落後此前的Leader不少。當原先的Leader跟ZooKeeper網絡連接恢復後,系統就產生腦裂,需要對2個Leader的數據做Merge或者捨棄,後者則會導致數據丟失。

另一個例子來自數據庫高可用設計,如下是Joyent的PostgreSQL高可用方案manatee,一個主節點帶一個同步複製的從節點,以及若干異步複製從節點,當主節點掛了之後,同步複製節點被選舉爲主節點,異步複製節點選舉一個提升爲同步複製,而此前掛掉的節點恢復之後首先加入異步複製集羣。

談談分佈式系統中的複製

另一個高可用設計MaxScale項目也是類似的設計,直接針對BinLog搭建Replication Proxy,該Proxy同時作爲主節點的Slave,以及其他Slave的Master。

談談分佈式系統中的複製

因此可以看出,主從複製對於數據的保障不高,基於主從結構做高可用設計總是一件很困難的事情。

第二種複製手段是通過兩階段提交來提供更高的保障。例如MySQL Cluster就是通過2PC來提供數據同步的。相比之下,主從複製可以看做一階段提交,因爲沒有發生錯誤的回滾。由於2PC很容易因爲參與者宕機導致一直阻塞,因此2PC用於複製並不常見。

第三種則引入分區一致性算法Paxos,又叫複製狀態機。複製狀態機在數據庫開發的很多領域都可以遇到,比如Google Megastore,針對不同分區的每次提交採用複製狀態機來確保每個分區的全局事務提交時序;Google Spanner在單分區內也採用了類似的設計。複製狀態機主要用於滿足兩點需求:客戶端在面對任何一個副本時都具備完全一致的訪問行爲;每個副本在執行請求時都需要按照完全一致的順序來進行。

複製狀態機通常都是基於複製日誌實現的,每一個服務器存儲一個包含一系列指令的日誌,並且按照日誌的順序進行執行。每一個日誌都按照相同的順序包含相同的指令,所以每一個服務器都執行相同的指令序列。因爲每個狀態機都是確定的,每一次執行操作都產生相同的狀態和同樣的序列。保證複製日誌相同就是一致性算法的工作了。在一臺服務器上,一致性模塊接收客戶端發送來的指令然後增加到自己的日誌中去。它和其他服務器上的一致性模塊進行通信來保證每一個服務器上的日誌最終都以相同的順序包含相同的請求,儘管有些服務器會宕機。一旦指令被正確的複製,每一個服務器的狀態機按照日誌順序處理他們,然後輸出結果被返回給客戶端。因此,服務器集羣看起來形成一個高可靠的狀態機。

談談分佈式系統中的複製

複製狀態機如此重要,那麼有沒有獨立的實現呢,ZooKeeper和Raft都是。以ZooKeeper爲例,它實現了基於ZAB(ZooKeeper Atomic Broadcasting)協議的複製狀態機。Leader產生狀態變化後,跟隨者產生相應的持久化日誌並做確認。當從足夠的副本(包含跟隨者和Leader)獲得ACK確認後,Leader通過發送提交信息進行一次提交。當某副本得到提交信息後,它應用到本地並進行相應的狀態調整,所以,在ZooKeeper裏進行一次狀態確認需要兩輪的消息傳送,這是消耗很高的工作。

談談分佈式系統中的複製

除了在Spanner這種前衛數據庫中見到的複製狀態機,在常規數據庫裏我們能見到的很少(拿來做分佈式協調者的不算),那麼,我們通常見到的MariaDB Galera Cluster是否算複製狀態機呢?Galera完全是多主同步複製,複製協議採用Atomic broadcast,而原子多播是複製狀態機的一種實現[4]。

談談分佈式系統中的複製

Galera的複製工作如下:

1. 事務在本地節點執行時採取樂觀策略,成功廣播到所有節點後再做衝突檢測

2. 檢測出衝突時,本地事務優先被回滾

3. 每個節點獨立、異步執行隊列中的Write Set

4. 事務T在A節點執行成功返回客戶端後,其他節點保證T一定會被執行,因此有可能存在延遲,即虛擬同步

Galera是同步複製方案,事務在本地節點(客戶端提交事務的節點)上提交成功時,其它節點保證執行該事務。在提交事務時,本地節點把事務複製到所有節點後,之後各個節點獨立異步地進行certification test、事務插入待執行隊列、執行事務。然而由於不同節點之間執行事務的速度不一樣,長時間運行後,慢節點的待執行隊列可能會越積越長,最終可能導致事務丟失。Galera內部實現了flow control,作用就是協調各個節點,保證所有節點執行事務的速度大於隊列增長速度,從而避免丟失事務。根據[2]的測試結果,Galera會導致stale read,也不能提供宣稱的快照隔離事務級別,因此,選擇的時候需要知道它的適用場景。

此外,還有一種複製叫做鏈式複製,本號之前的文章有介紹,它是主從複製的變種,所有讀請求都轉發到尾部節點響應,所有的寫入請求都從頭部節點寫入,因此所有寫入請求都嚴格按照單一時序進行,而針對尾部的讀請求可以做到強一致性。相比複製狀態機,鏈式複製可以提供更高的容錯性,因爲Paxos是基於多數選舉原則的算法,如果系統可以容納F個節點錯誤,那麼系統中需要有2F+1個副本存在,而鏈式複製只需要F+1個副本。跟主從複製相比,主從複製中的主節點的角色在簡單鏈式複製中由2個副本承擔:頭節點負責寫入請求的順序化,尾節點負責響應。這種角色的分工可以產生更低的讀延遲和開銷,因爲只有一個節點負責處理讀請求,而且不用等待其餘任何副本的寫入請求,然而,鏈式複製的寫入延遲會增加,因爲需要鏈上的所有副本都串行寫入成功後纔可返回響應,這一點要大大低於主從結構。因此,簡單鏈式複製適用於高吞吐量和強一致性要求的場景(高併發讀,低延遲寫屬於被犧牲的方面)。

談談分佈式系統中的複製

再來看看第二種複製的用途——允許不同副本之間的數據差異。比如在Dynamo系統中,不同副本的數據並不總完全一致,可以按照R+W和N的關係進行Quorum選舉。

首先看看強Quorum,就是R+W>N的情況。這種情形是完全可以保證數據讀寫一致的,正如Amazon Dynamo那樣。典型部署如下:在N個節點中,

R=1,W=N,這是針對讀優化的場景

R=N,W=1,這是針對寫優化的場景

R=N/2,W=N/2+1,這是針對讀寫適中的場景

例如默認情況下,Riak採用了N=3,R=2以及W=2的配置。

當R+W<=N時,叫做Partial Quorum,通常用於低延遲的服務,此時,讀取不一致數據的概率是:

談談分佈式系統中的複製

當N的值很大時,不一致的概率就會降低。2012年的文章PBS(Probabilistically Bounded Staleness)就是基於此提出的理論,旨在提供最終一致的程度,並在Cassandra數據庫上進行了實現[3],根據測試,N=3時,從R=1, W=1變換到R=2, W=1時,數據不一致的窗口可以從1352 ms降低到202 ms,同時相比R=3,W=1的219ms延遲, R=2,W=1則可以降低到32ms。

談談分佈式系統中的複製

採用Quorum算法可以提供比Paxos更高的吞吐量,例如Apache BookKeeper項目,區別於ZooKeeper基於選舉和一致性模塊機制的複製狀態機,BookKeeper可以服務高吞吐量的分佈式日誌系統。BookKeeper仍然依賴ZooKeeper作爲一致性模塊,這個項目已經很多年沒有更新了,如果不是看到近期Twitter採用BookKeeper作爲其分佈式日誌核心組件的介紹[1],也不會關注到。在BookKeeper中,引入一個概念叫Ledger,可以把它看作事務日誌,對於每個Ledger,都只有唯一的寫入者,寫入者通過多個被稱爲Bookie的節點向Ledger同時寫入,當大部分Bookie被寫入,並且可以通過向不同組的Bookie寫入不同數據提高寫入並行度。

談談分佈式系統中的複製

然而對於需要讀取的客戶端來說,就存在困難,因爲它無法知道這些寫入的數據是否被正確地複製到了其他副本,所以需要從多個Bookie讀取數據才能確保一致。此外,BookKeeper還引入了ZooKeeper來存放被正確複製的最後記錄的ID,爲避免過於頻繁地向ZooKeeper寫入數據,該操作僅在Ledger關閉時進行。

談談分佈式系統中的複製

談談分佈式系統中的複製

談談分佈式系統中的複製

通過一系列優化,儘管通過一致性模塊進行協調,但BookKeeper仍然保持了非常高的吞吐量和低延遲---差不多可以達到百萬TPS和10毫秒級別,非常適合作爲分佈式日誌和消息隊列,Yahoo的Cloud Message Service也基於它進行。

關於複製還應當瞭解的一個話題叫做CRDT[5],全稱叫做Convergent Replicated Data Type,目的是提供不用協調的收斂到一致的數據結構,因此又稱爲Conflict-free Replicated Data Type。分佈式計數器是CRDT數據結構中最常見的一個例子:假設每個用戶都能寫入到自己的計數器中,當每個用戶需要改變它的計數器數值時,它只改變自己的數值,這樣就有了併發同時發生的數值增加。因此,這是個分佈式系統,那麼如何得到最後的整個計數器結果?只要將單獨的這些計數器加起來即可—爲了得到一個全局整體的計數器,每個節點用戶都需要知道彼此另外節點上的計數器值,可以在節點間廣播這些值。可是網絡不是100%可靠,廣播的消息有可能會丟失或延遲到達。解決消息丟失並不容易,讓我們來看看如何解決:

1. 在發送方,節點服務器規則地廣播它們的狀態。

2. 在接收方,我們需要一種方式搞清楚發現消息的重複,節點服務器通過比較接受值和現有值,如果這兩個值是相同的表示這是重複的消息,否則就是一個新的消息,更新原來的值即可。

解決延遲消息投遞問題需要另外的技巧:假設計算器總是不斷向前增加,不允許減少。那麼現在該發現當接受到消息應該怎麼做了:只要選擇接受值和現有值之間最大差值的那個值。這樣,每個節點都知道接受到消息後該怎麼做,我們有了最終強一致性,因爲只要一個節點接受到所有廣播消息事件(無論以任何順序),它會知道計數器當前狀態,每個節點的狀態也是相同的,沒有衝突,所有節點最終會匯聚到相同的值。這就是CRDT的分佈式計數器。

CRDT還有其他一些應用場景,具體可以看[5]的綜述:

支持加法和減法的計數器

Last-Writer-Win註冊器(一個註冊器是一個能夠存儲對象和值的單元) – 基於時間戳merge

Multi-value 註冊器 – 基於版本向量merge,如Amazon的Dynamo

Grow-only Set (支持新增和尋找). 這將會表明對於高級類型是有用的構建塊。

2P-Set (two-phase兩段集合set), 其每個條目能被增加或可選移除,但是不會在其後再次增加

U-Set, (unique set). 2P-Set的簡化。

Last-Writer-Wins element Set, 元素條目是有時間戳,有add-set 和 remove-set

PN-Set 爲每個元素保留計數器

Observed-Remove Set

Riak以及Akka都有支持CRDT數據結構的工具包[6][7]。

複製的話題非常廣泛,幾乎涵蓋了分佈式系統設計的每個方面,下次本號再次涉及該話題時,將重點講述複製狀態機在新型數據庫中的具體設計。

[1] http://www.infoq.com/cn/news/2015/09/BookKeeper-Twitter

[2] https://aphyr.com/posts/328-call-me-maybe-percona-xtradb-cluster

[3] http://pbs.cs.berkeley.edu/ Probabilistically Bounded Stalenessfor Practical Partial Quorums

[4] http://www.gpfeng.com/?p=603

[5] A Comprehensive Study of Convergent and Commutative Replicated Data Types

[6] http://docs.basho.com/riak/latest/theory/concepts/crdts

[7] https://github.com/jboner/akka-crdt

本文爲頭條號作者發佈,不代表今日頭條立場。

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