天天看點

分布式事務:Saga模式

1 Saga相關概念

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

論文位址:sagas

1.1 Saga的組成

  • 每個Saga由一系列sub-transaction Ti 組成
  • 每個Ti 都有對應的補償動作Ci,補償動作用于撤銷Ti造成的結果

可以看到,和TCC相比,Saga沒有“預留”動作,它的Ti就是直接送出到庫。

Saga的執行順序有兩種:

  • T1, T2, T3, ..., Tn
  • T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n

Saga定義了兩種恢複政策:

  • backward recovery,向後恢複,補償所有已完成的事務,如果任一子事務失敗。即上面提到的第二種執行順序,其中j是發生錯誤的sub-transaction,這種做法的效果是撤銷掉之前所有成功的sub-transation,使得整個Saga的執行結果撤銷。
  • forward recovery,向前恢複,重試失敗的事務,假設每個子事務最終都會成功。适用于必須要成功的場景,執行順序是類似于這樣的:T1, T2, ..., Tj(失敗), Tj(重試),..., Tn,其中j是發生錯誤的sub-transaction。該情況下不需要Ci。

顯然,向前恢複沒有必要提供補償事務,如果你的業務中,子事務(最終)總會成功,或補償事務難以定義或不可能,向前恢複更符合你的需求。

理論上補償事務永不失敗,然而,在分布式世界中,伺服器可能會當機,網絡可能會失敗,甚至資料中心也可能會停電。在這種情況下我們能做些什麼? 最後的手段是提供回退措施,比如人工幹預。

1.2 Saga的使用條件

Saga看起來很有希望滿足我們的需求。所有長活事務都可以這樣做嗎?這裡有一些限制:

  1. Saga隻允許兩個層次的嵌套,頂級的Saga和簡單子事務
  2. 在外層,全原子性不能得到滿足。也就是說,sagas可能會看到其他sagas的部分結果
  3. 每個子事務應該是獨立的原子行為
  4. 在我們的業務場景下,各個業務環境(如:航班預訂、租車、酒店預訂和付款)是自然獨立的行為,而且每個事務都可以用對應服務的資料庫保證原子操作。

補償也有需考慮的事項:

  • 補償事務從語義角度撤消了事務Ti的行為,但未必能将資料庫傳回到執行Ti時的狀态。(例如,如果事務觸發飛彈發射, 則可能無法撤消此操作)

但這對我們的業務來說不是問題。其實難以撤消的行為也有可能被補償。例如,發送電郵的事務可以通過發送解釋問題的另一封電郵來補償。

對于ACID的保證:

Saga對于ACID的保證和TCC一樣:

  • 原子性(Atomicity):正常情況下保證。
  • 一緻性(Consistency),在某個時間點,會出現A庫和B庫的資料違反一緻性要求的情況,但是最終是一緻的。
  • 隔離性(Isolation),在某個時間點,A事務能夠讀到B事務部分送出的結果。
  • 持久性(Durability),和本地事務一樣,隻要commit則資料被持久。

Saga不提供ACID保證,因為原子性和隔離性不能得到滿足。原論文描述如下:

full atomicity is not provided. That is, sagas may view the partial results of other sagas

通過saga log,saga可以保證一緻性和持久性。

和TCC對比

Saga相比TCC的缺點是缺少預留動作,導緻補償動作的實作比較麻煩:Ti就是commit,比如一個業務是發送郵件,在TCC模式下,先儲存草稿(Try)再發送(Confirm),撤銷的話直接删除草稿(Cancel)就行了。而Saga則就直接發送郵件了(Ti),如果要撤銷則得再發送一份郵件說明撤銷(Ci),實作起來有一些麻煩。

如果把上面的發郵件的例子換成:A服務在完成Ti後立即發送Event到ESB(企業服務總線,可以認為是一個消息中間件),下遊服務監聽到這個Event做自己的一些工作然後再發送Event到ESB,如果A服務執行補償動作Ci,那麼整個補償動作的層級就很深。

