1. 程式人生 > >副本機制與副本同步------《Designing Data-Intensive Applications》讀書筆記6

副本機制與副本同步------《Designing Data-Intensive Applications》讀書筆記6

一致性 不響應 rabbit 故障恢復 logs 啟動 markdown 分布式系統 觸發器

進入到第五章了,來到了分布式系統之中最核心與復雜的內容:副本與一致性。通常分布式系統會通過網絡連接的多臺機器上保存相同數據的副本,所以在本篇之中,我們來展開看看如何去管理和維護這些副本,以及這個過程之中會遇到的各種問題。

1.副本

在數據系統之中,我們通常會有這樣幾個原因來使用副本技術:

  • 保持地理位置接近用戶,從而減少延遲(如:Cache,CDN技術
  • 提高系統的可用性和魯棒性,即使系統中的某些部分已經失效了,仍然可以對外提供服務。(如:GFS三副本的設計
  • 通過擴展性來提供讀查詢,從而增加讀取吞吐量。(如:ZooKeeper之中的Observer

首先,如果副本的數據不隨時間變化,那麽副本的管理是比較簡單的:只需要將數據復制到每個節點一次,就OK了。副本管理真正的困難在於對副本數據的修改,這會涉及到很多瑣碎的問題。其次,副本復制時要考慮許多權衡,使用同步還是異步復制,以及如何處理失效的副本?接下來我們來一一探討這個問題。

2.Leader-Follower機制

如何保障多個副本在不同節點上的一致性一直分布式系統之中的一個核心問題。分布式系統在寫入數據時,需要由每個副本進行處理;否則,副本將不再包含相同的數據。Leader-Follower是一種常見的機制,我們來梳理一下它的原理:

    1. 一個節點上的副本被指定為Leader。當客戶端需要向系統寫入數據時,必須將寫入請求發送給Leader,由Leader首先將新數據寫入本地存儲的副本。
    1. 管理其他副本的節點稱為Follower。每當Leader將新數據寫入本地存儲d的副本時,也會將數據更改寫入日誌之中。每個Follower會從Leader那裏獲取修改日誌,並相應地更新數據到的本地副本之中,這樣,所有的在Follower
      上副本的修改順序會和Leader保持相同的順序。
    1. 當客戶端需要從系統之中讀取數據時,它可以查詢Leader或其他Follower。(註:Follower與Leader之中的數據存在延遲,無法保證強一致性)寫入請求只能由Leader來響應,或是由Follower轉發給Leader

技術分享圖片

許多關系數據庫在同步副本時使用這樣的機制,如PostgreSQL,MySQL,Oracle Data Guard 和SQL Server。同時許多非關系型數據庫與分布式消息隊列也采用這樣的機制,包括MongoDB,Rethinkd,Kafka,RabbitMQ。

2.1 同步與異步復制

在副本進行主從復制時一個重要細節是復制是同步還是異步發生的?(在關系數據庫中,這往往是一個可配置的選項。在其他系統之中,如Ceph,是系統默認的)

技術分享圖片

由上圖可知,同步復制有相當大的延遲,而異步復制的響應相當快速。但是異步復制卻不能保證完成所需要多長時間。有些情況下,Follower的數據可能比Leader上的數據落後幾分鐘或更多。如:節點之間存在網絡問題或節點的故障恢復。如果Leader失敗且不可恢復,則尚未復制到Follower的任何寫操作都將丟失。

而同步復制的優點是保證了Follower與Leader之間的副本一致性,一旦任意一個Leader失效了,任何一個Follower的數據都與Leader相同。但是同步復制一旦出現網絡或節點的故障,會導致無法處理寫入。Leader必須阻止所有寫入並等待Follow上的副本再次可用。如果所有的Follower都是同步復制,那麽任何一個節點的中斷都會導致整個系統癱瘓。在實際運用之中,如果在數據庫上啟用同步復制,通常其中一個副本是同步復制的,而另一個是異步復制的。如果同步的副本變得不可用或十分緩慢,可以將同步操作切換到另一個異步副本之中。這樣保證了至少兩個節點上有一個數據的最新副本:Leader和一個同步Follower。這種配置稱之為半同步。(鏈式復制也是類似於半同步的一種復制機制,不丟失數據但仍能提供良好性能和可用性的復制方法。)

2.2 添加新的Follower

有時我們需要添加新的Follower來增加副本的數量,或者替換失敗的節點。此時就需要確保新的Follower擁有一個正確的副本的數據。僅僅將數據文件從一個節點復制到另一個節點通常是不夠的:客戶端不停向系統寫入數據,所以數據副本總是處於不斷變化的狀態。這裏可以簡單地通過鎖定系統,使其拒絕客戶端的寫請求來使各個副本上保持一致,但這樣會大大降低系統的可用性。所以我們需要一個不停機的方式來添加新的Follower:

  • 1.在某個時間點對Leader的副本進行快照,並且將快照復制到新加入的Follower節點。

  • 2 .Follower連接到Leader,並向Leader請求快照之後所有的數據更改。通常是Leader節點的日誌序列號。

    1. 當Follower處理完快照之後的數據更改之後,它就可以正常處理來自Leader的數據更改了。
2.3 節點故障

在分布式系統之中,任何節點都可能出現故障,而能夠在不停機的情況下重新啟動單個節點是操作和維護是十分必要的。盡管每個節點故障,但我們需要讓一個節點停機的影響盡可能小。

  • Follower故障

在Follower的本地磁盤上,都保存著從Leader收到的數據更改的日誌。當一個Follower崩潰並重新啟動,或者Leader與Follower之間的網絡暫時中斷。Follower可從它的日誌找到故障發生之前處理的最後一個事務,然後連接到Leader並請求在Follower斷開連接的時候發生的所有數據變化。(這個流程和添加新的Follower其實是同樣的思路

  • Leader故障

在處理Leader的失敗時顯然會更為棘手:其中一個Follower需要被提升為新的Leader,客戶端也需要識別並且將後續的請求發送給新的Leader,而其他的Follower則需要開始在新Leader之下工作。處理Leader故障通常是如下的流程:

  • 1、確認Leader失效。絕大多數系統使用超時機制:如果一個節點不響應一段時間,例如,30秒,它被認為是失效了。(如果是中心化的系統可以采用Lease機制。筆者在碩士生階段對Cassandra數據庫有過系統的調研,在Cassandra中采用了由日本學者Naohiro Hayashibara提出的《The Phi Accrual Failure Detector》失敗探測算法,通過多維度累積量來判斷節點是否失效,不失為一個好的解決方案,十分適合類P2P架構的分布式系統

  • 2、選取新的Leader。在中心化架構之中,如HDFS,新的Leader可以用中心化節點指定。而在非中心化的架構之中,則可以通過選舉過程來完成,分布式系統之中的選舉協議有很多:2PC,3PC,Paxos,Raft等等。

  • 3、調整系統配置以使用新的Leader。如果舊的Leader回歸到集群,它可能仍然認為自己是Leader,這時需要確保舊的Leader成為Follower並承認新的Leader。

如果是異步復制的場景,新的Leader可能舊的Leader之前的完整的寫入信息。最常見的解決方案是丟棄舊Leader之前寫入多於新Leader的信息丟棄,但是這顯然違反了數據系統寫入持久性的要求。
在某些故障場景中,可能會出現兩個節點都認為他們是Leader,這種情況被稱為腦裂。此時兩個Leader都會接受寫請求,數據很可能會出現丟失或損壞。
什麽時候進行故障切換也是一個值得探討的問題:較長的超時時間意味著在Leader失效的情況下恢復時間更長。然而,如果時間太短,可能會有不必要的故障轉移。例如,臨時負載高峰時刻可能導致節點的響應時間增加到超時,那麽不必要的故障轉移會使情況變得更糟,而不是更好。為此,一些運營團隊更願意執行手動的故障轉移,即使系統本身支持自動的故障轉移。

3. 日誌的復制

日誌在副本的一致性之中是至關重要的,所以我們接下來簡要的梳理一下日誌復制可用的方法:

  • Statement-Based復制
    在最簡單的情況下,Leader將每個寫請求通過日誌的形式發送給Follower。每個Follower解析和執行對應的操作語句,雖然這聽起來很合理,但是實際操作中會存在一些坑:

(1) 非確定性函數,如now()獲得當前的日期和時間或rand()得到一個隨機數,這樣會導致副本之間的不一致。(這裏可以轉換思維,用一個確定的修改值,來替換不確定性的函數調用)

(2) 如果使用一個自動遞增的列,或如果他們依賴於數據庫中的現有數據(例如,更新…在<條件>),他們必須執行完全相同的順序在每個副本,否則也會產生不一致性。(異步轉發,亂序到達。這個可以通過操作序列號等強制要求進行規避。

(3) 有副作用的語句(例如觸發器、存儲過程、用戶定義函數)可能會導致每個副本上出現不同的副作用。

  • Write-ahead日誌復制
    日誌是一個只包含所有寫入操作的字節序列。我們可以使用完全相同的日誌來在另一個節點上構建一個副本。Leader將日誌寫入磁盤之後,將它通過網絡發送給Follower。當Follower處理這個日誌時,它構建了一個與Leader完全相同的數據結構的副本。這種方式的缺點是:日誌在非常低的級別上描述數據。這使得數據拷貝與存儲引擎緊密耦合。

  • Row-based日誌復制
    Row-based與Write-ahead的方法類似,但是它允許復制日誌與存儲引擎內部分離。這種日誌稱為邏輯日誌,邏輯日誌通常是描述在一個行的粒度上記錄寫入操作:
    對於插入的行,日誌包含所有列的新值。
    對於已刪除的行,日誌包含足夠的信息以唯一地標識刪除的行。(主鍵
    對於更新的行,日誌包含足夠的信息以唯一地標識更新的行,以及所有列的新值。
    由於邏輯日誌與存儲引擎內部分離,因此可以更容易地保持向後兼容,從而允許Leader與Follower運行不同版本的數據系統,甚至是不同的存儲引擎。同時,邏輯日誌格式對外部應用程序也更容易解析。可以將邏輯日誌的內容發送到外部系統(如用於離線分析的數據倉庫),或者用於構建自定義索引和緩存。

4. 復制延遲

副本可以增加系統的可伸縮性(處理比單個機器處理更多的請求)和降低延遲(將副本放置在離用戶更近的地方)。寫入操作必須通過Leader副本,但是只讀查詢可以在任何副本上進行。 對於一次寫入,多次讀取的應用來說,采用讀擴展架構是十分合理的。但是由於上文提及的原因,我們通常不會采用同步復制的方式。這將導致數據出現明顯的不一致性:如果您同時對Leader和Follwer執行相同的查詢,可能會得到不同的結果,因為並不是所有的寫入實時在Follower上反饋。這種不一致性僅僅是暫時狀態,所以這種情況被稱為最終一致性。

對於這種情況我們應該這麽去處理和理解,我們下回分解~~~(第五章的內容炒雞多,接下來會通過多篇讀書筆記來給大家梳理,講解,下一篇再見~~

副本機制與副本同步------《Designing Data-Intensive Applications》讀書筆記6