本地讀寫的多活資料儲存架構設計要義

EAWorld發表於2019-06-04

本地讀寫的多活資料儲存架構設計要義

本文由公眾號EAWorld翻譯發表,轉載需註明出處。

作者:Parashar Borkotoky 

譯者:白小白 

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

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

本地讀寫的多活資料儲存架構設計要義

本地讀寫多活示例


本地讀-本地寫的多活資料儲存架構是最難實現的資料模式之一。這一模式也常被稱為“雙活”或者“多主”,對於不同行業大容量低延遲的事務類應用而言,這是一種必備的能力。

系統的整體可用性取決於單獨元件的可用性。使用者介面層、服務層和訊息層可以跨分割槽/跨地域進行橫向擴充套件以提供高可用性,但對於事務性資料儲存(尤其是寫操作)而言,採用同樣的處理方案仍舊充滿挑戰。無論部署在本地或者雲端,很多關係型資料庫和noSQL資料庫都提供了這種開箱即用的能力。也有一些企業選擇自己實現定製化的複製方案來達成多活的目標,對於強烈依賴低延遲的使用者尤其如此。忽略方案的差異性,人們需要對一些通用的風險與權衡進行仔細考量。

同步複製與非同步複製

首當其衝需要考慮的就是在跨可用域的資料複製過程中,是採用同步複製還是非同步複製的方案。

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

  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,資料治理,移動架構原創技術分享

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562043/viewspace-2646749/,如需轉載,請註明出處,否則將追究法律責任。

相關文章