10 分鐘說透 Saga 分佈式事務

開篇

隨着微服務架構的興起,越來越多的公司會在實際場景中遇到分佈式事務的問題。特別是在金融應用場景,幾個跨進程的應用共同完成一個任務,就更離不開分佈式事務的參與。而對於分佈式事務而言,2PC、TCC 也是經常被提到了,不過在面對長業務流程,並且很難進行 TCC 改造的場景,會選擇使用 Saga 分佈式事務。今天會給大家介紹 Saga 實現分佈式事務的內容:

Saga 的分佈式解決方案

隨着互聯網的快速發展,原來的單體應用已經很難支撐大流量高併發的請求了,因此軟件系統由原來的單體應用逐漸向分佈式過度,如圖 1 所示,左邊的 Web App 包含了 UI 和服務的模塊,在轉變以後會對應右邊的微服務架構,服務之間存在關聯地相互調用。

圖 1 單體到分佈式的系統架構過渡

在進行分佈式部署之後,會存在多個服務共同完成一個事務操作,並且這些服務彼此都存在於不同的服務器或者網絡環境,服務之間需要通過網絡遠程協作完成事務稱之爲分佈式事務。例如:銀行轉賬業務、下單扣件庫存等。

在分佈式事務場景下,如果對數據有強一致性要求,會在業務層上纔去 “兩階段提交”(2PC)的方案。

如果保證最終一致性的話可以採取 TCC (Try Confirm Cancel)模式。雖然 TCC 保證最終一致性的模式被業內廣泛使用,但是對於某些分佈式事務場景,流程多、流程長、還可能要調用其它公司的服務。特別是對於不可控的服務(其他公司的服務),這些服務無法遵循 TCC 開發模式,導致 TCC 模式的開發成本增高。體現在具體場景中,以金融核心的業務爲代表(渠道層、產品層、集成層),其特點是:流程多、流程長、調用不可控服務。同時也是應爲流程長,事務邊界太長,加鎖時間長,使用 TCC 模式會影響併發性能。

鑑於此類業務場景的分佈式事務處理,提出了 Saga 分佈式處理模式。Saga 是一種 “長事務的解決方案”,更適合於 “業務流程長、業務流程多” 的場景。特別是針對參與事務的服務是遺留系統服務,此類服務無法提供 TCC 模式下的三個接口,就可以採用 Saga 模式。

其適用於的業務業務場景有,金融機構對接系統(需要對接外部系統)、渠道整合(流程長)、分佈式架構服務等。其優勢是一階段提交本地事務,無鎖,高性能;參與者可異步執行,高吞吐;補償服務易於實現,因爲一個更新操作的反向操作是比較容易理解的;當然其也存在缺點,就是不保證隔離性。

Saga 處理事務一致性

1987 年普林斯頓大學的 Hector Garcia-Molina 和 Kenneth Salem 發表了一篇 Paper Sagas,講述的是如何處理 long lived transaction(長活事務)。Saga 是一個長活事務可被分解成可以交錯運行的子事務集合。其中每個子事務都是一個保持數據庫一致性的真實事務。

在這位老兄的論文中提到,每個 Saga 由一系列 sub-transaction Ti 組成。每個 Ti 都有對應的補償動作 Ci,補償動作用於撤銷 Ti 造成的結果。這裏可以理解爲,針對每一個分佈式事務的每個執行操作或者是步驟都是一個 Ti,例如扣減庫存是 T1、創建訂單是 T2、支付服務是 T3。那麼針對每個 Ti 都對應一個補償動作 Ci,例如回覆庫存 C1、訂單回滾 C2、支付回滾 C3。

Saga 事務有兩種恢復策略:

向前恢復**(****forward recovery****)**,也就是 “勇往直前”。

對於執行不通過的事務,會嘗試重試事務,這裏有一個假設就是每個子事務最終都會成功。這種方式適用於必須要成功的場景,如圖 2 所示,上面的圖例,子事務按照從左到右的順序執行,T1 執行完畢以後 T2 執行,然後是 T3、T4、T5。

圖 2 Saga 事務執行的策略

事務恢復的順序也是按照:T1、T2、T3、T4、T5 的方向進行,如果在執行 T1 的時候失敗了就重試 T1,以此類推在哪個子事務執行時失敗了就執行哪個事務。因此叫做 “勇往直前”。

