支付寶螞蟻金服怎麼在分佈式架構下保證轉賬業務數據的一致性?

概述

本文以分佈式架構下的轉賬服務爲業務場景,先闡述分佈式架構下跨數據庫轉賬遇到的數據一致性問題;再詳細介紹如何使用行業常見的分佈式事務解決方案(消息事務、衝正補償、JTA/XA),以及螞蟻的分佈式事務(DTX),解決跨庫轉賬的數據一致性問題,並列舉了各種解決方案的優劣勢。

通過對比各種分佈式事務解決方案,您會發現,分佈式事務有豐富的接入模式,能應對各種複雜的業務場景,接入維護簡單,性能優異,行業優勢明顯。

需求背景和技術問題

需求描述

轉賬是金融機構日常業務中的常見場景。假設用戶 A爲轉賬發起方,從自己的賬戶餘額中轉出一筆資金至用戶 B 的賬戶中。此操作涉及兩部分:“A 賬戶的扣錢”和“B 賬戶的加錢”,這兩個操作要都成功纔算轉賬成功。

▲ 用戶 A 向用戶 B 轉賬

如果一個操作成功而另一個操作失敗(比如:B 賬戶加錢成功,A 賬戶扣錢失敗),則會出現總體資金數據不一致,造成資金損失;故轉賬服務的中的“加錢”和“扣錢”操作必須在一個事務內,要麼都成功,要麼都失敗。

在非分佈式架構下,用戶 A、用戶 B 的賬戶數據都在同一個數據庫中,可以使用數據庫事務來保證“加錢”和“扣錢”操作的在一個事務內。

但是在分佈式架構下,用戶 A 的賬戶數據、用戶 B 的賬戶數據會分別存儲在不同數據庫中,此時便無法再使用數據庫事務來保證“加錢”和“扣錢”操作的原子性,需要考慮能保證數據一致性的解決方案。

需求分析

▲ 用戶 A 發起轉賬業務

如上圖所示,在分佈式架構下,用戶 A 發起轉賬操作,向用戶 B 轉賬 100 元。

轉賬過程中,首先是在數據庫 A 中扣除賬號 A 的 100 元,緊接着是在數據庫 B 中給賬戶 B 加 100 元。

“數據庫 A 上賬戶 A 扣款操作”、“數據庫 B 上賬戶 B 加錢操作”都成功纔算轉賬成功;如果一個操作成功,另一操作失敗(比如:賬戶 B 加錢成功,賬戶 A 扣錢失敗),則會出現資金數據不一致,造成資金損失;如果加錢操作和扣錢操作丟失敗,那麼轉賬是失敗的,但是不會有資金損失。

故需要保證數據庫 A、數據庫 B 上的更新操作都成功或者都失敗;整個資金數據最終是一致的。

技術問題

分佈式架構下,用戶 A、用戶 B 的賬戶數據存儲在不同的數據庫中,需要引入能保證跨數據庫的多個操作在一個事務內的解決方案,以保證轉賬操作的原子性,保障跨庫轉賬時的資金安全。

下文將介紹目前常見的分佈式事務解決方案,並將其應用到轉賬場景,以解決跨庫轉賬時的數據一致性問題。

行業分佈式事務解決方案

分佈式事務是指事務中資源分佈於網絡中的多個不同節點的事務。

▲ 分佈式事務中的事務管理器與資源管理器

如上圖所示,分佈式事務有一個事務管理器(Transaction Manager)和多個資源管理器(Resource Manager)組成:

  • 事務管理器:通常被稱爲事務發起方,負責發起方分佈式事務,編排、協調所有資源管理器完成業務活動。

  • 資源管理器:也被稱爲事務參與者,對應單個業務動作,由事務管理器協調、編排。

 在本文的轉賬案例中,轉賬服務便是事務管理器(發起方),轉賬服務內部執行“扣錢”、“加錢”動作是事務的參與者。

二階段提交協議(2PC)是分佈式事務的基礎協議,在此協議中,事務管理器分兩個階段協調資源管理器。

▲ 二階段提交

如上圖所示,在第一階段,事務管理器向所有資源管理器發生準備請求,如果所有資源管理返回準備成功,那麼在第二階段事務管理器向所有資源發生提交請求,完成所有資源的提交。