不過沒有預留動作也可以認為是優點:

  • 有些業務很簡單,套用TCC需要修改原來的業務邏輯,而Saga隻需要添加一個補償動作就行了。
  • TCC最少通信次數為2n,而Saga為n(n=sub-transaction的數量)。
  • 有些第三方服務沒有Try接口,TCC模式實作起來就比較tricky了,而Saga則很簡單。
  • 沒有預留動作就意味着不必擔心資源釋放的問題,異常處理起來也更簡單(請對比Saga的恢複政策和TCC的異常處理)。

2 Saga相關實作

Saga Log

Saga保證所有的子事務都得以完成或補償,但Saga系統本身也可能會崩潰。Saga崩潰時可能處于以下幾個狀态:

  • Saga收到事務請求,但尚未開始。因子事務對應的微服務狀态未被Saga修改,我們什麼也不需要做。
  • 一些子事務已經完成。重新開機後,Saga必須接着上次完成的事務恢複。
  • 子事務已開始,但尚未完成。由于遠端服務可能已完成事務,也可能事務失敗,甚至服務請求逾時,saga隻能重新發起之前未确認完成的子事務。這意味着子事務必須幂等。
  • 子事務失敗,其補償事務尚未開始。Saga必須在重新開機後執行對應補償事務。
  • 補償事務已開始但尚未完成。解決方案與上一個相同。這意味着補償事務也必須是幂等的。
  • 所有子事務或補償事務均已完成,與第一種情況相同。

為了恢複到上述狀态,我們必須追蹤子事務及補償事務的每一步。我們決定通過事件的方式達到以上要求,并将以下事件儲存在名為saga log的持久存儲中:

  • Saga started event 儲存整個saga請求,其中包括多個事務/補償請求
  • Transaction started event 儲存對應事務請求
  • Transaction ended event 儲存對應事務請求及其回複
  • Transaction aborted event 儲存對應事務請求和失敗的原因
  • Transaction compensated event 儲存對應補償請求及其回複
  • Saga ended event 标志着saga事務請求的結束,不需要儲存任何内容
分布式事務:Saga模式

通過将這些事件持久化在saga log中,我們可以将saga恢複到上述任何狀态。

由于Saga隻需要做事件的持久化,而事件内容以JSON的形式存儲,Saga log的實作非常靈活,資料庫(SQL或NoSQL),持久消息隊列,甚至普通檔案可以用作事件存儲, 當然有些能更快得幫saga恢複狀态。

注意事項

對于服務來說,實作Saga有以下這些要求:

  1. Ti和Ci是幂等的。
  2. Ci必須是能夠成功的,如果無法成功則需要人工介入。
  3. Ti - Ci和Ci - Ti的執行結果必須是一樣的:sub-transaction被撤銷了。

第一點要求Ti和Ci是幂等的,舉個例子,假設在執行Ti的時候逾時了,此時我們是不知道執行結果的,如果采用forward recovery政策就會再次發送Ti,那麼就有可能出現Ti被執行了兩次,是以要求Ti幂等。如果采用backward recovery政策就會發送Ci,而如果Ci也逾時了,就會嘗試再次發送Ci,那麼就有可能出現Ci被執行兩次,是以要求Ci幂等。

第二點要求Ci必須能夠成功,這個很好了解,因為,如果Ci不能執行成功就意味着整個Saga無法完全撤銷,這個是不允許的。但總會出現一些特殊情況比如Ci的代碼有bug、服務長時間崩潰等,這個時候就需要人工介入了。

第三點乍看起來比較奇怪,舉例說明,還是考慮Ti執行逾時的場景,我們采用了backward recovery,發送一個Ci,那麼就會有三種情況:

  1. Ti的請求丢失了,服務之前沒有、之後也不會執行Ti
  2. Ti在Ci之前執行
  3. Ci在Ti之前執行

對于第1種情況,容易處理。對于第2、3種情況,則要求Ti和Ci是可交換的(commutative),并且其最終結果都是sub-transaction被撤銷。

3 Saga協調

協調saga:saga的實作包含協調saga步驟的邏輯。當系統指令啟動saga時,協調邏輯必須選擇并告知第一個saga參與者執行本地事務。一旦該事務完成,saga的排序協調選擇并調用下一個saga參與者。這個過程一直持續到saga執行了所有步驟。如果任何本地事務失敗,則saga必須以相反的順序執行補償事務。建構一個saga的協調邏輯有幾種不同的方法:

  • 編排(Choreography):在saga參與者中配置設定決策和排序。他們主要通過交換事件進行溝通。
  • 控制(Orchestration):在saga控制類中集中saga的協調邏輯。一個saga控制者向saga參與者發送指令消息,告訴他們要執行哪些操作。

