Saga模式
Saga模式使用一系列本地事務來提供事務管理,而一個本地事務對應一個Saga參與者,在Saga流程裏面每一個本地事務只操作本地數據庫,然後通過消息或事件來觸發下一個本地事務,如果其中一個本地事務失敗了,Saga就會執行一系列補償事務來實現回滾操作。(補償事務簡單來講就是對之前本地事務做的修改導致不一致的情況執行反向操作來消除掉不一致的狀態)。
上圖左側是正常的事務流程,當執行事務T3時出現異常,則開始反向執行右邊的事務補償,其中C3是T3的補償,C2是T2的補償,C1是T1的補償,將T3,T2,T1已經修改的數據做補償處理。
實現分析
對Saga事務流程進行排序,當Ti事務完成之後,需要決定下一步要怎麼進行。如果成功執行T(i+1)分支,如果失敗,則執行C(i-1)分支。這類似一個工作流或是狀態機的概念。從實現來看,有兩種方式:
集中式實現
集中式協調器負責服務調用以及事務協調(Orchestration)即編排實現:集中式協調器負責服務調用以及事務協調。Saga提供一個控制類,其方便參與者之間的協調工作。事務執行的命令從控制類發起,按照邏輯順序請求Saga的參與者,從參與者那裏接受到反饋以後,控制類在發起向其他參與者的調用。所有Saga的參與者都圍繞這個控制類進行溝通和協調工作。
去中心化實現
分佈式的實現方式——通過事件驅動的方式進行事務協調(Choreography)即協同實現:Saga參與者(子事務)之間的調用、分配、決策和排序,通過交換事件進行進行。是一種去中心化的模式,參與者之間通過消息機制進行溝通,通過監聽器的方式監聽其他參與者發出的消息,從而執行後續的邏輯處理。由於沒有中間協調點,靠參與者自己進行相互協調。
實現比對
我個人認爲在計算機的世界裏沒有銀彈!任何的解決方案只能說是合適與不合適,而沒有完美的契合並解決。
如上兩種解決方式都有一定的弊端;對於集中式的實現方式,其弊端如下:
- 必須額外實現一個協調器,相當於增加了系統複雜度
- 需要考慮協調器自身發生故障時應對措施
分佈式的實現方式,其弊端如下:
- 添加新的事務步驟時比較麻煩,需要確定哪個Saga參與者訂閱了哪個事件。
- 有可能出現循環依賴的問題,每一個Saga參與者都可能訂閱其他參與者的事件。
- 集成測試異常複雜,需要運行所有服務來模擬事務。
實現方式
目前看到市面上已經有很多的saga實現,他們都具備saga的基本功能。
這些實現,可以大致可以分爲兩類
狀態機實現
Seata
這一類的典型實現有seata的saga,他引入了一個DSL語言定義的狀態機,允許用戶做以下操作:
在某一個子事務結束後,根據這個子事務的結果,決定下一步做什麼
能夠把子事務執行的結果保存到狀態機,並在後續的子事務中作爲輸入
允許沒有依賴的子事務之間併發執行。
-
優點:
功能強大,事務可以靈活自定義 -
缺點:
狀態機的使用門檻非常高,需要了解相關DSL,可讀性差,出問題難調試。官方例子是一個包含兩個子事務的全局事務,Json格式的狀態機定義大約有95行,較難入門。
接口入侵強,只能使用特定的輸入輸出接口參數類型,在雲原生時代,對強類型的gRPC不友好(gRPC協議,在TM拿不到用戶自定義的輸入輸出pb文件,因此無法解析結果中的字段)
Masstransit Saga State Machines
Masstransit是一個免費、開源的.NET 分佈式應用框架。其功能之一就是提供了強大的狀態機編排能力。通過集成消息隊列中間件,基於C#高效易用的語法,支持了狀態機的編排。其使用語法示例如下
///// 下單 初始化 → 已初始化
///// 翻譯:當前狀態是Initial且執行OrderProcessInitializationEvent事件時,Then(然後)執行xxxx,最後將狀態轉換(TransitionTo)爲OrderProcessInitializedState
During(Initial,
When(OrderProcessInitializationEvent)
.Then(x => {
x.Saga.OrderStartDate = DateTime.Now;
})
.TransitionTo(OrderProcessInitializedState));
///// 庫存 已初始化 → 校驗庫存
///// 翻譯:當前狀態是OrderProcessInitializedState且執行CheckProductStockEvent事件時,Then(然後)執行xxxx,最後將狀態轉換(TransitionTo)爲CheckProductStockState
During(OrderProcessInitializedState,
When(CheckProductStockEvent)
.Then(x => {
System.Console.WriteLine(x.Message.OrderId);
})
.TransitionTo(CheckProductStockState));
///// 支付 校驗庫存 → 支付
During(CheckProductStockState,
When(TakePaymentEvent)
.TransitionTo(TakePaymentState));
///// 訂單 支付 → 創建訂單
During(TakePaymentState,
When(CreateOrderEvent).Then(x => {
System.Console.WriteLine(x.Message.OrderId);
})
.TransitionTo(CreateOrderState));
///// 創建訂單失敗
DuringAny(When(CreateOrderFaultEvent)
.TransitionTo(CreateOrderFaultedState)
.Then(context => context.Publish<Fault<TakePaymentEvent>>(new {context.Message})));
///// 支付失敗
DuringAny(When(TakePaymentEventFaultEvent)
.TransitionTo(TakePaymentFaultedState)
.Then(context => context.Publish<Fault<CheckProductStockEvent>>(new {context.Message})));
///// 校驗庫存失敗
DuringAny(When(CheckProductStockFaultEvent)
.TransitionTo(CheckProductStockFaultedState)
.Then(context => context.Publish<Fault<OrderProcessInitializationEvent>>(new {context.Message})));
///// 下單失敗
DuringAny(When(OrderProcessInitializationFaultEvent)
.TransitionTo(OrderProcessInitializedFaultedState)
.Then(context => context.Publish<OrderProcessFailedEvent>(new {OrderId = context.Saga.CorrelationId})));
///// 下單流程失敗
DuringAny(When(OrderProcessFailedEvent)
.TransitionTo(OrderProcessFailedState));
流程邏輯:當客戶端請求下單服務時,業務邏輯正常執行,執行成功後發佈事件到消息隊列,狀態機監聽到對應的訂單事件後,修改當前狀態,發佈事件標識成功或失敗,訂單服務業務監聽事件,響應狀態的調整(一般是標識或回滾業務)。
-
優點
方便簡單,而且強大,流程編排能力很強。 -
缺點:引入了rabbitmq,有中間件依賴。
可參考實現:
使用 Masstransit中的 Request/Response 與 Courier 功能實現最終一致性
分佈式事務 | 基於MassTransit的StateMachine實現Saga編排式分佈式事務
非狀態機實現
這一類的實現有eventuate的saga,dtm的saga。
在這一類的實現中,沒有引入新的DSL來實現狀態機,而是採用函數接口的方式,定義全局事務下的各個分支事務。
優點:
簡單易上手,易維護
缺點:
難以做到狀態機的事務靈活自定義