向後恢復**(****backward recovery****)**,在執行事務失敗時,補償所有已完成的事務,是 “一退到底” 的方式。如圖 2 所示,下面的圖例,子事務依舊從左往右執行,在執行到事務 T3 的時候,該事務執行失敗了,於是按照紅線的方向開始執行補償事務,先執行 C3、然後是 C2 和 C1,直到 T0、T1、T2 的補償事務 C1、C2、C3 都執行完畢。也就是回滾整個 Saga 的執行結果。

Saga 分佈式事務協調

上面介紹了 Saga 的概念和事務恢復方式,每個事務存在多個子事務,每個子事務都有一個補償事務,其在事務回滾的時候使用。由於子事務對應的操作在分佈式的系統架構中會部署在不同的服務中,這些子事務爲了完成共同的事務需要進行協同。

實際上在啓動一個 Saga 事務時,協調邏輯會告訴第一個 Saga 參與者,也就是子事務,去執行本地事務。事務完成之後 Saga 的會按照執行順序調用 Saga 的下一個參與的子事務。這個過程會一直持續到 Saga 事務執行完畢。

如果在執行子事務的過程中遇到子事務對應的本地事務失敗,則 Saga 會按照相反的順序執行補償事務。通常來說我們把這種 Saga 執行事務的順序稱爲個 Saga 的協調邏輯。這種協調邏輯有兩種模式,編排(Choreography)和控制(Orchestration)分別如下:

編排(Choreography):參與者(子事務)之間的調用、分配、決策和排序,通過交換事件進行進行。是一種去中心化的模式,參與者之間通過消息機制進行溝通,通過監聽器的方式監聽其他參與者發出的消息,從而執行後續的邏輯處理。由於沒有中間協調點,靠參與靠自己進行相互協調。

控制(Orchestration):Saga 提供一個控制類,其方便參與者之前的協調工作。事務執行的命令從控制類發起,按照邏輯順序請求 Saga 的參與者,從參與者那裏接受到反饋以後,控制類在發起向其他參與者的調用。所有 Saga 的參與者都圍繞這個控制類進行溝通和協調工作。

下面通過一個例子來介紹這兩種協調模式,假設有一個下單的業務,從訂單服務的創建訂單操作發起,會依次調用支付服務中的支付訂單,庫存服務中的扣減庫存以及發貨服務中的發貨操作,最終如果所有參與者(服務)中的操作(子事務)完成的話,整個下單事務就算完成。

編排(Choreography),由於沒有中心的控制類參與參與者操作之間的協調工作,因此通過消息發送的方式進行協調。

如圖 3 所示:

圖 3 編排模式 - 事務執行成功

  1. “訂單服務”中執行 “創建訂單” 操作,此時會發送一個 “創建訂單消息” 到隊列中。

  2. “支付服務”監聽到隊列中的這個訂單消息,調用 “支付訂單” 的操作,同時也發送 “只服務消息” 到隊列中。

  3. “庫存服務”在監聽到 “支付消息” 之後會進行 “扣減庫存” 的處理,並且發送 “扣減庫存消息” 等待下一個消費者接受。

  4. “發貨服務”作爲整個事務的最後一個子事務,在接到 “扣減庫存消息” 以後會執行發貨的子事務,完成事務以後會給 “訂單服務” 發送“發貨消息”,訂單服務在接受到消息以後完成整個事務閉環,並且提交。

上面說的是事務執行成功的情況,如果事務執行失敗那應該如何處理?

如圖 4 所示:

圖 4 編排模式 - 事務執行失敗

  1. 假設在執行 “發貨” 時子事務失敗了,會發送“發貨失敗消息”。

  2. 庫存服務在接受到 “發貨失敗消息” 之後會執行 “回滾庫存” 的操作,該操作將原來扣減的庫存加回去,同時發送“扣減失敗消息”。

  3. “支付服務”在接受到 “扣減失敗消息” 之後會執行“回滾支付”,進行退款的操作,同時發送“支付失敗消息”。訂單服務在接受到該消息以後將下單事務標記爲失敗。

從上面的描述可以看出****編排的好處

簡單:每個子事務進行操作時只用發佈事件消息,其他子事務監聽處理。

松耦合:參與者(服務)之間通過訂閱事件進行溝通,組合會更加靈活。

當然也有一些****缺點

