聊聊分散式解決方案Saga模式

2023-05-29 15:01:41

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來實現狀態機,而是採用函數介面的方式,定義全域性事務下的各個分支事務。

優點:

簡單易上手,易維護

缺點:
難以做到狀態機的事務靈活自定義

ACID與Saga