分佈式事務解決方案以及 .Net Core 下的實現(上)

數據一致性是構建業務系統須要考慮的重要問題 , 以往咱們是依靠數據庫來保證數據的一致性。可是在微服務架構以及分佈式環境下實現數據一致性是一個頗有挑戰的的問題。最近在研究分佈式事物,分佈式的解決方案有不少解決方案,也讓我在研究的同時也引起了不少思考。今天我想講的是分佈式事物解決方案是和saga有關。html

原文地址:微服務場景下的數據一致性解決方案

PPT地址:Saga分佈式事務解決方案與實踐

incubator-servicecomb-saga地址:incubator-servicecomb-saga

servicecomb-saga-csharp(servicecomb-saga netcore sdk)地址:servicecomb-saga-csharp

根據原文作一些解釋性的地方 方便更加理解git

單體應用的數據一致性

我就給你們講一個國外常常用到的例子吧,就是假若有一家大型的企業,下屬有航空公司、租車公司、和連鎖酒店。這個大公司爲客戶提供一站式的旅遊行程規劃服務,這樣客戶只須要提供出行目的地, 這個大公司能幫助客戶預訂機票、租車、以及預訂酒店。從業務的角度,咱們必須保證上述三個服務的預訂都完成才能知足一個成功的旅遊行程,不然不能成行。github

咱們的單體應用要知足這個需求很是簡單,只需將這個三個服務請求放到同一個數據庫事務中,數據庫會幫咱們保證所有成功或者所有回滾。數據庫

image.png

這三個服務上線公司滿意,客戶也很滿意apache

微服務場景下的數據一致性

隨之時間的推移,這個大企業的行程規劃服務很是成功,用戶量劇增上百倍。企業的下屬航空公司、租車公司、和連鎖酒店也相繼推出了更多服務以知足客戶需求, 咱們的應用和開發團隊也所以日漸龐大。現在咱們的單體應用已變得如此複雜,以致於沒人瞭解整個應用是怎麼運做的。更糟的是新功能的上線如今須要全部研發團隊合做, 日夜奮戰數週才能完成。看着市場佔有率每況愈下,公司高層對研發部門愈來愈不滿意。服務器

通過數輪討論,領導最終決定將龐大的單體應用一分爲四:機票預訂服務、租車服務、酒店預訂服務、和支付服務。服務各自使用本身的數據庫,並經過HTTP協議通訊。 負責各服務的團隊根據市場需求按照本身的開發節奏發版上線。現在咱們面臨新的挑戰:如何保證最初三個服務的預訂都完成才能知足一個成功的旅遊行程, 不然不能成行的業務規則?如今服務有各自的邊界,並且數據庫選型也不盡相同,經過數據庫保證數據一致性的方案已不可行。
image.png網絡

Sagas

通過一段時間的查找,我發現了一篇論文,1987年Hector & Kenneth 發表論文 Sagas論文地址數據結構

Saga是一個長活事務(Long Live Transaction (LLT)),可被分解成能夠交錯運行的子事務集合。其中每一個子事務都是一個保持數據庫一致性的真實事務(LLT = T1 + T2 + T3 + ... + Tn)。每一個本地事務Tx 有對應的補償 Cx。架構

在大企業的業務場景下,一個行程規劃的事務就是一個Saga,其中包含四個子事務:機票預訂、租車、酒店預訂、和支付。併發

image.png

根據上面提到的公式

當每一個saga子事務 T1, T2, …, Tn 都有對應的補償定義 C1, C2, …, Cn-1, 那麼saga系統能夠保證 [1]子事務序列 T1, T2, …, Tn得以完成 (最佳狀況)或者序列
T1, T2, …, Tj, Cj, …,
C2, C1, 0 < j < n,
得以完成

image.png

換句話說,經過上述定義的事務/補償,saga保證知足如下業務規則:

全部的預訂都被執行成功,若是任何一個失敗,都會被取消
若是最後一步付款失敗,全部預訂也將被取消,這些取消就是所謂的補償。

