1. 程式人生 > >分散式系統資料一致性的6種方案(轉)

分散式系統資料一致性的6種方案(轉)

                                                                                問題的起源


在電商等業務中,系統一般由多個獨立的服務組成,如何解決分散式呼叫時候資料的一致性? 

具體業務場景如下,比如一個業務操作,如果同時呼叫服務 A、B、C,需要滿足要麼同時成功;要麼同時失敗。A、B、C 可能是多個不同部門開發、部署在不同伺服器上的遠端服務。

在分散式系統來說,如果不想犧牲一致性,CAP 理論告訴我們只能放棄可用性,這顯然不能接受。為了便於討論問題,先簡單介紹下資料一致性的基礎理論。

強一致

當更新操作完成之後,任何多個後續程序或者執行緒的訪問都會返回最新的更新過的值。這種是對使用者最友好的,就是使用者上一次寫什麼,下一次就保證能讀到什麼。根據 CAP 理論,這種實現需要犧牲可用性。

弱一致性

系統並不保證續程序或者執行緒的訪問都會返回最新的更新過的值。系統在資料寫入成功之後,不承諾立即可以讀到最新寫入的值,也不會具體的承諾多久之後可以讀到。

最終一致性

弱一致性的特定形式。系統保證在沒有後續更新的前提下,系統最終返回上一次更新操作的值。在沒有故障發生的前提下,不一致視窗的時間主要受通訊延遲,系統負載和複製副本的個數影響。DNS 是一個典型的最終一致性系統。

在工程實踐上,為了保障系統的可用性,網際網路系統大多將強一致性需求轉換成最終一致性的需求,並通過系統執行冪等性的保證,保證資料的最終一致性。但在電商等場景中,對於資料一致性的解決方法和常見的網際網路系統(如 MySQL 主從同步)又有一定區別,群友的討論分成以下 6 種解決方案。

1. 規避分散式事務——業務整合

業務整合方案主要採用將介面整合到本地執行的方法。拿問題場景來說,則可以將服務 A、B、C 整合為一個服務 D 給業務,這個服務 D 再通過轉換為本地事務的方式,比如服務 D 包含本地服務和服務 E,而服務 E 是本地服務 A ~ C 的整合。

優點:解決(規避)了分散式事務。

缺點:顯而易見,把本來規劃拆分好的業務,又耦合到了一起,業務職責不清晰,不利於維護。

由於這個方法存在明顯缺點,通常不建議使用。

2. 經典方案 - eBay 模式


此方案的核心是將需要分散式處理的任務通過訊息日誌的方式來非同步執行。訊息日誌可以儲存到本地文字、資料庫或訊息佇列,再通過業務規則自動或人工發起重試。人工重試更多的是應用於支付場景,通過對賬系統對事後問題的處理。

訊息日誌方案的核心是保證服務介面的冪等性。

考慮到網路通訊失敗、資料丟包等原因,如果介面不能保證冪等性,資料的唯一性將很難保證。

eBay 方式的主要思路如下。

Base:一種 Acid 的替代方案

此方案是 eBay 的架構師 Dan Pritchett 在 2008 年發表給 ACM 的文章,是一篇解釋 BASE 原則,或者說最終一致性的經典文章。文中討論了 BASE 與 ACID 原則在保證資料一致性的基本差異。

如果 ACID 為分割槽的資料庫提供一致性的選擇,那麼如何實現可用性呢?答案是

BASE (basically available, soft state, eventually consistent)

BASE 的可用性是通過支援區域性故障而不是系統全域性故障來實現的。下面是一個簡單的例子:如果將使用者分割槽在 5 個數據庫伺服器上,BASE 設計鼓勵類似的處理方式,一個使用者資料庫的故障隻影響這臺特定主機那 20% 的使用者。這裡不涉及任何魔法,不過它確實可以帶來更高的可感知的系統可用性。

文章中描述了一個最常見的場景,如果產生了一筆交易,需要在交易表增加記錄,同時還要修改使用者表的金額。這兩個表屬於不同的遠端服務,所以就涉及到分散式事務一致性的問題。

