本地讀寫的多活數據存儲架構設計要義

本文由公衆號EAWorld翻譯發表,轉載需註明出處。數據庫

 

做者:Parashar Borkotoky 安全

譯者:白小白 網絡

原文:http://t.cn/AiKO0q4P架構

原題:Design Considerations in a read local write local multi-master data storeapp

 

本地讀寫多活示例異步

 

本地讀-本地寫的多活數據存儲架構是最難實現的數據模式之一。這一模式也常被稱爲「雙活」或者「多主」,對於不一樣行業大容量低延遲的事務類應用而言,這是一種必備的能力。分佈式

 

系統的總體可用性取決於單獨組件的可用性。用戶界面層、服務層和消息層能夠跨分區/跨地域進行橫向擴展以提供高可用性,但對於事務性數據存儲(尤爲是寫操做)而言,採用一樣的處理方案仍舊充滿挑戰。不管部署在本地或者雲端,不少關係型數據庫和noSQL數據庫都提供了這種開箱即用的能力。也有一些企業選擇本身實現定製化的複製方案來達成多活的目標,對於強烈依賴低延遲的用戶尤爲如此。忽略方案的差別性,人們須要對一些通用的風險與權衡進行仔細考量。ide

 

同步複製與異步複製微服務

 

首當其衝須要考慮的就是在跨可用域的數據複製過程當中,是採用同步複製仍是異步複製的方案。翻譯

 

同步複製技術須要實現多可用域/可用區間的同步寫入。這將會帶來不少的問題:

 

  1. 隨着可用域的增長,寫入延遲也將逐漸增長

  2. 域內的服務須要同步的訪問域外資源,這一般會令域的回收和轉移變得更加複雜

  3. 故障檢測和恢復會變得愈來愈複雜。本地域的數據存儲寫入成功,對其餘域的數據存儲寫入失敗,這種狀況該怎麼處理?其餘域的數據存儲的不可用,是否應該影響本地域的服務可用性?

 

鑑於以上的這些因素,異步複製一般是首選方案,從而也引入了最終一致性的話題。

 

擁抱最終一致性

 

CAP定理

 

CAP定理指出,對於一個分佈式的數據庫系統而言,一致性(C)、可用性(A)和分區容忍性(P)三者僅能居其二。由於分區容忍性是現代分佈式系統的必備要件,這將歸結爲在一致性和可用性之間取得一個平衡。PACELC定理進一步擴展了CAP的理念,並指出,即便不考慮分區的狀況,仍舊須要在延遲(L)和一致性(C)之間進行必定的權衡。

 

譯註:PACELC中的E即Else,也就是或者。對於有分區的狀況,須要權衡AC,或者,在沒分區的狀況,須要權衡LC

 

在大量的用例中,最終一致性是可接受的妥協。好比審計或者聽從性日誌、產品目錄或者搜索索引,對於此類的應用而言,數據存儲的最終一致性是徹底可接受的。但對於其餘一些場景,好比銀行事務、航班預訂或者股票市場而言,即便是毫秒級的不一致性,也將損害用戶體驗或者系統可信性。

 

在這樣的狀況下,值得評估一下多活的數據存儲方案是否符合用戶場景的須要。

 

  1. 本地讀取-全局寫入的方式提供了可用性和一致性之間的平衡,是一種可選的方案。在對某個可用域的主副本數據存儲進行寫入操做的同時,會在其餘可用域生成只讀副本。當主副本數據發生單點失敗問題的時候,能夠在其餘可用域中選擇一個新的主副本,從而實現快速的故障恢復,這種方式所提供的可用性,對於不少場景來講是可接受的。

  2. 另外一種方式是分片寫入或者分區寫入,這將使得可用域中某一份單獨的數據存儲成爲一部分數據的主副本。

 

一旦決定採用多活的數據存儲方案,而且接納了最終一致性的理念,接下來須要重點考慮的就是採用合適的技術,以緩解和減輕一致性問題所產生的影響。

 

採用事件流進行復制

 

在多活架構下,數據的異步複製一般採用事件流的方式實現。好處以下:

 

  1. 寫入操做的順序將得以保留。這對不少用戶場景來講是必須的。

  2. 對於寫入失敗或者存儲不可用的狀況,事件複製器將持續的嘗試對副本數據的寫入操做直到成功,以保證故障能夠被恢復。

 

這一方案的挑戰在於,如何讓事件複製器處於高可用的狀態。這須要在順序複製和並行複製之間作出設計上的權衡。

 

寫入前的業務驗證

 

在數據複製的過程當中,複製器沒有辦法知道寫入的發起者是誰,但寫入自己可能存在不一致性或者錯誤的參數。爲了不寫入的過程對業務邏輯形成不可挽回的錯誤(尤爲是在事件的順序相當重要的場合),複製過程將被阻塞,直到當前的事件已經成功處理,纔會繼續進行下一項操做。

 