如果有任一資源管理一階段準備失敗,那麼在第二階段事務管理器向所有資源發生回滾請求,完成所有資源的回滾。

目前常見的分佈式事務解決方案均採用二階段提交協議實現。

下文將以轉賬場景爲例,分別介紹非事務解決方案和常見的分佈式事務解決方案,瞭解它們是如何保障分佈式架構下跨庫轉賬的原子性,保障資金數據的一致性。

非事務解決方案

▲ 轉賬操作流程

如上圖所示,在非分佈式事務解決方案中,轉賬操作會依次執行“A 賬戶扣除 100 元”、“B 賬戶增加 100 元”,最後完成轉賬操作。整個轉賬操作內部有兩個操作,分別是:“A 賬戶扣錢”、“B 賬戶加錢”,我們把這兩個操作抽象成扣錢服務(MinusAction)和加錢服務(AddAction):

  • 扣錢服務:負責在 A 賬戶餘額上扣除轉賬資金。

  • 加錢服務:負責向 B 賬戶加錢。

▲ 轉賬服務流程

如上圖所示,轉賬服務會依次調用 MinusAction 服務的 minus 方法完成 A 賬戶的扣款,AddAction 服務的 add 方法完成 B 賬戶的加錢。整個轉賬過程中,未引入任何分佈式事務解決方案來保證轉賬操作(加錢操作、扣錢操作)的原子性。正常情況下,加錢操作和扣錢操作都執行成功,這種實現方式不會有問題。但是在異常情況下(比如:A 賬戶扣錢成功,但是 B 賬戶加錢失敗,此時 A 賬戶扣掉的錢將無法恢復),會出現資金數據不一致,給用戶造成資金損失。因此,在金融行業,這種解決方案是不可取的,必需引入分佈式事務解決方案來保障轉賬服務的原子性。

消息事務解決方案

▲ 消息事務業務流程

消息事務是一種比較常見的分佈式事務解決方案。如上圖所示,引入了消息事務解決方案來解決轉賬操作的事務問題。

轉賬服務內部使用消息事務功能,發送加錢消息給加錢服務,同時在消息事務回調方法內調用扣錢服務完成 A 賬戶的扣錢;A 賬戶扣錢成功則加錢消息發送成功,否則加錢消息發送失敗;消息發送完成之後,轉賬服務返回結果,消息事務一階段完成。

在第二階段訂閱消息,消息消費時,調用加錢服務完成 B 賬戶的加錢;B 賬戶加錢成功則消息消費成功;否則消息消費失敗,消息隊列下個週期重試投遞消息。

消息事務解決方案分析

  • 消息事務的轉賬操作分爲兩個階段,在一階段執行的操作是:“A 賬戶扣除 100 元錢”和發送“B 賬戶加錢”消息至消息隊列,這兩個操作要麼都成功,要麼都失敗。

  • 在一階段結束之後,整個轉賬操作便結束,用戶會收到轉賬結果;此時用戶會認爲轉賬完成。

  • 二階段“B 賬戶加錢”消息的消費是異步的,由消息隊列將“B 賬戶加錢”消息發送至“B 賬戶扣錢”服務,此服務消費消息並完成賬戶 B 的加錢;消息隊列會一直重複投遞消息,直到“B 賬戶加錢”成功爲止。

消息事務解決方案問題

  • 消息消費延遲。一階段“A 賬戶扣款”之後,轉賬操作便結束,此時用戶認爲轉賬操作已經完成;但實際上“B 賬戶加錢操作”未執行;需要等待消息隊列投遞“B 賬戶加錢”消息方可執行,消息投遞延遲時間是不確定的,造成“B 賬戶加錢操作”執行實際不確定。

  • 要求二階段的消息消費必須 100% 成功。一階段“賬戶 A 扣除 100 元錢”成功之後,如果二階段“賬戶 B 加錢 100 元錢操作”無法成功(比如:賬戶 B 不存在、B賬戶被凍結等原因導致賬戶 B 加錢永遠不會成功),此時整個資金就處於不一致狀態,賬戶 A 扣除的 100 元錢將永遠無法得到補償;所以使用消息事務必須保證二階段的消息消費一定能成功。

  • 引入“消息隊列”風險點。消息事務可用的前提是消息隊列可用。消息隊列宕機會導致整個轉賬操作完全不可用;消息隊列出現消息積壓會導致二階段延遲更加嚴重。因此,消息隊列可能成爲消息事務解決方案的一個潛在瓶頸。