文中提出了一個經典的解決方法,將主要修改操作以及更新使用者表的訊息放在一個本地事務來完成。同時為了避免重複消費使用者表訊息帶來的問題,達到多次重試的冪等性,增加一個更新記錄表 updates_applied 來記錄已經處理過的訊息。

系統的執行虛擬碼如下

(點選可全屏縮放圖片)

基於以上方法,在第一階段,通過本地的資料庫的事務保障,增加了 transaction 表及訊息佇列 。

在第二階段,分別讀出訊息佇列(但不刪除),通過判斷更新記錄表 updates_applied 來檢測相關記錄是否被執行,未被執行的記錄會修改 user 表,然後增加一條操作記錄到 updates_applied,事務執行成功之後再刪除佇列。

通過以上方法,達到了分散式系統的最終一致性。進一步瞭解 eBay 的方案可以參考文末連結。

3. 去哪兒網分散式事務方案

隨著業務規模不斷地擴大,電商網站一般都要面臨拆分之路。就是將原來一個單體應用拆分成多個不同職責的子系統。比如以前可能將面向使用者、客戶和運營的功能都放在一個系統裡,現在拆分為訂單中心、代理商管理、運營系統、報價中心、庫存管理等多個子系統。

拆分首先要面臨的是什麼呢?

最開始的單體應用所有功能都在一起,儲存也在一起。比如運營要取消某個訂單,那直接去更新訂單表狀態,然後更新庫存表就 ok 了。因為是單體應用,庫在一起,這些都可以在一個事務裡,由關係資料庫來保證一致性。

但拆分之後就不同了,不同的子系統都有自己的儲存。比如訂單中心就只管理自己的訂單庫,而庫存管理也有自己的庫。那麼運營系統取消訂單的時候就是通過介面呼叫等方式來呼叫訂單中心和庫存管理的服務了,而不是直接去操作庫。這就涉及一個『分散式事務』的問題。

分散式事務有兩種解決方式

1. 優先使用非同步訊息。

上文已經說過,使用非同步訊息 Consumer 端需要實現冪等。

冪等有兩種方式,一種方式是業務邏輯保證冪等。比如接到支付成功的訊息訂單狀態變成支付完成,如果當前狀態是支付完成,則再收到一個支付成功的訊息則說明訊息重複了,直接作為訊息成功處理。

另外一種方式如果業務邏輯無法保證冪等,則要增加一個去重表或者類似的實現。對於 producer 端在業務資料庫的同例項上放一個訊息庫,發訊息和業務操作在同一個本地事務裡。發訊息的時候訊息並不立即發出,而是向訊息庫插入一條訊息記錄,然後在事務提交的時候再非同步將訊息發出,傳送訊息如果成功則將訊息庫裡的訊息刪除,如果遇到訊息佇列服務異常或網路問題,訊息沒有成功發出那麼訊息就留在這裡了,會有另外一個服務不斷地將這些訊息掃出重新發送。

2. 有的業務不適合非同步訊息的方式,事務的各個參與方都需要同步的得到結果。這種情況的實現方式其實和上面類似,每個參與方的本地業務庫的同例項上面放一個事務記錄庫。

比如 A 同步呼叫 B,C。A 本地事務成功的時候更新本地事務記錄狀態,B 和 C 同樣。如果有一次 A 呼叫 B 失敗了,這個失敗可能是 B 真的失敗了,也可能是呼叫超時,實際 B 成功。則由一箇中心服務對比三方的事務記錄表,做一個最終決定。假設現在三方的事務記錄是 A 成功,B 失敗,C 成功。那麼最終決定有兩種方式,根據具體場景:

  1. 重試 B,直到 B 成功,事務記錄表裡記錄了各項呼叫引數等資訊;

  2. 執行 A 和 B 的補償操作(一種可行的補償方式是回滾)。

對 b 場景做一個特殊說明:比如 B 是扣庫存服務,在第一次呼叫的時候因為某種原因失敗了,但是重試的時候庫存已經變為 0,無法重試成功,這個時候只有回滾 A 和 C 了。

那麼可能有人覺得在業務庫的同例項裡放訊息庫或事務記錄庫,會對業務侵入,業務還要關心這個庫,是否一個合理的設計?

