事務 - Saga模式

Saga

githubgit

1987年普林斯頓大學的Hector Garcia-Molina和Kenneth Salem發表了一篇Paper Sagas,講述的是如何處理long lived transaction(長活事務)。聽起來是否是以爲和分佈式事務很像?沒錯,下面來看看這個來自1987年的解決方案是如何啓發當今的分佈式事務問題的。github

協議介紹

Saga的組成:apache

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

能夠看到,和TCC相比,Saga沒有「預留」動做,它的Ti就是直接提交到庫。segmentfault

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

對於ACID的保證

Saga對於ACID的保證和TCC同樣:接口

  • A,正常狀況下保證。
  • C,在某個時間點,會出現A庫和B庫的數據違反一致性要求的狀況,可是最終是一致的。
  • I,在某個時間點,A事務可以讀到B事務部分提交的結果。
  • D,和本地事務同樣,只要commit則數據被持久。

和TCC對比

Saga相比TCC的缺點是缺乏預留動做,致使補償動做的實現比較麻煩:Ti就是commit,好比一個業務是發送郵件,在TCC模式下,先保存草稿(Try)再發送(Confirm),撤銷的話直接刪除草稿(Cancel)就好了。而Saga則就直接發送郵件了(Ti),若是要撤銷則得再發送一份郵件說明撤銷(Ci),實現起來有一些麻煩。事務

若是把上面的發郵件的例子換成:A服務在完成Ti後當即發送Event到ESB(企業服務總線,能夠認爲是一個消息中間件),下游服務監聽到這個Event作本身的一些工做而後再發送Event到ESB,若是A服務執行補償動做Ci,那麼整個補償動做的層級就很深。ci

不過沒有預留動做也能夠認爲是優勢:資源

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

實現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種狀況,容易處理。對於第二、3種狀況,則要求Ti和Ci是可交換的(commutative),而且其最終結果都是sub-transaction被撤銷。

參考資料

相關文章
相關標籤/搜索