1. 程式人生 > >微服務架構下的資料一致性保證

微服務架構下的資料一致性保證

從2014年開始,微服務逐漸進入大家的實現,被認為是下一代實現資訊化的有效手段。設計到系統,其中繞不開的就是資料一致性,從本地事務,到後來的分散式事務,都能夠有效的保證資料一致性。但是在微服務架構中,這兩種方式都不是最好的選擇。

1. 使用本地事務和分散式事務保證一致性

在傳統的單擊應用中,最簡單、最直接、最普遍的會使用一個關係型資料庫,通過關係型資料庫的事務保證資料的一致性。這種事務有四個基本要素:ACID。

  • A(Atomicity,原子性):整個事務中的所有操作,要麼全部完成,要麼全部失敗,不可能停滯在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
  • C(Consistency,一致性):一個事務可以封裝狀態改變(除非它是一個只讀的)。事務必須始終保持系統處於一致的狀態,不管在任何給定的時間併發事務有多少。
  • I(Isolation,隔離性):隔離狀態執行事務,使它們好像是系統在給定時間內執行的唯一操作。如果有兩個事務,執行在相同的時間內,執行相同的功能,事務的隔離性將確保每一事務在系統中認為只有該事務在使用系統。這種屬性有時稱為序列化,為了防止事務操作間的混淆,必須序列化或序列化請求,使得在同一時間僅有一個請求用於同一資料。
  • D(Durability,永續性):在事務完成以後,該事務對資料庫所作的更改便持久的儲存在資料庫之中,並不會被回滾。

在傳統的本地事務中,為了保證資料一致性,我們只需要先開始一個事務,然後進行新增、修改、刪除等操作,然後提交事務,如果發生異常就回滾。簡簡單單,就能夠站在各大資料庫廠商的肩膀上,實現資料一致性。

隨著組織規模不斷擴大、業務量不斷增加,單機應用已不足以支撐龐大的業務量和資料量。這個需要對應用和資料進行拆分。就出現了需要同時訪問多個數據庫的情況。這個時候就需要分散式事務來保證資料一致性,也就是常說的兩階段提交協議(2PC,Two Phase Commitment Protocol)。在這個協議中,最關鍵的點就是,多個數據庫的活動,均由一個事務協調器的元件來控制。具體的分為5個步驟:

  • 應用程式呼叫事務協調器中的提交方法
  • 事務協調器將聯絡事務中涉及的每個資料庫,並通知它們準備提交事務(這是第一階段的開始)
  • 接收到準備提交事務通知後,資料庫必須確保能在被要求提交事務時提交事務,或在被要求回滾事務時回滾事務。如果資料庫無法準備事務,它會以一個否定響應來回應事務協調器。
  • 事務協調器收集來自各資料庫的所有響應。
  • 在第二階段,事務協調器將事務的結果通知給每個資料庫。如果任一資料庫做出否定響應,則事務協調器會將一個回滾命令傳送給事務中涉及的所有資料庫。如果資料庫都做出肯定響應,則事務協調器會指示所有的資源管理器提交事務。一旦通知資料庫提交,此後的事務就不能失敗了。通過以肯定的方式響應第一階段,每個資源管理器均已確保,如果以後通知它提交事務,則事務不會失敗。

在傳統的系統架構中,通常使用的是資料庫來作為資源管理器,資料的一致性通過事務來保證,即使實在分散式事務中,也能夠利用資料庫的事務來實現資料一致性。

但是在微服務架構中,資料訪問變得複雜。通常情況下,資料都是各個微服務私有的,只能通過API的方式訪問資料。這種方式可以實現微服務之間的鬆耦合,使彼此獨立的微服務更容易的進行擴充套件。但是帶來的一個問題就是,不清楚各自底層的資料儲存(不一定是關係型資料庫),無法通過統一的事務協調器來完成資料一致性。

簡單的說,傳統的本地事務或分散式事務不適合微服務架構。

2. 微服務架構中的最終一致性

在分散式系統架構中有一個CAP理論:任何分散式系統只可同時滿足一致性(Consistency)、可用性(Availability)、分割槽容錯性(Partition tolerance)中的兩點,沒法三者兼顧。對於分散式系統來說,分割槽容錯性是基本要求,否則就失去了價值。因此,就只能在可用性和一致性之間做出選擇。如果選擇提供一致性需要付出在滿足一致性之前阻塞其他併發訪問的代價。這可能持續一個不確定的時間,尤其是在系統已經表現出高延遲時或者網路故障導致失去連線時。依據目前的成功經驗,可用性一般是更好的選擇,但是在服務和資料庫之間維護資料一致性是非常根本的需求,微服務架構中選擇滿足最終一致性。