Saga的恢復方式

原論文中描述了兩種類型的Saga恢復方式:

向後恢復 補償全部已完成的事務,若是任一子事務失敗。向前恢復 重試失敗的事務,假設每一個子事務最終都會成功

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

理論上補償事務永不失敗,然而,在分佈式世界中,咱們來想一想極端的狀況,無非就是往三種可能去考慮,成功,失敗,超時(有可能成功,也有可能失敗)。那麼服務器可能會宕機,網絡可能會失敗,甚至數據中心也可能會停電。在這種狀況下咱們能作些什麼? 最後的手段是提供回退措施,好比人工干預。

補充說明:ACID與SAGA

  • 原子性(Atomicity):Saga只提供ACD保證,原子性(經過Saga協調器實現)
  • 一致性(Consistency):本地事務 + Saga log
  • 隔離性(Isolation):Saga不保證
  • 持久性(Durability):Saga log 提供
有不少朋友會說怎麼不提供隔離性啊?

例子地址:地址

  • 兩個Saga事務同時操做一個資源會出現數據語義不一致的的狀況
  • 兩個Saga事務同時操做一個訂單 ,彼此操做會覆蓋對方(更新丟失)
  • 兩個Saga事務同時訪問扣款帳號,沒法看到退款 (髒讀取問題)
  • 在一個Saga事務內,數據被其餘事務修改先後的讀取值不一致(模糊讀取問題)
面對以上問題咱們應該如何應對隔離性問題呢?

下面給出對應的解決方案

  • 隔離的本質是控制併發,防止併發事務操做相同資源而引發結果錯亂
  • 在應用層面加入邏輯鎖的邏輯。
  • 業務層面採用預先凍結資金的方式隔離此部分資金。
  • 業務操做過程當中經過及時讀取當前狀態的方式獲取更新。

使用Saga的條件

Saga看起來頗有但願知足咱們的需求。全部長活事務均可以這樣作嗎?這裏有一些限制:

Saga只容許兩個層次的嵌套,頂級的Saga和簡單子事務 [1]
在外層,全原子性不能獲得知足。也就是說,sagas可能會看到其餘sagas的部分結果 [1]
每一個子事務應該是獨立的原子行爲 [2]
在咱們的業務場景下,航班預訂、租車、酒店預訂和付款是天然獨立的行爲,並且每一個事務均可以用對應服務的數據庫保證原子操做。
咱們在行程規劃事務層面也不須要原子性。一個用戶能夠預訂最後一張機票,然後因爲信用卡餘額不足而被取消。同時另外一個用戶可能開始會看到已無餘票, 接着因爲前者預訂被取消,最後一張機票被釋放,而搶到最後一個座位並完成行程規劃。

補償也有需考慮的事項:

補償事務從語義角度撤消了事務Ti的行爲,但未必能將數據庫返回到執行Ti時的狀態。(例如,若是事務觸發導彈發射, 則可能沒法撤消此操做)
但這對咱們的業務來講不是問題。其實難以撤消的行爲也有可能被補償。例如,發送電郵的事務能夠經過發送解釋問題的另外一封電郵來補償。

如今咱們有了經過Saga來解決數據一致性問題的方案。它容許咱們成功地執行全部事務,或在任何事務失敗的狀況下,補償已成功的事務。 雖然Saga不提供ACID保證,但仍適用於許多數據最終一致性的場景。那咱們如何設計一個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事務請求的結束,不須要保存任何內容

image.png

經過將這些事件持久化在saga log中,咱們能夠將saga恢復到上述任何狀態。

因爲Saga只須要作事件的持久化,而事件內容以JSON的形式存儲,Saga log的實現很是靈活,數據庫(SQL或NoSQL),持久消息隊列,甚至普通文件能夠用做事件存儲, 固然有些能更快得幫saga恢復狀態。

Saga請求的數據結構

