天天看點

如何保障微服務架構下的資料一緻性?

随着微服務架構的推廣,越來越多的公司采用微服務架構來建構自己的業務平台。就像前邊的文章說的,微服務架構為業務開發帶來了諸多好處的同時,例如單一職責、獨立開發部署、功能複用和系統容錯等等,也帶來一些問題。

例如上手難度變大,運維變得更複雜,子產品之間的依賴關系更複雜,資料一緻性難以保證,等等。但是辦法總是比問題多,本篇文章就來介紹一下我們是如何保障微服務架構的資料一緻性的。

以電商平台為例,當使用者下單并支付後,系統需要修改訂單的狀态并且增加使用者積分。由于系統采用的是微服務架構,分離出了支付服務、訂單服務和積分服務,每個服務都有獨立資料庫做資料存儲。當使用者支付成功後,無論是修改訂單狀态失敗還是增加積分失敗,都會造成資料的不一緻。

如何保障微服務架構下的資料一緻性?

為了解決例子中的資料一緻性問題,一個最直接的辦法就是考慮資料的 強一緻性。那麼如何保證資料的強一緻性呢?我們從關系型資料庫的 ACID 理論說起。

關系型資料庫具有解決複雜事務場景的能力,關系型資料庫的事務滿足 ACID 的特性。

Atomicity:原子性(要麼都做,要麼都不做)

Consistency:一緻性(資料庫隻有一個狀态,不存在未确定狀态)

Isolation:隔離性(事務之間互不幹擾)

Durability: 永久性(事務一旦送出,資料庫記錄永久不變)

具有 ACID 特性的資料庫支援資料的強一緻性,保證了資料本身不會出現不一緻。

然而微服務架構下,每個微服務都有自己的資料庫,導緻微服務架構的系統不能簡單地滿足 ACID,我們就需要尋找微服務架構下的資料一緻性解決方案。

微服務架構的系統本身是一種分布式系統,而本文讨論的問題其實也就是分布式事務之資料一緻性的問題,我們來聊聊分布式系統的 CAP 理論和 BASE 理論。

CAP 是指在一個分布式系統下, 包含三個要素:Consistency(一緻性)、Availability(可用性)、Partition tolerance(分區容錯性),并且三者不可得兼。

C:Consistency,一緻性,所有資料變動都是同步的。

A:Availability,可用性,即在可以接受的時間範圍内正确地響應使用者請求。

P:Partition tolerance,分區容錯性,即某節點或網絡分區故障時,系統仍能夠提供滿足一緻性和可用性的服務。

關系型資料庫單節點保證了資料強一緻性(C)和可用性(A),但是卻無法保證分區容錯性(P)。

然而在分布式系統下,為了保證子產品的分區容錯性(P),隻能在資料強一緻性(C)和可用性(A)之間做平衡。具體表現為在一定時間内,可能子產品之間資料是不一緻的,但是通過自動或手動補償後能夠達到最終的一緻。

BASE 理論主要是解決 CAP 理論中分布式系統的可用性和一緻性不可兼得的問題。BASE 理論包含以下三個要素:

BA:Basically Available,基本可用。

S:Soft State,軟狀态,狀态可以有一段時間不同步。

E:Eventually Consistent,最終一緻,最終資料是一緻的就可以了,而不是時時保持強一緻。

BASE 模型與 ACID 不同,滿足 CAP 理論,通過犧牲強一緻性來保證系統可用性。由于犧牲了強一緻性,系統在處理請求的過程中,資料可以存在短時的不一緻。

系統在處理業務時,記錄每一步的臨時狀态。當出現異常時,根據狀态判斷是否繼續處理請求或者退回原始狀态,進而達到資料的最終一緻。

例如,在上面的案例中,支付成功,訂單也成功,但增加積分失敗,此時,不應復原支付和訂單,而應通過一些補償方法來讓積分得以正确地增加。後面會講到具體的實作方法。

在分享我們的分布式事務實踐方案之前,先看看早期解決分布式事務問題的二階段送出協定。

X/Open DTP(Distributed Transaction Process)是一個分布式事務模型,此模型主要使用二階段送出(2PC,Two-Phase-Commit)來保證分布式事務的完整性。在這個模型裡面,有三個角色:

AP:Application,應用程式,業務層。

RM:Resource Manager,資料總管,關系型資料庫或支援 XA 接口(XA 規範是 X/Open 組織定義的分布式事務規範)的元件。