最終一致性是指系統中的所有資料副本經過一段時間後,最終能夠達到一致的狀態。

這裡所說的一段時間,也要是使用者可接受範圍內的一段時間。

從一致性的本質來看,就是在一個業務邏輯中包含的所有服務要麼都成功,要麼都失敗。那我們又該如何選擇方向,來保證成功還是保證失敗呢?就是就需要根據業務模式做出選擇。實現最終一致性有三種模式:可靠事件模式、業務補償模式、TCC模式。

2.1 可靠事件模式

可靠事件模式屬於事件驅動架構,當某件重要事情發生時,例如更新一個業務實體,微服務會向訊息代理髮佈一個事件。訊息代理會向訂閱事件的微服務推送事件,當訂閱這些事件的微服務接收此事件時,就可以完成自己的業務,也可能會引發更多的事件釋出。

下面舉個簡單的例子

1.訂單服務建立一個訂單,釋出一個“建立訂單”事件

2.支付服務消費“建立訂單”事件,待支付完成後釋出一個“支付成功”事件

3.訂單服務消費“支付成功”事件,訂單狀態更新為待出庫。

從而就實現了完整的業務流程。

這個過程可能導致出現不一致的地方在於:

  1. 某個服務在更新了業務實體後釋出事件卻失敗
  2. 雖然服務釋出事件成功,但是訊息代理未能正確推送事件到訂閱的微服務
  3. 接受事件的微服務重複消費了事件

可靠事件模式在於保證可靠事件投遞和避免重複消費。可靠事件投遞定義為:每個服務原子性的業務操作和釋出事件,訊息代理確保事件傳遞至少一次。避免重複消費要求服務實現冪等性,如支付服務不能因為重複收到事件而多次支付。

2.2 業務補償模式

在描述業務補償模式之前,先先定義兩個概念:

  • 業務異常:業務邏輯產生錯誤的情況,比如賬戶餘額不足、商品庫存不足等。
  • 技術異常:非業務邏輯產生的異常,如網路連線異常、網路超時等。

補償模式使用一個額外的協調服務來協調各個需要保證一致性的微服務,協調服務按順序呼叫各個微服務,如果某個微服務呼叫異常(包括業務異常和技術異常)就取消之前所有已經呼叫成功的微服務。

我們通過一個例子來說明補償模式,一家旅行公司提供預訂行程的業務,可以通過公司的網站提前預訂飛機票、火車票、酒店等。

假設一位客戶規劃的行程是:(1)上海-北京6月19日9點的某某航班,(2)某某酒店住宿3晚,(3)北京-上海6月22日17點火車。在客戶提交行程後,旅行公司的預訂行程業務按順序序列的呼叫航班預訂服務、酒店預訂服務、火車預訂服務。最後的火車預訂服務成功後整個預訂業務才算完成。

如果火車票預訂服務沒有呼叫成功,那麼之前預訂的航班、酒店都得取消。取消之前預訂的酒店、航班即為補償過程。

需要注意的是酒店的取消預訂、航班的取消預訂同樣不能保證一定成功,所以補償過程往往也同樣需要實現最終一致性,需要保證取消服務至少被呼叫一次和取消服務必須實現冪等性。

我們應該儘可能通過設計避免採用補償方式,比如上面的例子中,在預訂火車票失敗的時候可以提示客戶更改其他的時間。

2.3 TCC模式(Try-Confirm-Cancel)

一個完整的TCC業務由一個主業務服務和若干個從業務服務組成,主業務服務發起並完成整個業務活動,TCC模式要求從服務提供三個介面:Try、Confirm、Cancel。

  • Try:完成所有業務檢查,預留必須業務資源
  • Confirm:真正執行業務,不作任何業務檢查,只使用Try階段預留的業務資源,Confirm操作滿足冪等性
  • Cancel:釋放Try階段預留的業務資源,Cancel操作滿足冪等性

整個TCC業務分成兩個階段完成:

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

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

需要注意的是第二階段confirm或cancel操作本身也是滿足最終一致性的過程,在呼叫confirm或cancel的時候也可能因為某種原因(比如網路)導致呼叫失敗,所以需要活動管理支援重試的能力,同時這也就要求confirm和cancel操作具有冪等性。

3. 總結

前面提到的三種模式,幾乎能夠很好的解決網路故障或呼叫超時等基本問題。但在當今複雜的環境中,很多服務需要依賴外部系統,在這些業務場景中,就需要週期性的進行校驗操作,比如支付系統和銀行的對賬過程。

還需要順帶的提一下,技術能夠解決問題,但不能解決所有問題,很多情況下,需要首先保證業務流程準確,然後在技術解決不了的情況下,仍然需要人工干預。