所以,在寫入前進行足夠的業務驗證是十分必要的,這將避免狀況變得難以收拾。一些與網絡或者與系統的不可用有關的問題,都是可恢復的,複製器能夠用重試的方式對此類狀況進行處理。

 

更新操做帶來的問題

 

考慮以下的業務場景:顧客在創建訂單後進行了更新或者取消操做。

 

  • 步驟1:生成訂單 

  • 步驟2:更新訂單

 

在這種狀況下,對於第2步操做來講,應用一般須要獲取訂單的信息(讀),而且更新訂單的狀態(寫)。這是全部訂單系統的通用場景,如訂票、生產製造、貿易或者零售。

 

在最終一致性的架構下,若是步驟1和步驟2分屬於不一樣的可用域,這一般會引起一些問題。好比,當步驟1沒能及時的複製到步驟2所在的可用域的時候,步驟2的更新操做就可能失敗。

 

更新操做帶來的問題示意

 

以上的場景帶來的不僅是用戶體驗的不理想,更重要的是分引起數據不一致的問題:一種狀況是,步驟2的更新的操做可能基於一份過期的數據,甚至步驟1的複製操做覆蓋了步驟2的更新操做。

 

事件與狀態

 

在上面的例子中,考慮了兩個事件:訂單生成和訂單更新。假定接下來又發生了兩個事件:訂單更新和訂單取消。

 

大多數的數據存儲方案會將全部這些事件存儲在一個歷史實體、審計實體或者細節實體中,用以表徵單獨的事件。咱們稱之爲「事件實體」。

 

在不少狀況下,訂單的當前狀態也會被記錄,如「已取消」。咱們稱之爲「狀態實體」。

 

對於每個新的事件而言,有兩個必不可少的操做,對事件實體的插入操做,和對狀態實體的更新操做。

 

訂單事件示意

 

沒有狀態實體,咱們就須要去彙總全部的事件或者獲取最新的事件來獲知訂單的狀態。

 

在有些狀況下,數據存儲僅支持插入而不支持更新。這樣就只有事件實體而沒有狀態實體。這是否有助於補救更新操做所產生的問題呢?徹底不會。尤爲是在上文提到的讀取過期數據的情形下。固然,當複製器之間或者服務寫入之間發生衝突的時候,這確實有助於確保數據不會被覆蓋。

 

在此情形下,寫入操做的「只插」策略,或者事件溯源的設計方法將很實用。

 

粘滯會話

 

在上文所提到的更新問題中,插入、讀取和更新操做分屬於不一樣的可用域,但卻共享一個相同的上下文(如訂單ID),只有在這種狀況纔會發生問題。雖然在理想狀況下,服務請求應該是無狀態的,但若是與某一個上下文(如用戶或者訂單)有關的狀態能夠用Session、Cookie或規則的方式保存在流量管理器中,就能夠確保在多數狀況下,與某一個特定的上下文有關的一組事件能夠被路由到一個共同的可用域。這種程度的會話粘滯或者會話親和性能夠極大的改善數據過期的問題。

 

衝突解決方案

 

做爲預防性措施,業務驗證、會話粘滯或者事件溯源能夠在很大程度上緩解相關的風險,讓本地讀寫多活方案的實現更加健壯。然而,衝突是不可避免的。對於一些場景,尤爲是高頻的副本複製的場景下,須要認真考慮衝突的解決方案。

 

一些衝突解決方案包括:

 

  1. 基於時間戳的解決方案:好比,以最新寫入的爲準

  2. 基於規則的解決方案:好比,在進行副本複製的過程當中,若是符合某種特定條件,則忽略事件的寫入。

 

雖然多數衝突解決方案能夠自動執行,對於一些特定的場景,咱們仍舊須要創建一個可視化的用戶界面來手動的解決衝突問題。

 

結語

 

跨可用域的本地讀寫的多活實現是一項複雜的的任務,一般須要在應用的數據層之外解決不少問題。這是一種很難實現和治理的模型,僅在低延遲和高可用性不可或缺的場景下才須要考慮。仔細的分析和規劃相關的設計考量、妥協和治理要素,將有助於達成最優的解決方案。同時,也須要考慮採用節流方法來理解延遲和可用性之間的平衡。

 

譯註:節流方法(throttled approach):相似機場或者地鐵的安檢方式,當流量較大的時候,一部分人會被滯留在一個等待區內,直到前面的一批已經安全順利經過。

 

關於EAWorld:微服務,DevOps,數據治理,移動架構原創技術分享,長按二維碼關注

相關文章
相關標籤/搜索