TM: Transaction Manager ,事務管理器,負責各個 RM 的送出和復原。

當應用程式(AP)調用了事務管理器(TM)的送出方法時,事務的送出分為兩個階段實行。

如何保障微服務架構下的資料一緻性?

TM 通知所有參與事務的各個 RM,給每個 RM 發送 prepare 消息。

RM 接收到消息後進入準備階段後,要麼直接傳回失敗,要麼建立并執行本地事務,寫本地事務日志(redo 和 undo 日志),但是不送出(此處隻保留最後一步耗時最少的送出操作給第二階段執行)。

如何保障微服務架構下的資料一緻性?

TM 收到 RM 準備階段的失敗消息或者擷取 RM 傳回消息逾時,則直接給 RM 發送復原(rollback)消息,否則發送送出(commit)消息。

RM 根據 TM 的指令執行送出或者復原,執行完成後釋放所有事務處理過程中使用的鎖(最後階段釋放鎖)。

優點

2PC 提供了一套完整的分布式事務的解決方案,遵循事務嚴格的 ACID 特性。

缺點

TM 通過 XA 接口與各個 RM 之間進行資料互動,從第一階段的準備階段,業務所涉及的資料就被鎖定,并且鎖定跨越整個送出流程。在高并發和涉及業務子產品較多的情況下對資料庫的性能影響較大。

二階段是反可伸縮模式的,業務規模越大,涉及子產品越多,局限性越大,系統可伸縮性越差。

在技術棧比較雜的分布式應用中,存儲元件有很多不支援 XA 協定。

二階段的諸多弊端,導緻分布式系統下無法直接使用此方案來解決資料一緻性問題,但它提供了解決分布式系統下資料一緻性問題的思路。

下面就通過案例來分享我們是如何保證微服務架構的資料一緻性的。

可靠消息最終一緻性方案本質上是利用 MQ 元件實作的二階段送出。此方案涉及 3 個子產品:

上遊應用,執行業務并發送 MQ 消息。

可靠消息服務和 MQ 消息元件,協調上下遊消息的傳遞,并確定上下遊資料的一緻性。

下遊應用,監聽 MQ 的消息并執行自身業務。

如何保障微服務架構下的資料一緻性?

上遊應用将本地業務執行和消息發送綁定在同一個本地事務中,保證要麼本地操作成功并發送 MQ 消息,要麼兩步操作都失敗并復原。

上遊應用和可靠消息之間的業務互動圖如下:

如何保障微服務架構下的資料一緻性?

上遊應用發送待确認消息到可靠消息系統

可靠消息系統儲存待确認消息并傳回

上遊應用執行本地業務

上遊應用通知可靠消息系統确認業務已執行并發送消息。

可靠消息系統修改消息狀态為發送狀态并将消息投遞到 MQ 中間件。

以上每一步都可能出現失敗情況,分析一下這 5 步出現異常後上遊業務和消息發送是否一緻:

如何保障微服務架構下的資料一緻性?

上遊應用執行完成,下遊應用尚未執行或執行失敗時,此事務即處于 BASE 理論的 Soft State 狀态。

下遊應用監聽 MQ 消息并執行業務,并且将消息的消費結果通知可靠消息服務。

可靠消息的狀态需要和下遊應用的業務執行保持一緻,可靠消息狀态不是已完成時,確定下遊應用未執行,可靠消息狀态是已完成時,確定下遊應用已執行。

下遊應用和可靠消息服務之間的互動圖如下:

如何保障微服務架構下的資料一緻性?

下遊應用監聽 MQ 消息元件并擷取消息

下遊應用根據 MQ 消息體資訊處理本地業務

下遊應用向 MQ 元件自動發送 ACK 确認消息被消費

下遊應用通知可靠消息系統消息被成功消費,可靠消息将該消息狀态更改為已完成。

以上每一步都可能出現失敗情況,分析一下這 4 步出現異常後下遊業務和消息狀态是否一緻:

如何保障微服務架構下的資料一緻性?

通過分析以上兩個階段可能失敗的情況,為了確定上下遊資料的最終一緻性,在可靠消息系統中,需要開發消息狀态确認和消息重發兩個功能以實作 BASE 理論的 Eventually Consistent 特性。

可靠消息服務定時監聽消息的狀态,如果存在狀态為待确認并且逾時的消息,則表示上遊應用和可靠消息互動中的步驟 4 或者 5 出現異常。