衝正補償解決方案

衝正補償也是分佈式事務比較常用的一種解決方案,衝正補償可以解決消息事務二階段不可逆的問題。

各個業務參與者(加錢、扣錢動作)需要分別實現正向業務操作,以及其逆向回滾操作。

事務協調者先執行所有參與者正向業務操作,如果所有參與者正向操作均成功,那麼整個業務就算成功;如果任意參與者正向操作執行失敗,那麼協調者會去執行所有參與者的逆向操作,讓事務回滾。轉賬的衝正補償實現如下圖所示:

▲ 衝正補償業務流程

衝正補償解決方案中,每一個業務操作均需要實現正向和逆向兩個操作;對於扣錢服務,除了扣錢操作外,還需要實現其方法的回滾操作;對於加錢服務,除了加錢操作外,還需要實現其回滾操作。

衝正補償解決方案分析

  • 在衝正補償下,各服務均需要用戶設計和實現“正向”和“逆向”兩個操作。

  • 轉賬操作開始之後,先執行 A 賬戶的正向操作“A 賬戶扣錢操作”,如果執行失敗,則執行逆向操作“A 賬戶扣錢回滾操作”,最終轉賬操作失敗。

  • 如果 A 賬戶正向操作成功,則執行 B 賬戶的正向操作“B賬戶加錢操作”;如果“B 賬戶加錢”執行失敗,則會執行 B 賬戶逆向操作“B賬戶加錢回滾操作”,以及 A 賬戶的逆向操作“A 賬戶扣錢回滾操作”,最終轉賬失敗。

  • 如果 A 賬戶正向和 B 賬戶的正向操作均成功,那麼轉賬成功。

衝正補償解決方案問題

  • 接入成本高。衝正補償需要用戶設計實現各服務的正向和逆向操作,用戶在設計正向操作時,需要同時考慮逆向操作該如何執行;需要在正向操作中保存一些中間數據,供逆向操作運行時使用,系統設計實現較複雜。

  • 資金安全問題。假如在某些場景下,對 B 賬戶“加錢”(正向)成功之後,出現一些其他異常導致整個轉賬操作需要回滾,此時會觸發“加錢”操作的逆向操作去扣除 B 賬戶上的資金;但是如果 B 用戶在此之前已經把賬戶上的資金全部轉走,“扣除 B 賬戶上的資金”這個逆向操作可能永遠不會成功,此時就出現資金無法追回的問題。爲了解決資金安全問題,編排衝正補償各動作時,需要考慮如何保障資金安全;系統設計的方方面面均需考慮資金安全,無疑係統設計會複雜繁瑣,日後的代碼維護也需謹慎。

  • 維護成本高。正向、逆向操作執行過程中,可能出現服務器宕機、重啓等異常情況導致轉賬流程中斷(比如正向操作中,A賬戶扣錢成功之後,B 賬戶加錢還未開始,執行流程中斷),此時就需要用戶維護一個恢復程序,不斷找到這種未完成的轉賬任務,執行該筆轉賬剩餘的未完成的操作,使轉賬成功或者轉賬回滾,以保障數據的最終一致。但目前衝正補償並沒有標準的恢復程序可用,這個恢復程序就需要用戶自己設計實現,成本較高。

JTA/XA 解決方案

JTA/XA 解決方案通過 JTA API 調用數據庫的 XA 接口,協調各個數據庫上的 XA 事務的提交和回滾。

▲ JTA/XA 業務流程

如上圖所示,加錢操作、扣錢操作分別調用數據庫 A、數據庫 B 的 JTA/XA API。JTA/XA API 能幫助用戶分二階段協調各個數據庫上 XA 事務的同步提交和回滾。