3.1 編排(Choreography)

基于編排的saga:實作sagas的一種方法是使用編排。當使用編排時,沒有中央協調員告訴saga參與者該做什麼。相反,sagas參與者訂閱彼此的事件并做出相應的響應。

分布式事務:Saga模式

通過這個sagas的路徑如下:

  1. Order Service在APPROVAL_PENDING狀态下建立一個Order并釋出OrderCreated事件。
  2. Consumer Service消費OrderCreated事件,驗證消費者是否可以下訂單,并釋出ConsumerVerified事件。
  3. Kitchen Service消費OrderCreated事件,驗證訂單,在CREATE_PENDING狀态下建立故障單,并釋出TicketCreated事件。
  4. Accounting服務消費OrderCreated事件并建立一個處于PENDING狀态的Credit CardAuthorization。
  5. Accounting Service消費TicketCreated和ConsumerVerified事件,收取消費者的信用卡,并釋出信用卡授權活動。
  6. Kitchen Service使用CreditCardAuthorized事件并更改AWAITING_ACCEPTANCE票的狀态。
  7. Order Service收到CreditCardAuthorized事件,更改訂單狀态到APPROVED,并釋出OrderApproved事件。

建立訂單saga還必須處理saga參與者拒絕訂單并釋出某種失敗事件的場景。例如,消費者信用卡的授權可能會失敗。saga必須執行補償交易以撤消已經完成的事情。圖中顯示了AccountingService無法授權消費者信用卡時的事件流。

分布式事務:Saga模式

事件順序如下:

  1. Order服務在APPROVAL_PENDING狀态下建立一個Order并釋出OrderCreated事件。
  2. Consumer服務消費OrderCreated事件,驗證消費者是否可以下訂單,并釋出ConsumerVerified事件。
  3. Kitchen服務消費OrderCreated事件,驗證訂單,在CREATE_PENDING狀态下建立故障單,并釋出TicketCreated事件。
  4. Accounting服務消費TicketCreated和ConsumerVerified事件,向消費者的信用卡收費,并釋出信用卡授權失敗事件。
  5. Kitchen服務使用信用卡授權失敗事件并将故障單的狀态更改為REJECTED。
  6. 訂單服務消費信用卡授權失敗事件,并将訂單狀态更改為已拒絕。

可靠的基于事件的通信

在實施基于編排的saga時,您必須考慮一些與服務間通信相關的問題。第一個問題是確定saga參與者更新其資料庫并将事件作為資料庫事務的一部分釋出。

您需要考慮的第二個問題是確定saga參與者必須能夠将收到的每個事件映射到自己的資料。

編組的saga的好處和缺點

基于編舞的saga有幾個好處:

  • 簡單:服務在建立,更新或删除業務時釋出事件對象
  • 松耦合:參與者訂閱事件并且彼此之間沒有直接的了解。

并且有一些缺點:

  • 更難了解:與業務流程不同,代碼中沒有一個地方可以定義saga。相反,編排在服務中配置設定saga的實作。是以,開發人員有時很難了解給定的saga是如何工作的。
  • 服務之間的循環依賴關系:saga參與者訂閱彼此的事件,這通常會建立循環依賴關系。例如,如果仔細檢查圖示,您将看到存在循環依賴關系,例如訂單服務、會計服務、訂單服務。雖然這不一定是個問題,但循環依賴性被認為是設計問題。
  • 緊密耦合的風險:每個saga參與者都需要訂閱所有影響他們的事件。例如,會計服務必須訂閱導緻消費者信用卡被收費或退款的所有事件。是以,存在一種風險,即需要與Order Service實施的訂單生命周期保持同步更新。

3.2 控制(Orchestration)

控制是實作sagas的另一種方式。使用業務流程時,您可以定義一個控制類,其唯一的職責是告訴saga參與者該做什麼。 saga控制使用指令/異步回複樣式互動與參與者進行通信。