理解困難:沒有對業務流程進行完整的描述,要了解整個事務的執行過程需要通過閱讀代碼完成。增加開發人員理解和維護代碼的難度。

存在服務的循環依賴:由於通過消息和事件進行溝通,參與者之間會存在循環依賴的情況。也就是 A 服務調用 B 服務,B 服務又調用 A 服務的情況。這也增加了架構設計的複雜度,在設計初期需要認真考慮。

緊耦合風險:每個參與者執行的方法都依賴於上一步參與者發出的消息,但是上一步的參與者的所有消息都需要被訂閱,才能瞭解參與者的真實狀態,無形中增加了兩個服務的耦合度。

控制(Orchestration),其核心是定義一個控制類,它會告訴參與者(服務)應該執行哪些操作(子事務)。 Saga 控制類通過命令以及異步回覆的方式與參與者進行交互。

如圖 5 所示:

圖 5 控制模式 - 成功

1. 訂單服務執行下單事務時,向 Saga 協調器發送請求命令,Saga 協調器接受到命令以後按照子事務執行的順序調用服務中的方法。

  1. 最開始執行 “支付訂單” 的操作,調用 “支付服務” 中的 “支付訂單” 操作,並且通過虛線的部分返回執行結果“支付完成”。

  2. 接下來,執行 “庫存服務” 中的 “扣減庫存” 方法,同樣通過虛線部分返回扣減完成的消息給“請求反饋“模塊。

  3. 緊接着就是執行 “發貨“命令,調用” 發貨服務 “中的” 發貨 “方法,並且返回” 發貨完成“的響應。

  4. 最後,三個子事務都執行完畢以後,返回訂單服務,完成整個分佈式事務。

介紹完成成功完成事務之後,再來看看出現異常的情況。

如圖 6 所示:

圖 6 控制模式 - 失敗

1. 在執行 “發貨” 命令時發現 “發貨失敗”,於是“發貨服務” 反饋給 Saga 協調器。

  1. 此時協調器調用 “庫存服務” 中的 “回滾庫存” 操作,將扣減的庫存恢復。

  2. 然後調用 “支付服務” 中的 “回滾支付” 完成支付退款的工作。

  3. 最後,通知訂單服務事務處理失敗。

需要指出的是控制模式也是基於事件驅動的,與編排模式一樣會發送消息通知參與者執行命令,上面兩個圖中命令的執行和調用也是通過消息的方式進行。

控制器****設計的優點:

**避免循環依賴:**在編排模式中存在參與者之間的循環調用,而中心控制類的方式可以避免這種情況的發生。

**降低複雜性:**所有事務交給控制器完成,它負責命令的執行和回覆的處理,參與者只需要完成自身的任務,不用考慮處理消息的方式,降低參與者接入的複雜性。

**容易測試:**測試工作集中在集中控制類上,其他服務單獨測試功能即可。

**容易擴展:**如果事務需要添加新步驟,只需修改控制類,保持事務複雜性保持線性,回滾更容易管理。

當然這種方法也存在缺****點

**依賴控制器:**控制器中集中太多邏輯的風險。

**增加管理難度:**這種模式除了管理各個業務服務以外,還需要額外管理控制類服務,無形中增加了管理的難度和複雜度。而且存在單點風險,一旦控制器出現問題,整個業務就處於癱瘓中。

總結

這裏對 Saga 進行一個總結,首先 Saga 是針對分佈式長活事務的解決方案,針對事務長、多、複雜的情況,特別是服務由多個公司開發具有不可控性,可以使用 Saga 模式進行分佈式事務的處理。Saga 在處理事務一致性方面採取了向前恢復和向後恢復策略,前者通過不斷重試的方式保證事務完成,而後者通過子事務的補償事務,逐一回滾的方式讓事務標記失敗。在分佈式協調方面,Saga 採用了兩種模式:編排和控制。前者讓參與者(服務)之間通過消息進行溝通,根據事件出發事務的執行流程,是一種去中心化的模式。後者通過中心控制類,處理事務的執行和回滾步驟,統一調用服務和接受服務的反饋。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s?__biz=MzU0OTk3ODQ3Ng==&mid=2247497449&idx=1&sn=8479bfe33c5d792c9f9f35cacc6141f2&chksm=fba51eeaccd297fc89f19dfd6d670f42541a0a867dc67a9df605c1b225754707419ed0759d21&scene=132#wechat_redirect