1. 程式人生 > >微服務架構下的資料一致性保證(三):補償模式

微服務架構下的資料一致性保證(三):補償模式

在微服務架構下,一個典型的業務操作往往由一系列自治的微服務步驟組成,如果某個步驟發生了業務異常(比如支付時賬戶餘額不足等)時就出現了資料不一致。我們採用補償模式來撤銷已經完成的步驟,從而實現最終一致性。

今天分享的還是關於微服務架構下的資料一致性保證的話題,是資料一致性系列分享的第三篇。

在第一篇分享中介紹了微服務架構應滿足資料最終一致性,並簡要介紹了最終一致性的三種模式:可靠事件模式、補償模式、TCC模式。

在第二篇分享中深入可靠事件模式,講述了可靠事件投遞和冪等性的實現方式和需要注意的問題。

在今天的第三篇分享中來談談補償模式和TCC模式,主要從三個方面來談。

·

 · ·

首先簡單回顧一下補償模式。

補償模式使用一個額外的協調服務來協調各個需要保證一致性的微服務(我們稱為工作服務),協調服務按順序呼叫各個工作服務,如果某個工作服務呼叫失敗就撤銷之前所有已經完成的工作服務。要求需要保證一致性的工作服務提供補償操作。

比如一家旅行公司通過協調3種工作服務為客戶預訂航班、預訂酒店和預訂火車。


當其中的預訂火車失敗時,就需要取消之前的航班和酒店的預訂,這裡麵包含的三個工作服務都需要提供補償操作。


為了降低開發的複雜性和提高效率,協調服務實現為一個通用的補償框架。補償框架提供服務編排和自動完成補償的能力。

· · ·

第一部分:實現補償模式的關鍵在於業務流水的記錄

要實現補償過程,我們需要做到兩點

首先要確定失敗的步驟和狀態,從而確定需要補償的範圍。

在上面的例子中我們不光要知道第3個步驟(預訂火車)失敗,還要知道失敗的原因。如果是因為預訂火車服務返回無票,那麼補償過程只需要取消前兩個步驟就可以了;但是如果失敗的原因是因為網路超時,那麼補償過程除前兩個步驟之外還需要包括第3個步驟。

其次要能提供補償操作使用到的業務資料。

比如一個支付微服務的補償操作要求引數包括支付時的業務流水id、賬號和金額。理論上說實際完成補償操作可以根據唯一的業務流水id就可以,但是提供更多的要素有益於微服務的健壯性,微服務在收到補償操作的時候可以做業務的檢查,比如檢查賬戶是否相等,金額是否一致等等。

做到上面兩點的辦法是記錄完整的業務流水,可以通過業務流水的狀態來確定需要補償的步驟,同時業務流水為補償操作提供需要的業務資料。

當客戶的一個預訂請求達到時,協調服務(補償框架)為請求生成一個全域性唯一的業務流水號。並在呼叫各個工作服務的同時記錄完整的狀態。

1.記錄呼叫bookFlight的業務流水,呼叫bookFlight服務,更新業務流水狀態

2.記錄呼叫bookHotel的業務流水,呼叫bookHotel服務,更新業務流水狀態

3.記錄呼叫bookTrain的業務流水,呼叫bookTrain服務,更新業務流水狀態

當呼叫某個服務出現異常時,比如第3步驟(預訂火車)異常。

協調服務(補償框架)同樣會記錄第3步的狀態,同時會另外記錄一條事件,說明業務出現了異常。然後就是執行補償過程了,可以從業務流水的狀態中知道補償的範圍,補償過程中需要的業務資料從記錄的業務流水中獲取。

對於一個通用的補償框架來說,預先知道微服務需要記錄的業務要素是不可能的。那麼就需要一種方法來保證業務流水的可擴充套件性,這裡介紹兩種方法:大表和關聯表。

大表顧明思議就是設計時除必須的欄位外,還需要預留大量的備用欄位,框架可以提供輔助工具來幫助將業務資料對映到備用欄位中。

關聯表,分為框架表和業務表,技術表中儲存為實現補償操作所需要的技術資料,業務表儲存業務資料,通過在技術表中增加業務表名和業務表主鍵來建立和業務資料的關聯。

大表對於框架層實現起來簡單,但是也有一些難點,比如預留多少欄位合適,每個欄位又需要預留多少長度。另外一個難點是如果向從資料層面來查詢資料,很難看出備用欄位的業務含義,維護過程不友好。

關聯表在業務要素上更靈活,能支援不同的業務型別記錄不同的業務要素;但是對於框架實現上難度更高,另外每次查詢都需要複雜的關聯動作,效能方面會受影響。

