電商系統的分佈式事務調優

     如今,大部分公司的服務基本都實現了微服務化,首先是業務需求,爲了解耦業務;其次是爲了減少業務與業務之間的相互影響。

    電商系統亦是如此,大部分公司的電商系統都是分爲了不同服務模塊,例如商品模塊、訂單模塊、庫存模塊等等。事實上,分解服務是一把雙刃劍,可以帶來一些開發、性能以及運維上的優勢,但同時也會增加業務開發的邏輯複雜度。其中最爲突出的就是分佈式事務了。

     通常,存在分佈式事務的服務架構部署有以下兩種:同服務不同數據庫,不同服務不同數據庫。我們以商城爲例,用圖示說明下這兩種部署:

通常,我們都是基於第二種架構部署實現的,那我們應該如何實現在這種服務架構下,有關訂單提交業務的分佈式事務呢?

分佈式事務解決方案

    我們講過,在單個數據庫的情況下,數據事務操作具有 ACID 四個特性,但如果在一個事務中操作多個數據庫,則無法使用數據庫事務來保證一致性。也就是說,當兩個數據庫操作數據時,可能存在一個數據庫操作成功,而另一個數據庫操作失敗的情況,我們無法通過單個數據庫事務來回滾兩個數據操作。

    而分佈式事務就是爲了解決在同一個事務下,不同節點的數據庫操作數據不一致的問題。在一個事務操作請求多個服務或多個數據庫節點時,要麼所有請求成功,要麼所有請求都失敗回滾回去。通常,分佈式事務的實現有多種方式,例如 XA 協議實現的二階提交(2PC)、三階提交 (3PC),以及 TCC 補償性事務。

    在瞭解 2PC 和 3PC 之前,我們有必要先來了解下 XA 協議。XA 協議是由 X/Open 組織提出的一個分佈式事務處理規範,目前 MySQL 中只有 InnoDB 存儲引擎支持 XA 協議。

XA 規範

    在 XA 規範之前,存在着一個 DTP 模型,該模型規範了分佈式事務的模型設計。

    DTP 規範中主要包含了 AP、RM、TM 三個部分,其中 AP 是應用程序,是事務發起和結束的地方;RM 是資源管理器,主要負責管理每個數據庫的連接數據源;TM 是事務管理器,負責事務的全局管理,包括事務的生命週期管理和資源的分配協調等。

    XA 則規範了 TM 與 RM 之間的通信接口,在 TM 與多個 RM 之間形成一個雙向通信橋樑,從而在多個數據庫資源下保證 ACID 四個特性。

二階提交和三階提交

XA 規範實現的分佈式事務屬於二階提交事務,顧名思義就是通過兩個階段來實現事務的提交。

     在第一階段,應用程序向事務管理器(TM)發起事務請求,而事務管理器則會分別向參與的各個資源管理器(RM)發送事務預處理請求(Prepare),此時這些資源管理器會打開本地數據庫事務,然後開始執行數據庫事務,但執行完成後並不會立刻提交事務,而是向事務管理器返回已就緒(Ready)或未就緒(Not Ready)狀態。如果各個參與節點都返回狀態了,就會進入第二階段。

    到了第二階段,如果資源管理器返回的都是就緒狀態,事務管理器則會向各個資源管理器發送提交(Commit)通知,資源管理器則會完成本地數據庫的事務提交,最終返回提交結果給事務管理器。

    在第二階段中,如果任意資源管理器返回了未就緒狀態,此時事務管理器會向所有資源管理器發送事務回滾(Rollback)通知,此時各個資源管理器就會回滾本地數據庫事務,釋放資源,並返回結果通知。

第一,在整個流程中,我們會發現各個資源管理器節點存在阻塞,只有當所有的節點都準備完成之後,事務管理器纔會發出進行全局事務提交的通知,這個過程如果很長,則會有很多節點長時間佔用資源,從而影響整個節點的性能。一旦資源管理器掛了,就會出現一直阻塞等待的情況。類似問題,我們可以通過設置事務超時時間來解決。

第二,仍然存在數據不一致的可能性,例如,在最後通知提交全局事務時,由於網絡故障,部分節點有可能收不到通知,由於這部分節點沒有提交事務,就會導致數據不一致的情況出現。

事務補償機制(TCC)

     以上這種基於 XA 規範實現的事務提交,由於阻塞等性能問題,有着比較明顯的低性能、低吞吐的特性。所以在搶購活動中使用該事務,很難滿足系統的併發性能。除了性能問題,JTA 只能解決同一服務下操作多數據源的分佈式事務問題,換到微服務架構下,可能存在同一個事務操作,分別在不同服務上連接數據源,提交數據庫操作。

     而 TCC 正是爲了解決以上問題而出現的一種分佈式事務解決方案。TCC 採用最終一致性的方式實現了一種柔性分佈式事務,與 XA 規範實現的二階事務不同的是,TCC 的實現是基於服務層實現的一種二階事務提交。

TCC 分爲三個階段,即 Try、Confirm、Cancel 三個階段。

Try 階段:主要嘗試執行業務,執行各個服務中的 Try 方法,主要包括預留操作;Confirm 階段:確認 Try 中的各個方法執行成功,然後通過 TM 調用各個服務的 Confirm 方法,這個階段是提交階段;

Cancel 階段:當在 Try 階段發現其中一個 Try 方法失敗,例如預留資源失敗、代碼異常等,則會觸發 TM 調用各個服務的 Cancel 方法,對全局事務進行回滾,取消執行業務。

   以上執行只是保證 Try 階段執行時成功或失敗的提交和回滾操作,你肯定會想到,如果在 Confirm 和 Cancel 階段出現異常情況,那 TCC 該如何處理呢?此時 TCC 會不停地重試調用失敗的 Confirm 或 Cancel 方法,直到成功爲止。

   但 TCC 補償性事務也有比較明顯的缺點,那就是對業務的侵入性非常大。首先,我們需要在業務設計的時候考慮預留資源;然後,我們需要編寫大量業務性代碼,例如 Try、Confirm、Cancel 方法;最後,我們還需要爲每個方法考慮冪等性。這種事務的實現和維護成本非常高,但綜合來看,這種實現是目前大家最常用的分佈式事務解決方案。

總結

   在同服務多數據源操作不同數據庫的情況下,我們可以使用基於 XA 規範實現的分佈式事務,在 Spring 中有成熟的 JTA 框架實現了 XA 規範的二階事務提交。事實上,二階事務除了性能方面存在嚴重的阻塞問題之外,還有可能導致數據不一致,我們應該慎重考慮使用這

種二階事務提交。

    在跨服務的分佈式事務下,我們可以考慮基於 TCC 實現的分佈式事務,常用的中間件有 TCC-Transaction。TCC 也是基於二階事務提交原理實現的,但 TCC 的二階事務提交是提到了服務層實現。TCC 方式雖然提高了分佈式事務的整體性能,但也給業務層帶來了非常大的工作量,對應用服務的侵入性非常強,但這是大多數公司目前所採用的分佈式事務解決方案。

    Seata 是一種高效的分佈式事務解決方案,設計初衷就是解決分佈式帶來的性能問題以及侵入性問題。但目前 Seata 的穩定性有待驗證,例如,在 TC 通知 RM 開始提交事務後,TC 與 RM 的連接斷開了,或者 RM 與數據庫的連接斷開了,都不能保證事務的一致性。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/LELIAf4QFglVidSg5vjMdw