JTA/XA 解決方案分析

  • 用戶編寫 JTA 接口,內部分別開啓“A 數據庫”、“B 數據庫”上的 XA 事務。

  • 開啓 XA 事務之後,分別在“A 數據庫”XA 事務上執行 A 賬戶扣錢任務,在“B 數據庫”XA 事務中執行 B 賬號加錢任務;並結束 XA 事務。

  • 執行 XA 事務一階段的預提交。

  • 如果“A 數據庫”、“B 數據庫”上的 XA 事務預提交均成功,則提交 XA 事務。

  • 如果“A 數據庫”、“B 數據庫”上的 XA 事務預提交出現失敗,則回滾 XA 事務。

JTA/XA 解決方法下,數據庫 XA 事務作爲資源管理器,用戶自己作爲事務協調者,調用 JTA 接口操作 XA 事務。

JTA/XA 解決方案的問題

  • XA 併發性能受限。XA 事務內訪問的數據都會被數據庫加鎖,直到 XA 事務提交或者回滾,這些數據鎖纔會被釋放。這個數據庫層的全局鎖限制了 XA 事務的併發性,極大影響了 XA 事務的性能。

  • 運維成本高。與衝正補償一樣,XA 解決方案下,事務協調者執行轉賬操作的任意階段,都可能出現服務器宕機、重啓等異常情況導致轉賬流程中斷,此時就需要用戶維護一個恢復程序,不斷找到這種未完成的轉賬任務,執行該筆轉賬剩餘的未完成的操作,使轉賬成功或者轉賬回滾,以保障數據的最終一致。

    同樣的,JTA/XA 解決方案並沒有標準的恢復程序可用,這個恢復程序就需要用戶自己設計實現,成本較高。

螞蟻金服分佈式事務解決方案

前文介紹了消息事務、衝正補償等解決方案及其問題,接下來我們將介紹使用螞蟻金服的分佈式事務(DTX)解決方案來實現轉賬操作。

分佈式事務有兩種模式:TCC 模式和 FMT 模式:

  • TCC 模式由用戶實現 TCC 參與者,供事務發起方協調。

  • FMT 模式無需用戶實現 TCC 參與者,用戶的業務將作爲一階段操作,每一個業務的二階段操作由分佈式事務框架自動生成。

下面我們將分別介紹如何使用 TCC 模式、FMT 模式實現轉賬操作。

TCC 模式解決方案

TCC 即 Try-Confirm-Cancel 的縮寫,是服務化的二階段提交(2PC)編程模型:

  • Try:資源檢查和預留

  • Confirm:發生實際的業務操作;要求 Try 成功 Confirm 一定能成功

  • Cancel:預留資源的釋放,Try 階段的逆向操作

TCC 模式需要用戶將“A 賬戶扣錢”、“B 賬戶加錢”均分成二階段實現,在第一階段檢查預留資源,在二階段提交時執行實際的扣錢、加錢操作,二階段回滾時釋放預留資源。

使用分佈式事務的 TCC 模式,需要用戶設計、實現個業務動作的 TCC 服務,此 TCC 服務有 3 個方法,分別是一階段的準備方法(Try)、二階段的提交方法(Confirm)和二階段的回滾方法(Cancel)。

▲ 轉賬 TCC 業務流程

如上圖所示,TCC 模式的轉賬操作描述:

  • 轉賬操作內,首先執行各 TCC 參與者的一階段方法,做轉賬準備操作;一階段準備成功,則二階段的提交一定能成功。

  • 如果所有一階段參與者方法均執行成功,那麼二階段轉賬操作會去執行所有 TCC 參與者的提交方法,執行 A 賬戶的扣錢和 B 賬戶的加錢,完成真正用戶餘額的轉賬。

  • 如果一階段有任一參與者出現失敗,那麼二階段便會執行所有 TCC參與者的回滾方法,使得各賬戶恢復至轉賬前的狀態。

在轉賬方法內部,用戶只需要顯示調用各個參與者(A 賬戶扣錢參與者、B 賬戶加錢參與者)的一階段方法,無需關注參與者的二階段方法調用(參與者二階段方法由分佈式事務框架來調用,分佈式事務框架會根據轉賬方法返回結果是成功還是失敗,來決定是去調用各參與者二季度的提交方法還是回滾方法)。