可靠消息則攜帶消息體内的資訊向上遊應用發起請求查詢該業務是否已執行。上遊應用提供一個可查詢接口供可靠消息追溯業務執行狀态,如果業務執行成功則更改消息狀态為已發送,否則删除此消息確定資料一緻。具體流程如下:

如何保障微服務架構下的資料一緻性?

可靠消息查詢逾時的待确認狀态的消息

向上遊應用查詢業務執行的情況

業務未執行,則删除該消息,保證業務和可靠消息服務的一緻性。業務已執行,則修改消息狀态為已發送,并發送消息到 MQ 元件。

消息已發送則表示上遊應用已經執行,接下來則確定下遊應用也能正常執行。

可靠消息服務發現可靠消息服務中存在消息狀态為已發送并且逾時的消息,則表示可靠消息服務和下遊應用中存在異常的步驟,無論哪個步驟出現異常,可靠消息服務都将此消息重新投遞到 MQ 元件中供下遊應用監聽。

下遊應用監聽到此消息後,在保證幂等性的情況下重新執行業務并通知可靠消息服務此消息已經成功消費,最終確定上遊應用、下遊應用的資料最終一緻性。具體流程如下:

如何保障微服務架構下的資料一緻性?

可靠消息服務定時查詢狀态為已發送并逾時的消息

可靠消息将消息重新投遞到 MQ 元件中

下遊應用監聽消息,在滿足幂等性的條件下,重新執行業務。

下遊應用通知可靠消息服務該消息已經成功消費。

通過消息狀态确認和消息重發兩個功能,可以確定上遊應用、可靠消息服務和下遊應用資料的最終一緻性。

當然在實際接入過程中,需要引入人工幹預功能。比如引入重發次數限制,超過重發次數限制的将消息修改為死亡消息,等待人工幹預。

代入開篇案例,通過可靠消息最終一緻性方案,第一階段,訂單狀态更改之前,訂單服務向可靠消息服務請求儲存待确認消息。可靠消息服務儲存消息并傳回。

訂單服務接收到傳回資訊後執行本地業務并通知可靠消息服務業務已執行。消息服務更改消息狀态并将消息投遞到 MQ 中間件。

第二階段,積分系統監聽到 MQ 消息,檢視積分是否已增加,如果沒有增加則修改積分,然後請求可靠消息服務。可靠消息服務接收到積分系統的請求,将消息狀态更改為已完成。

到這裡,已經介紹完如何通過可靠消息服務來保證資料的一緻性。但由于引入了可靠消息服務和消息隊列,帶來了一定的複雜性,是以,它更适用于跨平台技術棧不統一的場景。

下面再來介紹在技術棧統一的情況下,如何通過 TCC 來解決資料一緻的方法。

TCC 方案是二階段送出的另一種實作方式,它涉及 3 個子產品,主業務、從業務和活動管理器(協作者)。

下面這張圖是網際網路上關于 TCC 比較經典的圖示:

如何保障微服務架構下的資料一緻性?

第一階段:主業務服務分别調用所有從業務服務的 try 操作,并在活動管理器中記錄所有從業務服務。當所有從業務服務 try 成功或者某個從業務服務 try 失敗時,進入第二階段。

第二階段:活動管理器根據第一階段從業務服務的 try 結果來執行 confirm 或 cancel 操作。如果第一階段所有從業務服務都 try 成功,則協作者調用所有從業務服務的 confirm 操作,否則,調用所有從業務服務的 cancel 操作。

在第二階段中,confirm 和 cancel 同樣存在失敗情況,是以需要對這兩種情況做 異常處理 以保證資料一緻性。

Confirm 失敗:則復原所有 confirm 操作并執行 cancel 操作。

Cancel 失敗:從業務服務需要提供自動 cancel 機制,以保證 cancel 成功。

目前有很多基于 RPC 的 TCC 架構,但是不适用于微服務架構下基于 HTTP 協定的互動模式。我們這次隻讨論基于 HTTP 協定的 TCC 實作。具體的實作流程如下:

如何保障微服務架構下的資料一緻性?

主業務服務調用從業務服務的 try 操作,并擷取 confirm/cancel 接口和逾時時間。

如果從業務都 try 成功,主業務服務執行本地業務,并将擷取的 confirm/cancel 接口發送給活動管理器,活動管理器會順序調用從業務 1 和從業務 2 的 confirm 接口并記錄請求狀态,如果請求成功,則通知主業務服務送出本地事務。如果 confirm 部分失敗,則活動管理器會順序調用從業務 1 和從業務 2 的 cancel 接口來取消 try 的操作。