實際上可以依靠運維的手段來簡化開發的侵入,我們的方法是讓 DBA 在公司所有 MySQL 例項上預初始化這個庫,通過框架層(訊息的客戶端或事務 RPC 框架)透明的在背後操作這個庫,業務開發人員只需要關心自己的業務邏輯,不需要直接訪問這個庫。

總結起來,其實兩種方式的根本原理是類似的,也就是將分散式事務轉換為多個本地事務,然後依靠重試等方式達到最終一致性

4. 蘑菇街交易建立過程中的分散式一致性方案

交易建立的一般性流程

我們把交易建立流程抽象出一系列可擴充套件的功能點,每個功能點都可以有多個實現(具體的實現之間有組合/互斥關係)。把各個功能點按照一定流程串起來,就完成了交易建立的過程。

面臨的問題

每個功能點的實現都可能會依賴外部服務。那麼如何保證各個服務之間的資料是一致的呢?比如鎖定優惠券服務呼叫超時了,不能確定到底有沒有鎖券成功,該如何處理?再比如鎖券成功了,但是扣減庫存失敗了,該如何處理?

方案選型

服務依賴過多,會帶來管理複雜性增加和穩定性風險增大的問題。試想如果我們強依賴 10 個服務,9 個都執行成功了,最後一個執行失敗了,那麼是不是前面 9 個都要回滾掉?這個成本還是非常高的。

所以在拆分大的流程為多個小的本地事務的前提下,對於非實時、非強一致性的關聯業務寫入,在本地事務執行成功後,我們選擇發訊息通知、關聯事務非同步化執行的方案。

訊息通知往往不能保證 100% 成功;且訊息通知後,接收方業務是否能執行成功還是未知數。前者問題可以通過重試解決;後者可以選用事務訊息來保證。

但是事務訊息框架本身會給業務程式碼帶來侵入性和複雜性,所以我們選擇基於 DB 事件變化通知到 MQ 的方式做系統間解耦,通過訂閱方消費 MQ 訊息時的 ACK 機制,保證訊息一定消費成功,達到最終一致性。由於訊息可能會被重發,訊息訂閱方業務邏輯處理要做好冪等保證。

所以目前只剩下需要實時同步做、有強一致性要求的業務場景了。在交易建立過程中,鎖券和扣減庫存是這樣的兩個典型場景。

要保證多個系統間資料一致,乍一看,必須要引入分散式事務框架才能解決。但引入非常重的類似二階段提交分散式事務框架會帶來複雜性的急劇上升;在電商領域,絕對的強一致是過於理想化的,我們可以選擇準實時的最終一致性。

我們在交易建立流程中,首先建立一個不可見訂單,然後在同步呼叫鎖券和扣減庫存時,針對呼叫異常(失敗或者超時),發出廢單訊息到MQ。如果訊息傳送失敗,本地會做時間階梯式的非同步重試;優惠券系統和庫存系統收到訊息後,會進行判斷是否需要做業務回滾,這樣就準實時地保證了多個本地事務的最終一致性。

 5. 支付寶及螞蟻金融雲的分散式服務 DTS 方案

業界常用的還有支付寶的一種 xts 方案,由支付寶在 2PC 的基礎上改進而來。主要思路如下,大部分資訊引用自官方網站。

分散式事務服務簡介

分散式事務服務 (Distributed Transaction Service, DTS) 是一個分散式事務框架,用來保障在大規模分散式環境下事務的最終一致性。DTS 從架構上分為 xts-client 和 xts-server 兩部分,前者是一個嵌入客戶端應用的 JAR 包,主要負責事務資料的寫入和處理;後者是一個獨立的系統,主要負責異常事務的恢復。

核心特性

傳統關係型資料庫的事務模型必須遵守 ACID 原則。在單資料庫模式下,ACID 模型能有效保障資料的完整性,但是在大規模分散式環境下,一個業務往往會跨越多個數據庫,如何保證這多個數據庫之間的資料一致性,需要其他行之有效的策略。在 JavaEE 規範中使用 2PC (2 Phase Commit, 兩階段提交) 來處理跨 DB 環境下的事務問題,但是 2PC 是反可伸縮模式,也就是說,在事務處理過程中,參與者需要一直持有資源直到整個分散式事務結束。這樣,當業務規模達到千萬級以上時,2PC 的侷限性就越來越明顯,系統可伸縮性會變得很差。基於此,我們採用 BASE 的思想實現了一套類似 2PC 的分散式事務方案,這就是 DTS。DTS在充分保障分散式環境下高可用性、高可靠性的同時兼顧資料一致性的要求,其最大的特點是保證資料最終一致 (Eventually consistent)。