TCC 模式優點

  • 無資金安全風險。一階段只會凍結轉賬資金,不會發生真正的資金轉賬。一階段成功之後纔回去執行二階段的提交操作,完成真正的資金轉賬;與衝正補償相比,TCC 模式資金更加安全。

  • 運維成本低;在轉賬操作執行過程中出現異常中斷時,TCC 模式無需用戶自己維護異常事務恢復程序,分佈式事務提供了標準的統一的恢復服務幫助用戶恢復異常事務;這個恢復服務用戶完全無感知。

  • 性能優越。TCC 參與者各階段方法由用戶實現;相對於 XA 這種數據庫層的全局鎖,用戶可自定義其數據庫的操作粒度,使數據庫層面的鎖衝突最小、最大限度的提高吞吐量。

TCC 模式問題

接入成本較高。用戶接入過程中,需要考慮如何將業務動作分成二階段完成,需要在第一階段預留資源,以保證第二階段的提交一定能成功。此外,TCC 參與者實現時還需要考慮冪等控制、防懸掛等,這些都增加了 TCC 模式的接入成本。

FMT 模式解決方案

針對 TCC 模式接入成本高的問題,分佈式事務開發了 FMT 模式。FMT 模式的接入成本極低,用戶無需實現 TCC 參與者,只需要極少的改動業務代碼即可接入分佈式事務。

FMT 模式優點

接入簡單、快捷;對業務代碼改動少。

FMT 模式問題

在實現上,爲了防止數據的無效讀寫等問題,添加了行鎖;相對於幾乎無鎖的 TCC 模式,性能稍弱。

分佈式事務與行業解決方案對比

與消息事務相比

  • 消息事務存在消息消費延遲的問題;分佈式事務所有操作均是同步調用,無任何延遲。

  • 消息事務在一階段成功後,要求二階段的消息消費必須成功。分佈式事務的 TCC 模式也是二階段操作,但在一階段準備操作後,可以保證二階段一定成功;而 FMT 模式在一階段準備操作後,會自動生成二階段操作,可以保證操作 100% 成功。

  • 消息事務依賴消息隊列服務;分佈式事務不依賴任何第三方服務。

與衝正補償相比

  • 衝正補償要求各個業務動作均實現“正向”、“逆向”2 個操作;TCC 要求業務動作分 2 階段實現,分別是一階段的準備,和二階段的提交/回滾。對於接入成本來說,二者可以說的等價的。但是FMT 模式下,無需用戶實現這些複雜操作,用戶只需按自身業務邏輯實現其代碼。

  • 衝正補償在正向操作中就完成用戶賬戶資金的修改,存在資金安全風險;TCC 模式一階段只是凍結資金,二階段才完成真正的資金變更,無資金安全問題;FMT 模式有全局行鎖對用戶數據進行加鎖,用戶在轉賬事務未完成前,無法動用賬戶資金,同樣無資金安全問題。

  • 衝正補償需要用戶維護一個事務的恢復服務;而分佈式事務提高了統一的標準的異常恢復程序,分佈式事務維護成本更低。

與 JTA/XA 相比

  • TCC 模式可以認爲是無任何數據庫層面的全局數據鎖,性能比 XA 高很多;FMT 模式雖然有類 XA 的全局數據鎖,但是我們對 FMT 的行鎖做了大量優化,引入樂觀鎖、自旋鎖、控制行鎖粒度等優化策略,FMT 的行鎖性能比 XA 稍高。

  • JTA/XA 解決方案需要用戶維護一個事務的恢復服務;而分佈式事務提高了統一的標準的異常恢復程序,維護成本更低。

總結

相較於消息事務、衝正補償、JTA/XA 等解決方案,分佈式事務提供了多種模式和解決方案,在性能、易用性、運維成本等方面均有較出色的表現,是目前行業內領先的分佈式事務解決方案。

分佈式事務提供了兩種模式:TCC 模式、FMT 模式。如果比較看重易用性,可以選擇使用 FMT 模式;如果業務邏輯比較複雜,對性能要求比較高,可以選擇 TCC 模式。

原創日期:2018-04-04

原文作者:紹輝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章