如果從業務部分或全部 try 失敗,則主業務直接復原并結束,而 try 成功的從業務服務則通過定時任務來處理處于 try 完成但逾時的資料,将這些資料做復原處理保證主業務服務和從業務服務的資料一緻。

代入開篇提到的案例,通過 TCC 方案,訂單服務在訂單狀态修改之前執行預增積分操作(try),并從積分服務擷取 confirm/cancel 預增積分的請求位址。

如果預增積分(try)成功,則訂單服務更改訂單狀态并通知活動管理器,活動管理器請求積分子產品的 confirm 接口來增加積分。

如果預增積分(try)失敗,則訂單服務業務復原。積分服務通過定時任務删除預增積分(try)逾時的資料。

另外如果活動管理器調用積分服務的 confirm 接口失敗,則活動管理器調用積分服務 cancel 接口來取消預增積分,進而,保證訂單和積分資料的最終一緻性。

通過上面的對可靠消息服務和 TCC 方案的描述,我們解決了技術棧一緻和不一緻的兩種情況下的資料一緻性問題。

但是,通常在這些核心業務上有很多附加業務,比如當使用者支付完成後,需要通過短信通知使用者支付成功。

這一類業務的成功或者失敗不會影響核心業務,甚至很多大型網際網路平台在并高并發的情況下會主動關閉這一類業務以保證核心業務的順利執行。那麼怎麼處理這類情況呢,我們來看看最大努力通知方案。

最大努力通知方案涉及三個子產品:

上遊應用,發消息到 MQ 隊列。

下遊應用(例如短信服務、郵件服務),接受請求,并傳回通知結果。

最大努力通知服務,監聽消息隊列,将消息存儲到資料庫中,并按照通知規則調用下遊應用的發送通知接口。

具體流程如下:

如何保障微服務架構下的資料一緻性?

上遊應用發送 MQ 消息到 MQ 元件内,消息内包含通知規則和通知位址

最大努力通知服務監聽到 MQ 内的消息,解析通知規則并放入延時隊列等待觸發通知

最大努力通知服務調用下遊的通知位址,如果調用成功,則該消息标記為通知成功,如果失敗則在滿足通知規則(例如 5 分鐘發一次,共發送 10 次)的情況下重新放入延時隊列等待下次觸發。

最大努力通知服務表示在不影響主業務的情況下,盡可能地確定資料的一緻性。它需要開發人員根據業務來指定通知規則,在滿足通知規則的前提下,盡可能的確定資料的一緻,以盡到最大努力的目的。

根據不同的業務可以定制不同的通知規則,比如通知支付結果等相對嚴謹的業務,可以将通知頻率設定高一些,通知時間長一些,比如隔 5 分鐘通知一次,持續時間 1 小時。

如果不重要的業務,比如通知使用者積分增加,則可以将通知頻率設定低一些,時間短一些,比如 10 分鐘通知一次,持續 30 分鐘。

代入上面提到的支付成功短信通知使用者的案例,通過最大努力通知方案,當支付成功後,将消息發送到 MQ 中間件,在消息中,定義發送規則為 5 分鐘一次,最大發送數為 10 次。

最大努力通知服務監聽 MQ 消息并根據規則調用消息通知服務(短信服務)的消息發送接口,并記錄每次調用的日志資訊。在通知成功或者已通知 10 次時,停止通知。

上面通過案例詳細介紹了我們解決微服務之間資料不一緻問題的三種方案,下面通過一張簡單的對比圖,為大家選擇合适的解決方案提供簡單依據。

如何保障微服務架構下的資料一緻性?

需求決定架構,這也是微服務大火的原因,越是大型架構其優勢愈發明顯,靈活高效的部署方式和高可擴充,但它也有一些小的缺點,如果你在猶豫是否投入使用,不如來 QCon 上海 2017 看看 Amazon 作為這一領域的先驅在内部系統架構是如何建構和演進的。除此之外,還有 Google、Twitter、Intel 等公司的架構實踐。

如何保障微服務架構下的資料一緻性?

來源:infoQ微信公衆号

♥ 作者:明志健緻遠

♠ 出處:http://www.cnblogs.com/study-everyday/

♦ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

♣ 本部落格大多為學習筆記或讀書筆記,本文如對您有幫助,還請多推薦下此文,如有錯誤歡迎指正,互相學習,共同進步。

繼續閱讀