在咱們的業務場景下,航班預訂、租車、和酒店預訂沒有依賴關係,能夠並行處理,但對於咱們的客戶來講,只在全部預訂成功後一次付費更加友好。 那麼這四個服務的事務關係能夠用下圖表示:

image.png

將行程規劃請求的數據結構實現爲有向非循環圖剛好合適。 圖的根是saga啓動任務,葉是saga結束任務。

image.png

Parallel Saga

如上所述,航班預訂,租車和酒店預訂能夠並行處理。可是這樣作會形成另外一個問題:若是航班預訂失敗,而租車正在處理怎麼辦?咱們不能一直等待租車服務迴應, 由於不知道須要等多久。

最好的辦法是再次發送租車請求,得到迴應,以便咱們可以繼續補償操做。但若是租車服務永不迴應,咱們可能須要採起回退措施,好比手動干預。

超時的預訂請求可能最後仍被租車服務收到,這時服務已經處理了相同的預訂和取消請求。

image.png

所以,服務的實現必須保證補償請求執行之後,再次收到的對應事務請求無效。 Caitie McCaffrey在她的演講Distributed Sagas: A Protocol for Coordinating Microservices中把這個稱爲可交換的補償請求 (commutative compensating request)。

分佈式saga架構

分佈式的Saga借鑑了zipkin的思想,Omega就是相似探針的形式,上報saga事件,而後Alpha是屬於Saga的ProcessManager.也就是協調器的東西。

  • alpha充當協調者的角色,主要負責對事務的事件進行持久化存儲以及協調子事務的狀態,使其得以最終與全局事務的狀態保持一致。
  • omega是微服務中內嵌的一個agent,負責對網絡請求進行攔截並向alpha上報事務事件,並在異常狀況下根據alpha下發的指令執行相應的補償操做。

image.png

接下來咱們看下Omega的內部實現

omega是微服務中內嵌的一個agent。當服務收到請求時,omega會將其攔截並從中提取請求信息中的全局事務id做爲其自身的全局事務id(即Saga事件id),並提取本地事務id做爲其父事務id。在預處理階段,alpha會記錄事務開始的事件;在後處理階段,alpha會記錄事務結束的事件。所以,每一個成功的子事務都有一一對應的開始及結束事件。

image.png

咱們再看下 他們是如何通訊的

服務間通訊的流程與Zipkin的相似。在服務生產方,omega會攔截請求中事務相關的id來提取事務的上下文。在服務消費方,omega會在請求中注入事務相關的id來傳遞事務的上下文。經過服務提供方和服務消費方的這種協做處理,子事務能鏈接起來造成一個完整的全局事務。

image.png

藉助zipkin的思想就可讓整一個事務組造成一個鏈式結構。

image.png

Saga 具體處理流程

Saga處理場景是要求相關的子事務提供事務處理函數同時也提供補償函數。Saga協調器alpha會根據事務的執行狀況向omega發送相關的指令,肯定是否向前重試或者向後恢復。

成功場景
成功場景下,每一個事務都會有開始和有對應的結束事件。
image.png

異常場景
異常場景下,omega會向alpha上報中斷事件,而後alpha會向該全局事務的其它已完成的子事務發送補償指令,確保最終全部的子事務要麼都成功,要麼都回滾。
image.png

超時場景 (須要調整)
超時場景下,已超時的事件會被alpha的按期掃描器檢測出來,與此同時,該超時事務對應的全局事務也會被中斷。
image.png

以上都是介紹完incubator-servicecomb-saga 整體架構。我以爲它的idea很nice,因此我和水哥,還有老杜作了一個頗有趣的事情。什麼事情呢?就是實現了Omega這個客戶端,github地址在這裏:servicecomb-saga-csharp,目前實現上面的三種場景。

下篇結合實際的sample和你們講解下netcore下的實現,這篇文章讓你們總體的瞭解什麼是saga。

相關文章
相關標籤/搜索