簡單的說,DTS 框架有如下特性:

  • 最終一致:事務處理過程中,會有短暫不一致的情況,但通過恢復系統,可以讓事務的資料達到最終一致的目標。

  • 協議簡單:DTS 定義了類似 2PC 的標準兩階段介面,業務系統只需要實現對應的介面就可以使用 DTS 的事務功能。

  • 與 RPC 服務協議無關:在 SOA 架構下,一個或多個 DB 操作往往被包裝成一個一個的 Service,Service 與 Service 之間通過 RPC 協議通訊。DTS 框架構建在 SOA 架構上,與底層協議無關。

  • 與底層事務實現無關: DTS 是一個抽象的基於 Service 層的概念,與底層事務實現無關,也就是說在 DTS 的範圍內,無論是關係型資料庫 MySQL,Oracle,還是 KV 儲存 MemCache,或者列存資料庫 HBase,只要將對其的操作包裝成 DTS 的參與者,就可以接入到 DTS 事務範圍內。

以下是分散式事務框架的流程圖

實現

  1. 一個完整的業務活動由一個主業務服務與若干從業務服務組成。

  2. 主業務服務負責發起並完成整個業務活動。

  3. 從業務服務提供 TCC 型業務操作。

  4. 業務活動管理器控制業務活動的一致性,它登記業務活動中的操作,並在活動提交時確認所有的兩階段事務的 confirm 操作,在業務活動取消時呼叫所有兩階段事務的 cancel 操作。”

與 2PC 協議比較

  1. 沒有單獨的 Prepare 階段,降低協議成本

  2. 系統故障容忍度高,恢復簡單

6. 農信網資料一致性方案

1. 電商業務

公司的支付部門,通過接入其它第三方支付系統來提供支付服務給業務部門,支付服務是一個基於 Dubbo 的 RPC 服務。

對於業務部門來說,電商部門的訂單支付,需要呼叫

  1. 支付平臺的支付介面來處理訂單;

  2. 同時需要呼叫積分中心的介面,按照業務規則,給使用者增加積分。

從業務規則上需要同時保證業務資料的實時性和一致性,也就是支付成功必須加積分。

我們採用的方式是同步呼叫,首先處理本地事務業務。考慮到積分業務比較單一且業務影響低於支付,由積分平臺提供增加與回撤介面。

具體的流程是先呼叫積分平臺增加使用者積分,再呼叫支付平臺進行支付處理,如果處理失敗,catch 方法呼叫積分平臺的回撤方法,將本次處理的積分訂單回撤。

(點選圖片可以全屏縮放)

2. 使用者資訊變更

公司的使用者資訊,統一由使用者中心維護,而使用者資訊的變更需要同步給各業務子系統,業務子系統再根據變更內容,處理各自業務。使用者中心作為 MQ 的 producer,新增通知給 MQ。APP Server 訂閱該訊息,同步本地資料資訊,再處理相關業務比如 APP 退出下線等。

我們採用非同步訊息通知機制,目前主要使用 ActiveMQ,基於 Virtual Topic 的訂閱方式,保證單個業務叢集訂閱的單次消費。

總結

分散式服務對衍生的配套系統要求比較多,特別是我們基於訊息、日誌的最終一致性方案,需要考慮訊息的積壓、消費情況、監控、報警等。

參考資料

  • Base: An Acid Alternative (eBay 方案)

In partitioned databases, trading some consistency for availability can lead to dramatic improvements in scalability.

http://queue.acm.org/detail.cfm?id=1394128 英文版

http://article.yeeyan.org/view/167444/125572  中文版

  • 分散式事務服務 (DTS) 

https://www.cloud.alipay.com/docs/middleware/xts/index.html