1. 程式人生 > >不同微服務獨立資料庫,如何保障微服務架構下的資料一致性

不同微服務獨立資料庫,如何保障微服務架構下的資料一致性

雖然已經紅了很久,但是“微服務架構”正變得越來越重要,也將繼續火下去。各個公司與技術人員都在分享微服務架構的相關知識與實踐經驗,但我們發現,目前網上的這些相關文章中,要麼上來就是很有借鑑意義的乾貨,要麼就是以高階的專業術語來講述何為微服務架構。就是沒有一個做到成熟地將技術傳播出來,同時完美地照顧“初入微服務領域人員”,從 0 開始,採用通俗易懂的語言去講解微服務架構的系列。所以,我們邀請青柳雲的蘇槐與 InfoQ 一起共建微服務架構專題“ Re:從 0 開始的微服務架構”,為還沒有入門該領域的技術人員開路,也幫助微服務架構老手溫故知新。

專題文章傳送

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

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

微服務架構的資料一致性問題

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

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

ACID

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

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

  • Consistency:一致性(資料庫只有一個狀態,不存在未確定狀態)

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

  • Durability: 永久性(事務一旦提交,資料庫記錄永久不變)

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

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

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

CAP

CAP 是指在一個分散式系統下, 包含三個要素:Consistency(一致性)、Availability(可用性)、Partition tolerance(分割槽容錯性),並且 三者不可得兼

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

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

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

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

然而在分散式系統下,為了保證模組的分割槽容錯性(P),只能在資料強一致性(C)和可用性(A)之間做平衡。具體表現為在一定時間內,可能模組之間資料是不一致的,但是通過自動或手動補償後能夠達到最終的一致。

BASE

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 訊息,要麼兩步操作都失敗並回滾。

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

  1. 上游應用傳送待確認訊息到可靠訊息系統

  2. 可靠訊息系統儲存待確認訊息並返回

  3. 上游應用執行本地業務

  4. 上游應用通知可靠訊息系統確認業務已執行併發送訊息。

  5. 可靠訊息系統修改訊息狀態為傳送狀態並將訊息投遞到 MQ 中介軟體。

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

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

下游應用監聽 MQ 訊息並執行業務(第二階段)

下游應用監聽 MQ 訊息並執行業務,並且將訊息的消費結果通知可靠訊息服務。

可靠訊息的狀態需要和下游應用的業務執行保持一致,可靠訊息狀態不是已完成時,確保下游應用未執行,可靠訊息狀態是已完成時,確保下游應用已執行。

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

  1. 下游應用監聽 MQ 訊息元件並獲取訊息

  2. 下游應用根據 MQ 訊息體資訊處理本地業務

  3. 下游應用向 MQ 元件自動傳送 ACK 確認訊息被消費

  4. 下游應用通知可靠訊息系統訊息被成功消費,可靠訊息將該訊息狀態更改為已完成。

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

通過分析以上兩個階段可能失敗的情況,為了確保上下游資料的最終一致性,在可靠訊息系統中,需要開發 訊息狀態確認和 訊息重發兩個功能以實現 BASE 理論的 Eventually Consistent特性。

訊息狀態確認

可靠訊息服務定時監聽訊息的狀態,如果存在狀態為待確認並且超時的訊息,則表示上游應用和可靠訊息互動中的步驟 4 或者 5 出現異常。

可靠訊息則攜帶訊息體內的資訊向上遊應用發起請求查詢該業務是否已執行。上游應用提供一個可查詢介面供可靠訊息追溯業務執行狀態,如果業務執行成功則更改訊息狀態為已傳送,否則刪除此訊息確保資料一致。具體流程如下:

  1. 可靠訊息查詢超時的待確認狀態的訊息

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

  3. 業務未執行,則刪除該訊息,保證業務和可靠訊息服務的一致性。業務已執行,則修改訊息狀態為已傳送,併發送訊息到 MQ 元件。

訊息重發

訊息已傳送則表示上游應用已經執行,接下來則確保下游應用也能正常執行。

可靠訊息服務發現可靠訊息服務中存在訊息狀態為已傳送並且超時的訊息,則表示可靠訊息服務和下游應用中存在異常的步驟,無論哪個步驟出現異常,可靠訊息服務都將此訊息重新投遞到 MQ 元件中供下游應用監聽。

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

  1. 可靠訊息服務定時查詢狀態為已傳送並超時的訊息

  2. 可靠訊息將訊息重新投遞到 MQ 元件中

  3. 下游應用監聽訊息,在滿足冪等性的條件下,重新執行業務。

  4. 下游應用通知可靠訊息服務該訊息已經成功消費。

通過訊息狀態確認和訊息重發兩個功能,可以確保上游應用、可靠訊息服務和下游應用資料的最終一致性。

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

