數據一致性是構建業務系統須要考慮的重要問題 , 以往咱們是依靠數據庫來保證數據的一致性。可是在微服務架構以及分佈式環境下實現數據一致性是一個頗有挑戰的的問題。最近在研究分佈式事物,分佈式的解決方案有不少解決方案,也讓我在研究的同時也引起了不少思考。今天我想講的是分佈式事物解決方案是和saga有關。html
根據原文作一些解釋性的地方 方便更加理解git
我就給你們講一個國外常常用到的例子吧,就是假若有一家大型的企業,下屬有航空公司、租車公司、和連鎖酒店。這個大公司爲客戶提供一站式的旅遊行程規劃服務,這樣客戶只須要提供出行目的地, 這個大公司能幫助客戶預訂機票、租車、以及預訂酒店。從業務的角度,咱們必須保證上述三個服務的預訂都完成才能知足一個成功的旅遊行程,不然不能成行。github
咱們的單體應用要知足這個需求很是簡單,只需將這個三個服務請求放到同一個數據庫事務中,數據庫會幫咱們保證所有成功或者所有回滾。數據庫
這三個服務上線公司滿意,客戶也很滿意apache
隨之時間的推移,這個大企業的行程規劃服務很是成功,用戶量劇增上百倍。企業的下屬航空公司、租車公司、和連鎖酒店也相繼推出了更多服務以知足客戶需求, 咱們的應用和開發團隊也所以日漸龐大。現在咱們的單體應用已變得如此複雜,以致於沒人瞭解整個應用是怎麼運做的。更糟的是新功能的上線如今須要全部研發團隊合做, 日夜奮戰數週才能完成。看着市場佔有率每況愈下,公司高層對研發部門愈來愈不滿意。服務器
通過數輪討論,領導最終決定將龐大的單體應用一分爲四:機票預訂服務、租車服務、酒店預訂服務、和支付服務。服務各自使用本身的數據庫,並經過HTTP協議通訊。 負責各服務的團隊根據市場需求按照本身的開發節奏發版上線。現在咱們面臨新的挑戰:如何保證最初三個服務的預訂都完成才能知足一個成功的旅遊行程, 不然不能成行的業務規則?如今服務有各自的邊界,並且數據庫選型也不盡相同,經過數據庫保證數據一致性的方案已不可行。
網絡
通過一段時間的查找,我發現了一篇論文,1987年Hector & Kenneth 發表論文 Sagas論文地址數據結構
Saga是一個長活事務(Long Live Transaction (LLT)),可被分解成能夠交錯運行的子事務集合。其中每一個子事務都是一個保持數據庫一致性的真實事務(LLT = T1 + T2 + T3 + ... + Tn)。每一個本地事務Tx 有對應的補償 Cx。架構
在大企業的業務場景下,一個行程規劃的事務就是一個Saga,其中包含四個子事務:機票預訂、租車、酒店預訂、和支付。併發
根據上面提到的公式
當每一個saga子事務 T1, T2, …, Tn 都有對應的補償定義 C1, C2, …, Cn-1, 那麼saga系統能夠保證 [1]子事務序列 T1, T2, …, Tn得以完成 (最佳狀況)或者序列
T1, T2, …, Tj, Cj, …,
C2, C1, 0 < j < n,
得以完成
換句話說,經過上述定義的事務/補償,saga保證知足如下業務規則:
全部的預訂都被執行成功,若是任何一個失敗,都會被取消
若是最後一步付款失敗,全部預訂也將被取消,這些取消就是所謂的補償。
原論文中描述了兩種類型的Saga恢復方式:
向後恢復 補償全部已完成的事務,若是任一子事務失敗。向前恢復 重試失敗的事務,假設每一個子事務最終都會成功
顯然,向前恢復沒有必要提供補償事務,若是你的業務中,子事務(最終)總會成功,或補償事務難以定義或不可能,向前恢復更符合你的需求。
理論上補償事務永不失敗,然而,在分佈式世界中,咱們來想一想極端的狀況,無非就是往三種可能去考慮,成功,失敗,超時(有可能成功,也有可能失敗)。那麼服務器可能會宕機,網絡可能會失敗,甚至數據中心也可能會停電。在這種狀況下咱們能作些什麼? 最後的手段是提供回退措施,好比人工干預。
補充說明:ACID與SAGA
例子地址:地址
下面給出對應的解決方案
Saga看起來頗有但願知足咱們的需求。全部長活事務均可以這樣作嗎?這裏有一些限制:
Saga只容許兩個層次的嵌套,頂級的Saga和簡單子事務 [1]
在外層,全原子性不能獲得知足。也就是說,sagas可能會看到其餘sagas的部分結果 [1]
每一個子事務應該是獨立的原子行爲 [2]
在咱們的業務場景下,航班預訂、租車、酒店預訂和付款是天然獨立的行爲,並且每一個事務均可以用對應服務的數據庫保證原子操做。
咱們在行程規劃事務層面也不須要原子性。一個用戶能夠預訂最後一張機票,然後因爲信用卡餘額不足而被取消。同時另外一個用戶可能開始會看到已無餘票, 接着因爲前者預訂被取消,最後一張機票被釋放,而搶到最後一個座位並完成行程規劃。
補償也有需考慮的事項:
補償事務從語義角度撤消了事務Ti的行爲,但未必能將數據庫返回到執行Ti時的狀態。(例如,若是事務觸發導彈發射, 則可能沒法撤消此操做)
但這對咱們的業務來講不是問題。其實難以撤消的行爲也有可能被補償。例如,發送電郵的事務能夠經過發送解釋問題的另外一封電郵來補償。
如今咱們有了經過Saga來解決數據一致性問題的方案。它容許咱們成功地執行全部事務,或在任何事務失敗的狀況下,補償已成功的事務。 雖然Saga不提供ACID保證,但仍適用於許多數據最終一致性的場景。那咱們如何設計一個Saga系統?
Saga保證全部的子事務都得以完成或補償,但Saga系統自己也可能會崩潰。Saga崩潰時可能處於如下幾個狀態:
補償事務已開始但還沒有完成。解決方案與上一個相同。這意味着補償事務也必須是冪等的。
全部子事務或補償事務均已完成,與第一種狀況相同。
爲了恢復到上述狀態,咱們必須追蹤子事務及補償事務的每一步。咱們決定經過事件的方式達到以上要求,並將如下事件保存在名爲saga log的持久存儲中:
經過將這些事件持久化在saga log中,咱們能夠將saga恢復到上述任何狀態。
因爲Saga只須要作事件的持久化,而事件內容以JSON的形式存儲,Saga log的實現很是靈活,數據庫(SQL或NoSQL),持久消息隊列,甚至普通文件能夠用做事件存儲, 固然有些能更快得幫saga恢復狀態。
在咱們的業務場景下,航班預訂、租車、和酒店預訂沒有依賴關係,能夠並行處理,但對於咱們的客戶來講,只在全部預訂成功後一次付費更加友好。 那麼這四個服務的事務關係能夠用下圖表示:
將行程規劃請求的數據結構實現爲有向非循環圖剛好合適。 圖的根是saga啓動任務,葉是saga結束任務。
如上所述,航班預訂,租車和酒店預訂能夠並行處理。可是這樣作會形成另外一個問題:若是航班預訂失敗,而租車正在處理怎麼辦?咱們不能一直等待租車服務迴應, 由於不知道須要等多久。
最好的辦法是再次發送租車請求,得到迴應,以便咱們可以繼續補償操做。但若是租車服務永不迴應,咱們可能須要採起回退措施,好比手動干預。
超時的預訂請求可能最後仍被租車服務收到,這時服務已經處理了相同的預訂和取消請求。
所以,服務的實現必須保證補償請求執行之後,再次收到的對應事務請求無效。 Caitie McCaffrey在她的演講Distributed Sagas: A Protocol for Coordinating Microservices中把這個稱爲可交換的補償請求 (commutative compensating request)。
分佈式的Saga借鑑了zipkin的思想,Omega就是相似探針的形式,上報saga事件,而後Alpha是屬於Saga的ProcessManager.也就是協調器的東西。
接下來咱們看下Omega的內部實現
omega是微服務中內嵌的一個agent。當服務收到請求時,omega會將其攔截並從中提取請求信息中的全局事務id做爲其自身的全局事務id(即Saga事件id),並提取本地事務id做爲其父事務id。在預處理階段,alpha會記錄事務開始的事件;在後處理階段,alpha會記錄事務結束的事件。所以,每一個成功的子事務都有一一對應的開始及結束事件。
咱們再看下 他們是如何通訊的
服務間通訊的流程與Zipkin的相似。在服務生產方,omega會攔截請求中事務相關的id來提取事務的上下文。在服務消費方,omega會在請求中注入事務相關的id來傳遞事務的上下文。經過服務提供方和服務消費方的這種協做處理,子事務能鏈接起來造成一個完整的全局事務。
藉助zipkin的思想就可讓整一個事務組造成一個鏈式結構。
Saga處理場景是要求相關的子事務提供事務處理函數同時也提供補償函數。Saga協調器alpha會根據事務的執行狀況向omega發送相關的指令,肯定是否向前重試或者向後恢復。
成功場景
成功場景下,每一個事務都會有開始和有對應的結束事件。
異常場景
異常場景下,omega會向alpha上報中斷事件,而後alpha會向該全局事務的其它已完成的子事務發送補償指令,確保最終全部的子事務要麼都成功,要麼都回滾。
超時場景 (須要調整)
超時場景下,已超時的事件會被alpha的按期掃描器檢測出來,與此同時,該超時事務對應的全局事務也會被中斷。
以上都是介紹完incubator-servicecomb-saga 整體架構。我以爲它的idea很nice,因此我和水哥,還有老杜作了一個頗有趣的事情。什麼事情呢?就是實現了Omega這個客戶端,github地址在這裏:servicecomb-saga-csharp,目前實現上面的三種場景。
下篇結合實際的sample和你們講解下netcore下的實現,這篇文章讓你們總體的瞭解什麼是saga。