以電商系統爲例,聊聊分佈式事務
如今每個人(包括我)都在思考、構建微服務,分佈式系統是微服務的核心原則和一切實現的上下文。
1 什麼是分佈式事務?
跨越網絡上多個物理系統或計算機的事務被簡單的稱爲分佈式事務。在微服務世界中,事務被分割到多個服務中,需要按順序調用這些服務以完成整個事務。
下面是一個單體電子商務系統使用事務的例子:
圖 1:單體中的事務
在上面的系統中,如果用戶向平臺發送 Checkout 請求,平臺將創建一個本地數據庫事務,該事務操作多個數據庫表,以處理訂單並從庫存中保留商品。如果有任何步驟失敗,事務(包括訂單和保留的商品)可以回滾。這被稱爲 ACID(原子性 Atomicity、一致性 Consistency、隔離性 Isolation、持久性 Durability),由數據庫系統保證。
下面是電子商務系統分解爲微服務的情況:
圖 2:微服務中的事務
當我們解耦這個系統時,創建了微服務 OrderMicroservice 和 InventoryMicroservice,各自有獨立的數據庫。當用戶發起 Checkout 請求時,這兩個微服務都將被調用從而將更改應用到各自的數據庫中。因爲事務是通過多個系統跨多個數據庫的,所以現在這是一個分佈式事務。
2 微服務中的分佈式事務有什麼問題?
隨着微服務體系架構的出現,事務可以跨越多個微服務,從而跨越數據庫,因此我們現在無法利用數據庫的 ACID 特性,從而面臨以下關鍵問題:
如何保持事務的原子性?
原子性意味着事務要麼完成所有步驟,要麼沒有完成任何步驟。在上面的例子中,如果 InventoryMicroservice 方法中的 “保留商品” 失敗,如何回滾 OrderMicroservice 應用的“處理訂單”?
如何處理併發請求?
如果某個微服務的對象被持久化到數據庫中,同時有另一個請求讀取相同的對象。服務應該返回舊數據還是新數據?在上面的例子中,一旦 OrderMicroservice 已經完成,那麼 InventoryMicroservice 在執行更新的過程時,客戶下單的請求中應該包括當前的訂單嗎?
如今,系統應該爲失敗而設計,其中主要的問題就是處理分佈式事務。下面引用 Pat Helland 的話:
一般來說,應用程序開發人員不會簡單的就能實現支持分佈式事務的大型可伸縮應用系統。
3 可能的解決方案
在設計和構建基於微服務的應用時,上述兩個問題非常關鍵。爲了解決這些問題,下面列舉幾種方法:
-
兩階段提交(Two-Phase Commit)
-
最終一致性和補償(Eventual Consistency and Compensation)/SAGA
兩階段提交
顧名思義,這種處理事務的方式有兩個階段,準備階段和提交階段,其中起到重要作用的是事務協調器(Transaction Coordinator),負責維護事務的生命週期。
工作方式:
在準備階段,所有涉及到的微服務都準備提交,並通知協調器已經準備好完成事務。然後在提交階段,事務協調器向所有微服務發出提交或回滾命令。
以電子商務系統爲例。
圖 3:在微服務上成功的兩階段提交
在上面的示例中(圖 3),當用戶發送 Checkout 請求時,TransactionCoordinator 將發起一個帶有所有上下文信息的全局事務。首先,向 OrderMicroservice 發送 prepare 命令創建訂單。然後,向 InventoryMicroservice 發送 prepare 命令保留商品。
當兩個服務都可以執行更改時,它們將鎖定對象,不再接受其他更改,並通知 TransactionCoordinator。一旦 TransactionCoordinator 確認所有微服務都已準備好應用更改,就會通過請求事務 commit 來要求這些微服務持久化所作的更改,然後所有對象才能被解鎖。
圖 4:在微服務上失敗的兩階段提交
在失敗的場景中(圖 4)——如果在任何時候有某個微服務沒有做好準備,TransactionCoordinator 將中止事務併發起回滾流程。圖中由於某種原因,OrderMicroservice 未能創建訂單,但是 InventoryMicroservice 已經回覆說它準備創建訂單。TransactionCoordinator 將請求 InventoryMicroservice 中止創建訂單,並回滾所做的任何更改、解鎖數據庫對象。
優點:
-
該方法保證事務是原子的。交易結束時,要麼所有微服務都成功,要麼所有微服務都沒有改變。
-
其次,允許讀寫分離,在事務協調器提交更改之前,對象上的更改是不可見的。
-
這種方法通過同步調用通知客戶端成功或失敗。
缺點:
-
沒什麼事情是完美的,兩階段提交與單個微服務的處理時間比起來慢很多,並且高度依賴於事務協調器,在高負載期間,事務協調器確實會降低系統的速度。
-
另一個主要缺點是數據庫行鎖定,該鎖可能成爲性能瓶頸,並且可能出現兩個事務相互鎖定造成的死鎖。
最終一致性和補償 / SAGA
最終一致性的最佳定義之一是 microservices.io 描述的:每個服務在更新數據時發佈一個事件。其他服務訂閱事件,當接收到事件時,更新其數據。
在這種方法中,分佈式事務由相關微服務上的異步本地事務來完成,微服務通過事件總線相互通信。
工作方式:
再以電子商務系統爲例。
圖 5:最終的一致性 / SAGA,成功的場景
在上面的例子中(圖 5),客戶端請求系統處理訂單。在處理過程中,Choreographer 發出一個 Create Order 事件,表示開始一個事務。OrderMicroservice 監聽到這個事件並創建一個訂單,如果成功,發出一個 Order Created 事件。Choreographer 偵聽此事件,並通過發出 Reserve items 事件繼續保留商品。InventoryMicroservice 偵聽此事件並保留商品,如果成功,發出 Items Reserved 事件。在這個例子中,這意味着事務的結束。
微服務之間所有基於事件的通信都是通過事件總線進行的,並由另一個系統編排以解決複雜性問題。
圖 6:最終的一致性 / SAGA,失敗場景
如果由於任何原因 InventoryMicroservice 未能保留商品(圖 6),它會發出 Failed to Reserve Items 事件。Choreographer 偵聽此事件,並通過發出 Delete Order 事件啓動補償事務。OrderMicroservice 偵聽此事件並刪除所創建的訂單。
優點:
這種方法的一大優點是每個微服務只關注自己的原子事務。如果某個服務花費了更長的時間,其他微服務不會被阻塞,這也意味着不需要數據庫鎖。由於其基於異步事件的解決方案,這種方法可以使系統在高負載下具有高度的可伸縮性。
缺點:
該方法的主要缺點是沒有讀取隔離。這意味着在上面的示例中,客戶端可以看到已創建的訂單,但在下一秒中,由於補償事務,訂單會被刪除。此外,當微服務的數量增加時,調試和維護就變得更加困難。
4 結論
首先儘量避免分佈式事務,如果正在構建新應用,那麼就從單體開始,如 Martin Fowler 在 MonolithFirst 中所描述的那樣:
更常見的方法是從單體開始,逐漸剝離邊緣的微服務。這種方法可以在微服務體系架構的核心留下一個巨大的單體,大多數新的開發都發生在微服務中,而這個單體相對來說變化不大。
當一個事件需要在兩個地方更新數據時,與兩階段提交相比,最終一致性 / SAGA 方案是處理分佈式事務的更好的方式,主要原因是兩階段提交在分佈式環境中不能伸縮。不過最終一致性方案引入了新問題,例如如何以原子方式更新數據庫和發出事件,因此採用這種方案需要開發和測試團隊改變思維方式。
本文源自公衆號 DeepNoMind,分佈式實驗室已獲完整授權。
分佈式實驗室 關注分佈式相關的開源項目和基礎架構,致力於分析並報道這些新技術是如何以及將會怎樣影響企業的軟件構建方式。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/p6CIELU5cku1R-8XmU4Tlg