代入開篇案例,通過可靠訊息最終一致性方案,第一階段,訂單狀態更改之前,訂單服務向可靠訊息服務請求儲存待確認訊息。可靠訊息服務儲存訊息並返回。

訂單服務接收到返回資訊後執行本地業務並通知可靠訊息服務業務已執行。訊息服務更改訊息狀態並將訊息投遞到 MQ 中介軟體。

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

到這裡,已經介紹完如何通過可靠訊息服務來保證資料的一致性。但由於引入了可靠訊息服務和訊息佇列,帶來了一定的 複雜性,所以,它更 適用於跨平臺技術棧不統一的場景

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

TCC(Try-Confirm-Cancel)

TCC 方案是二階段提交的 另一種實現方式,它涉及 3 個模組,主業務從業務和 活動管理器(協作者)

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

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

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

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

  1. Confirm 失敗:則回滾所有 confirm 操作並執行 cancel 操作。

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

目前有很多基於 RPC 的 TCC 框架,但是不適用於微服務架構下基於 HTTP 協議的互動模式。我們這次只討論基於 HTTP 協議的 TCC 實現。具體的實現流程如下:

  1. 主業務服務呼叫從業務服務的 try 操作,並獲取 confirm/cancel 介面和超時時間。

  2. 如果從業務都 try 成功,主業務服務執行本地業務,並將獲取的 confirm/cancel 介面傳送給活動管理器,活動管理器會順序呼叫從業務 1 和從業務 2 的 confirm 介面並記錄請求狀態,如果請求成功,則通知主業務服務提交本地事務。如果 confirm 部分失敗,則活動管理器會順序呼叫從業務 1 和從業務 2 的 cancel 介面來取消 try 的操作。

  3. 如果從業務部分或全部 try 失敗,則主業務直接回滾並結束,而 try 成功的從業務服務則通過定時任務來處理處於 try 完成但超時的資料,將這些資料做回滾處理保證主業務服務和從業務服務的資料一致。

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

如果預增積分(try)成功,則訂單服務更改訂單狀態並通知活動管理器,活動管理器請求積分模組的 confirm 介面來增加積分。

如果預增積分(try)失敗,則訂單服務業務回滾。積分服務通過定時任務刪除預增積分(try)超時的資料。

另外如果活動管理器呼叫積分服務的 confirm 介面失敗,則活動管理器呼叫積分服務 cancel 介面來取消預增積分,從而,保證訂單和積分資料的最終一致性。

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

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

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

最大努力通知

最大努力通知方案涉及三個模組:

  • 上游應用,發訊息到 MQ 佇列。

  • 下游應用(例如簡訊服務、郵件服務),接受請求,並返回通知結果。

  • 最大努力通知服務,監聽訊息佇列,將訊息儲存到資料庫中,並按照通知規則呼叫下游應用的傳送通知介面。

具體流程如下:

  1. 上游應用傳送 MQ 訊息到 MQ 元件內,訊息內包含通知規則和通知地址

  2. 最大努力通知服務監聽到 MQ 內的訊息,解析通知規則並放入延時佇列等待觸發通知

  3. 最大努力通知服務呼叫下游的通知地址,如果呼叫成功,則該訊息標記為通知成功,如果失敗則在滿足通知規則(例如 5 分鐘發一次,共傳送 10 次)的情況下重新放入延時佇列等待下次觸發。

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

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

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

代入上面提到的支付成功簡訊通知使用者的案例,通過最大努力通知方案,當支付成功後,將訊息傳送到 MQ 中介軟體,在訊息中,定義傳送規則為 5 分鐘一次,最大發送數為 10 次。

最大努力通知服務監聽 MQ 訊息並根據規則呼叫訊息通知服務(簡訊服務)的訊息傳送介面,並記錄每次呼叫的日誌資訊。在通知成功或者已通知 10 次時,停止通知。

總 結

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

作者介紹

小羊,青柳雲架構師,現服務於神州數碼青柳雲團隊,擅長大型專案規劃、微服務架構和分散式架構。

蘇槐,微訊號 Sulaohuai,青柳雲研發總監,現服務於神州數碼青柳雲團隊,曾就職於 Oracle,新加坡電信等企業。擅長容器技術、微服務架構、敏捷開發及技術管理。

下篇文章

下篇文章介紹如何 使用 Docker來支撐微服務架構部署及運維。

沈劍聊微服務:先做好你的服務拆分

更多精彩技術專題請關注QCon 全球軟體開發大會,QCon 上海站 9 折優惠報名最後一週,2017 年 09 月 17 日前,立減 680 元,團購報名更多優惠~點選【閱讀原文】跟技術大咖零距離。欲購票或諮詢問題可聯絡購票經理 Hanna ,電話:15110019061,微信:qcon-0410。