有了上面的完整的流水記錄,協調服務就可以根據工作服務的狀態在異常時完成補償過程。

· · ·

第二部分:通過重試來保證補償過程的完整,從而滿足最終一致性

補償過程作為一個服務呼叫過程同樣存在呼叫不成功的情況,這個時候需要通過重試的機制來保證補償的成功率。當然這也就要求補償操作本身具備冪等性。

關於冪等性的實現在上一篇分享中已經做過討論,有興趣的同學可以從普元官方下載。

如果只是一味的失敗就立即重試會給工作服務造成不必要的壓力,我們要根據服務執行失敗的原因來選擇不同的重試策略。

1) 如果失敗的原因不是暫時性的,由於業務因素導致(如業務要素檢查失敗)的業務錯誤,這類錯誤是不會重發就能自動恢復的,那麼應該立即終止重試。

2) 如果錯誤的原因是一些罕見的異常,比如因為網路傳輸過程出現數據丟失或者錯誤,應該立即再次重試,因為類似的錯誤一般很少會再次發生。

3) 如果錯誤的原因是系統繁忙(比如http協議返回的500或者另外約定的返回碼)或者超時,這個時候需要等待一些時間再重試。

重試操作一般會指定重試次數上線,如果重試次數達到了上限就不再進行重試了。這個時候應該通過一種手段通知相關人員進行處理。

對於等待重試的策略如果重試時仍然錯誤,可逐漸增加等待的時間,直到達到一個上限後,以上限作為等待時間。

如果某個時刻聚集了大量需要重試的操作,補償框架需要控制請求的流量,以防止對工作服務造成過大的壓力。

另外關於補償模式還有幾點補充說明

1.微服務實現補償操作不是簡單的回退到業務發生時的狀態,因為可能還有其他的併發的請求同時更改了狀態。一般都使用逆操作的方式完成補償。

2.補償過程不需要嚴格按照與業務發生的相反順序執行,可以依據工作服務的重用程度優先執行,甚至是可以併發的執行。

3.有些服務的補償過程是有依賴關係的,被依賴服務的補償操作沒有成功就要及時終止補償過程。

4.如果在一個業務中包含的工作服務不是都提供了補償操作,那我們編排服務時應該把提供補償操作的服務放在前面,這樣當後面的工作服務錯誤時還有機會補償。

5.設計工作服務的補償介面時應該以協調服務請求的業務要素作為條件,不要以工作服務的應答要素作為條件。因為還存在超時需要補償的情況,這時補償框架就沒法提供補償需要的業務要素。

· · ·

第三部分:TCC模式是優化的補償模式。

在補償模式中一個比較明顯的缺陷是,沒有隔離性。從第一個工作服務步驟開始一直到所有工作服務完成(或者補償過程完成),不一致是對其他服務可見的。另外最終一致性的保證還充分的依賴了協調服務的健壯性,如果協調服務異常,就沒法達到一致性。

TCC模式在一定程度上彌補了上述的缺陷,在TCC模式中直到明確的confirm動作,所有的業務操作都是隔離的(由業務層面保證)。另外工作服務可以通過指定try操作的超時時間,主動的cancel預留的業務資源,從而實現自治的微服務。

TCC模式和補償模式一樣需要需要有協調服務和工作服務,協調服務也可以作為通用服務一般實現為框架。與補償模式不同的是TCC服務框架不需要記錄詳細的業務流水,完成confirm和cancel操作的業務要素由業務服務提供。


在第4步確認預訂之前,訂單只是pending狀態,只有等到明確的confirm之後訂單才生效。


如果3個服務中某個服務try操作失敗,那麼可以向TCC服務框架提交cancel,或者什麼也不做由工作服務自己超時處理。

TCC模式也不能百分百保證一致性,如果業務服務向TCC服務框架提交confirm後,TCC服務框架向某個工作服務提交confirm失敗(比如網路故障),那麼就會出現不一致,一般稱為heuristic exception。

需要說明的是為保證業務成功率,業務服務向TCC服務框架提交confirm以及TCC服務框架向工作服務提交confirm/cancel時都要支援重試,這也就要confirm/cancel的實現必須具有冪等性。如果業務服務向TCC服務框架提交confirm/cancel失敗,不會導致不一致,因為服務最後都會超時而取消。

另外heuristic exception是不可杜絕的,但是可以通過設定合適的超時時間,以及重試頻率和監控措施使得出現這個異常的可能性降低到很小。如果出現了heuristic exception是可以通過人工的手段補救的。