分布式事務:Saga模式
  • Order Service首先建立一個Order和一個建立訂單控制器。之後,路徑的流程如下:
  • saga orchestrator向Consumer Service發送Verify Consumer指令。
  • Consumer Service回複Consumer Verified消息。
  • saga orchestrator向Kitchen Service發送Create Ticket指令。
  • Kitchen Service回複Ticket Created消息。
  • saga協調器向Accounting Service發送授權卡消息。
  • Accounting服務部門使用卡片授權消息回複。
  • saga orchestrator向Kitchen Service發送Approve Ticket指令。
  • saga orchestrator向訂單服務發送準許訂單指令。

使用狀态機模組化SAGA ORCHESTRATORS

模組化saga orchestrator的好方法是作為狀态機。狀态機由一組狀态和一組由事件觸發的狀态之間的轉換組成。每個transition都可以有一個action,對于一個saga來說是一個saga參與者的調用。狀态之間的轉換由saga參與者執行的本地事務的完成觸發。目前狀态和本地事務的特定結果決定了狀态轉換以及執行的操作(如果有的話)。對狀态機也有有效的測試政策。是以,使用狀态機模型可以更輕松地設計、實施和測試。

分布式事務:Saga模式

圖顯示了Create Order Saga的狀态機模型。此狀态機由多個狀态組成,包括以下内容:

  • Verifying Consumer:初始狀态。當處于此狀态時,該saga正在等待消費者服務部門驗證消費者是否可以下訂單。
  • Creating Ticket:該saga正在等待對建立票證指令的回複。
  • Authorizing Card:等待Accounting服務授權消費者的信用卡。
  • OrderApproved:表示saga成功完成的最終狀态。
  • Order Rejected:最終狀态表明該訂單被其中一方參與者們拒絕。

SAGA ORCHESTRATION和TRANSACTIONAL MESSAGING

基于業務流程的saga的每個步驟都包括更新資料庫和釋出消息的服務。例如,Order Service持久儲存Order和Create Order Saga orchestrator,并向第一個saga參與者發送消息。一個saga參與者,例如Kitchen Service,通過更新其資料庫并發送回複消息來處理指令消息。 Order Service通過更新saga協調器的狀态并向下一個saga參與者發送指令消息來處理參與者的回複消息。服務必須使用事務性消息傳遞,以便自動更新資料庫并釋出消息。

讓我們來看看使用saga編排的好處和缺點。

基于ORCHESTRATION的SAGAS的好處和缺點

基于編排的saga有幾個好處:

  1. 更簡單的依賴關系:編排的一個好處是它不會引入循環依賴關系。 saga orchestrator調用saga參與者,但參與者不會調用orchestrator。是以,協調器依賴于參與者,但反之亦然,是以沒有循環依賴性。
  2. 較少的耦合:每個服務都實作了由orchestrator調用的API,是以它不需要知道saga參與者釋出的事件。
  3. 改善關注點分離并簡化業務邏輯:saga協調邏輯本地化在saga協調器中。域對象更簡單,并且不了解它們參與的saga。例如,當使用編排時,Order類不知道任何saga,是以它具有更簡單的狀态機模型。在執行建立訂單saga期間,它直接從APPROVAL_PENDING狀态轉換到APPROVED狀态。 Order類沒有與saga的步驟相對應的任何中間狀态。是以,業務更加簡單。

業務流程也有一個缺點:

  • 在協調器中集中過多業務邏輯的風險。這導緻了一種設計,其中智能協調器告訴啞巴服務要做什麼操作。幸運的是,您可以通過設計獨立負責排序的協調器來避免此問題,并且不包含任何其他業務邏輯。

除了最簡單的saga,我建議使用編排。為您的saga實施協調邏輯隻是您需要解決的設計問題之一。

 參考示例

  • Saga
  • Eventual Data Consistency Solution in ServiceComb - part 1
  • Eventual Data Consistency Solution in ServiceComb - part 2
  • Eventual Data Consistency Solution in ServiceComb - part 3
  • 微服務場景下的資料一緻性解決方案
  • Microservices Patterns by Chris Richardson

繼續閱讀