從一筆金幣充值去思考分布式事務

支付重構

考慮支付重構的時候,自然想到原本屬于一個本地事務中的處理,現(xiàn)在要跨應用了要怎么處理。拿充值訂單舉個栗子吧,假設:原本訂單模塊和賬戶模塊是放在一起的,現(xiàn)在需要做服務拆分,拆分成訂單服務,賬戶服務。原本收到充值回調(diào)后,可以將修改訂單狀態(tài)和增加金幣放在一個mysql事務中完成的,但是呢,因為服務拆分了,就面臨著需要協(xié)調(diào)2個服務才能完成這個事務

所以就帶出來,我們今天要分享和討論的話題是:怎么解決分布式場景下數(shù)據(jù)一致性問題,暫且用分布式事務?來定義吧。

同樣的問題還存在于其他的場景:

送禮:

1. 調(diào)用支付服務:先扣送禮用戶的金幣,然后給主播加相應的荔枝

2. 確認第一步成功后,播放特效,發(fā)聊天室送禮評論等

充值成功消息:

完成充值訂單

發(fā)送訂單完成的kafka消息

在涉及支付交易等付費接口的時候,數(shù)據(jù)一致性的問題就顯得尤為重要,因為都是錢啊

回到頂部

目前分布式事務是怎么解決的呢?

問題肯定不是新問題,也就是目前已經(jīng)有相應的解決方案了,那就看一下現(xiàn)在是怎么來解決這類問題的吧。

購買基礎商品成功發(fā)送支付訂單完成消息為例:假設支付下單購買基礎商品,此刻已經(jīng)收到支付回調(diào),訂單已經(jīng)處理成功了,這個時候kafka服務故障,消息發(fā)送失??;而這個時候處理訂單的事務已經(jīng)提交了,怎么保證訂單完成的消息一定能發(fā)出去呢?


解讀一下這個流程:

綠色部分,表示流程正常運行的交互過程:

先往JobController中提交一個job(用于故障恢復)

提交成功后,開始處理訂單邏輯

處理完訂單邏輯之后,開始發(fā)送kafka消息

消息也發(fā)送成功后,刪除第一步提交的job

黃色部分,表示流程出現(xiàn)了異常,數(shù)據(jù)可能存在不一致現(xiàn)象。這個時候就需要進行流程恢復

JobController任務控制器定時去redis查詢延時任務列表(每個任務都有一個時間戳,按時間戳排序過濾)

將任務進行恢復(調(diào)用job注冊時定義的處理方法)

任務執(zhí)行成功,表示流程完成;否則下一個定時周期重試

問題:

基于redis存儲恢復任務,可能存在數(shù)據(jù)丟失風險

架構體系中沒有統(tǒng)一的分布式事務規(guī)范,可否將這層邏輯獨立為分布式事務中間件

缺少事務執(zhí)行策略管理,如:控制最大重試次數(shù)等

事務執(zhí)行狀態(tài)沒有記錄,追查需要去翻看日志

回到頂部

行業(yè)中有什么解決方案

說解決方案之前,我們先了解一下這些方案的理論依據(jù),有助于幫助我們來理解和實踐這些方案

理論依據(jù)(討論的前提)

本地事務、分布式事務

如果說本地事務是解決單個數(shù)據(jù)源上的數(shù)據(jù)操作的一致性問題的話,那么分布式事務則是為了解決跨越多個數(shù)據(jù)源上數(shù)據(jù)操作的一致性問題。

強一致性、弱一致性、最終一致性

從客戶端角度,多進程并發(fā)訪問時,更新過的數(shù)據(jù)在不同進程如何獲取的不同策略,決定了不同的一致性。對于關系型數(shù)據(jù)庫,要求更新過的數(shù)據(jù)能被后續(xù)的訪問都能看到,這是強一致性。如果能容忍后續(xù)的部分或者全部訪問不到,則是弱一致性。如果經(jīng)過一段時間后要求能訪問到更新后的數(shù)據(jù),則是最終一致性

服務端角度,如何盡快將更新后的數(shù)據(jù)分布到整個系統(tǒng),降低達到最終一致性的時間窗口,是提高系統(tǒng)的可用度和用戶體驗非常重要的方面。對于分布式數(shù)據(jù)系統(tǒng):

N — 數(shù)據(jù)復制的份數(shù)

W — 更新數(shù)據(jù)時需要保證寫完成的節(jié)點數(shù)

R — 讀取數(shù)據(jù)的時候需要讀取的節(jié)點數(shù)

如果W+R>N,寫的節(jié)點和讀的節(jié)點重疊,則是強一致性。例如對于典型的一主一備同步復制的關系型數(shù)據(jù)庫,N=2,W=2,R=1,則不管讀的是主庫還是備庫的數(shù)據(jù),都是一致的。

如果W+R<=N,則是弱一致性。例如對于一主一備異步復制的關系型數(shù)據(jù)庫,N=2,W=1,R=1,則如果讀的是備庫,就可能無法讀取主庫已經(jīng)更新過的數(shù)據(jù),所以是弱一致性。

CAP理論

分布式環(huán)境下(數(shù)據(jù)分布)要任何時刻保證數(shù)據(jù)一致性是不可能的,只能采取妥協(xié)的方案來保證數(shù)據(jù)最終一致性。這個也就是著名的CAP定理。

需要明確的一點是,對于一個分布式系統(tǒng)而言,分區(qū)容錯性是一個最基本的要求。因為 既然是一個分布式系統(tǒng),那么分布式系統(tǒng)中的組件必然需要被部署到不同的節(jié)點,否則也就無所謂分布式系統(tǒng)了,因此必然出現(xiàn)子網(wǎng)絡。而對于分布式系統(tǒng)而言,網(wǎng) 絡問題又是一個必定會出現(xiàn)的異常情況,因此分區(qū)容錯性也就成為了一個分布式系統(tǒng)必然需要面對和解決的問題。因此系統(tǒng)架構師往往需要把精力花在如何根據(jù)業(yè)務 特點在C(一致性)和A(可用性)之間尋求平衡。

BASE 理論

BASE是Basically Available(基本可用)、Soft state(軟狀態(tài))和Eventually consistent(最終一致性)三個短語的縮寫。BASE理論是對CAP中一致性和可用性權衡的結果,其來源于對大規(guī)?;ヂ?lián)網(wǎng)系統(tǒng)分布式實踐的總結, 是基于CAP定理逐步演化而來的。BASE理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據(jù)自身業(yè)務特點,采用適當?shù)姆绞絹硎瓜到y(tǒng)達到最終一致性。

BASE理論面向的是大型高可用可擴展的分布式系統(tǒng),和傳統(tǒng)的事物ACID特性是相反的,它完全不同于ACID的強一致性模型,而是通過犧牲強一致性來獲得可用性,并允許數(shù)據(jù)在一段時間內(nèi)是不一致的,但最終達到一致狀態(tài)。但同時,在實際的分布式場景中,不同業(yè)務單元和組件對數(shù)據(jù)一致性的要求是不同的,因此在具體的分布式系統(tǒng)架構設計過程中,ACID特性和BASE理論往往又會結合在一起。

柔性事務

不同于ACID的剛性事務,在分布式場景下基于BASE理論,就出現(xiàn)了柔性事務的概念。要想通過柔性事務來達到最終的一致性,就需要依賴于一些特性,這些特性在具體的方案中不一定都要滿足,因為不同的方案要求不一樣;但是都不滿足的話,是不可能做柔性事務的。

可見性(對外可查詢)

在分布式事務執(zhí)行過程中,如果某一個步驟執(zhí)行出錯,就需要明確的知道其他幾個操作的處理情況,這就需要其他的服務都能夠提供查詢接口,保證可以通過查詢來判斷操作的處理情況。

為了保證操作的可查詢,需要對于每一個服務的每一次調(diào)用都有一個全局唯一的標識,可以是業(yè)務單據(jù)號(如訂單號)、也可以是系統(tǒng)分配的操作流水號(如支付記錄流水號)。除此之外,操作的時間信息也要有完整的記錄。

冪等操作

冪等性,其實是一個數(shù)學概念。冪等函數(shù),或冪等方法,是指可以使用相同參數(shù)重復執(zhí)行,并能獲得相同結果的函數(shù)。

f(f(x)) = f(x)

在編程中一個冪等操作的特點是其任意多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同。也就是說,同一個方法,使用同樣的參數(shù),調(diào)用多次產(chǎn)生的業(yè)務結果與調(diào)用一次產(chǎn)生的業(yè)務結果相同。 這一個要求其實也比較好理解,因為要保證數(shù)據(jù)的最終一致性,很多解決防范都會有很多重試的操作,如果一個方法不保證冪等,那么將無法被重試。 冪等操作的實現(xiàn)方式有多種,如在系統(tǒng)中緩存所有的請求與處理結果、檢測到重復操作后,直接返回上一次的處理結果等。

業(yè)界方案

兩階段提交(2PC)

XA是X/Open CAE Specification (Distributed Transaction Processing)模型中定義的TM(Transaction Manager)與RM(Resource Manager)之間進行通信的接口。

在XA規(guī)范中,數(shù)據(jù)庫充當RM角色,應用需要充當TM的角色,即生成全局的txId,調(diào)用XAResource接口,把多個本地事務協(xié)調(diào)為全局統(tǒng)一的分布式事務。


二階段提交是XA的標準實現(xiàn)。它將分布式事務的提交拆分為2個階段:prepare和commit/rollback。

2PC模型中,在prepare階段需要等待所有參與子事務的反饋,因此可能造成數(shù)據(jù)庫資源鎖定時間過長,不適合并發(fā)高以及子事務生命周長較長的業(yè)務場景。兩階段提交這種解決方案屬于犧牲了一部分可用性來換取的一致性。

saga

saga的提出,最早是為了解決可能會長時間運行的分布式事務(long-running process)的問題。所謂long-running的分布式事務,是指那些企業(yè)業(yè)務流程,需要跨應用、跨企業(yè)來完成某個事務,甚至在事務流程中還需要有手工操作的參與,這類事務的完成時間可能以分計,以小時計,甚至可能以天計。這類事務如果按照事務的ACID的要求去設計,勢必造成系統(tǒng)的可用性大大的降低。試想一個由兩臺服務器一起參與的事務,服務器A發(fā)起事務,服務器B參與事務,B的事務需要人工參與,所以處理時間可能很長。如果按照ACID的原則,要保持事務的隔離性、一致性,服務器A中發(fā)起的事務中使用到的事務資源將會被鎖定,不允許其他應用訪問到事務過程中的中間結果,直到整個事務被提交或者回滾。這就造成事務A中的資源被長時間鎖定,系統(tǒng)的可用性將不可接受。

saga,則是一種基于補償?shù)南Ⅱ?qū)動的用于解決long-running process的一種解決方案。目標是為了在確保系統(tǒng)高可用的前提下盡量確保數(shù)據(jù)的一致性。還是上面的例子,如果用saga來實現(xiàn),那就是這樣的流程:服務器A的事務先執(zhí)行,如果執(zhí)行順利,那么事務A就先行提交;如果提交成功,那么就開始執(zhí)行事務B,如果事務B也執(zhí)行順利,則事務B也提交,整個事務就算完成。但是如果事務B執(zhí)行失敗,那事務B本身需要回滾,這時因為事務A已經(jīng)提交,所以需要執(zhí)行一個補償操作,將已經(jīng)提交的事務A執(zhí)行的操作作反操作,恢復到未執(zhí)行前事務A的狀態(tài)。這樣的基于消息驅(qū)動的實現(xiàn)思路,就是saga。我們可以看出,saga是犧牲了數(shù)據(jù)的強一致性,僅僅實現(xiàn)了最終一致性,但是提高了系統(tǒng)整體的可用性。

補償事務(TCC)

TCC 其實就是采用的補償機制,其核心思想是:針對每個操作,都要注冊一個與其對應的確認和補償(撤銷)操作。TCC模型是把鎖的粒度完全交給業(yè)務處理。它分為三個階段:

Try 階段主要是對業(yè)務系統(tǒng)做檢測及資源預留

Confirm 階段主要是對業(yè)務系統(tǒng)做確認提交,Try階段執(zhí)行成功并開始執(zhí)行 Confirm階段時,默認 Confirm階段是不會出錯的。即:只要Try成功,Confirm一定成功。

Cancel 階段主要是在業(yè)務執(zhí)行錯誤,需要回滾的狀態(tài)下執(zhí)行的業(yè)務取消,預留資源釋放。

下面對TCC模式下,A賬戶往B賬戶匯款100元為例子,對業(yè)務的改造進行詳細的分析:

匯款服務和收款服務分別需要實現(xiàn),Try-Confirm-Cancel接口,并在業(yè)務初始化階段將其注入到TCC事務管理器中。

[匯款服務]

Try:

? ? 檢查A賬戶有效性,即查看A賬戶的狀態(tài)是否為“轉(zhuǎn)帳中”或者“凍結”;

? ? 檢查A賬戶余額是否充足;

? ? 從A賬戶中扣減100元,并將狀態(tài)置為“轉(zhuǎn)賬中”;

? ? 預留扣減資源,將從A往B賬戶轉(zhuǎn)賬100元這個事件存入消息或者日志中;

Confirm:

不做任何操作;

Cancel:

? ? A賬戶增加100元;

從日志或者消息中,釋放扣減資源。

[收款服務]

Try:

檢查B賬戶賬戶是否有效;

Confirm:

? ? 讀取日志或者消息,B賬戶增加100元;

? ? 從日志或者消息中,釋放扣減資源;

Cancel:

不做任何操作。

由此可以看出,TCC模型對業(yè)務的侵入強,改造的難度大。

本地消息表(異步確保)

本地消息表這種實現(xiàn)方式應該是業(yè)界使用最多的,其核心思想是將分布式事務拆分成本地事務進行處理,這種思路是來源于ebay。我們可以從下面的流程圖中看出其中的一些細節(jié):

基本思路就是:

消息生產(chǎn)方,需要額外建一個消息表,并記錄消息發(fā)送狀態(tài)。消息表和業(yè)務數(shù)據(jù)要在一個事務里提交,也就是說他們要在一個數(shù)據(jù)庫里面。然后消息會經(jīng)過MQ發(fā)送到消息的消費方。如果消息發(fā)送失敗,會進行重試發(fā)送。

消息消費方,需要處理這個消息,并完成自己的業(yè)務邏輯。此時如果本地事務處理成功,表明已經(jīng)處理成功了,如果處理失敗,那么就會重試執(zhí)行。如果是業(yè)務上面的失敗,可以給生產(chǎn)方發(fā)送一個業(yè)務補償消息,通知生產(chǎn)方進行回滾等操作。

生產(chǎn)方和消費方定時掃描本地消息表,把還沒處理完成的消息或者失敗的消息再發(fā)送一遍。如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的。

事務消息

事務消息作為一種異步確保型事務, 將兩個事務分支通過MQ進行異步解耦,事務消息的設計流程同樣借鑒了兩階段提交理論,整體交互流程如下圖所示:


事務發(fā)起方首先發(fā)送prepare消息到MQ。

在發(fā)送prepare消息成功后執(zhí)行本地事務。

根據(jù)本地事務執(zhí)行結果返回commit或者是rollback。

如果消息是rollback,MQ將刪除該prepare消息不進行下發(fā),如果是commit消息,MQ將會把這個消息發(fā)送給consumer端。

如果執(zhí)行本地事務過程中,執(zhí)行端掛掉,或者超時,MQ將會不停的詢問其同組的其它producer來獲取狀態(tài)。

Consumer端的消費成功機制有MQ保證。

有一些第三方的MQ是支持事務消息的,比如RocketMQ,但是市面上一些主流的MQ都是不支持事務消息的,比如 RabbitMQ 和 Kafka 都不支持。

盡最大努力通知

最大努力通知方案主要也是借助MQ消息系統(tǒng)來進行事務控制,這一點與可靠消息最終一致方案一樣。看來MQ中間件確實在一個分布式系統(tǒng)架構中,扮演者重要的角色。最大努力通知方案是比較簡單的分布式事務方案,它本質(zhì)上就是通過定期校對,實現(xiàn)數(shù)據(jù)一致性。

最大努力通知方案的實現(xiàn)

業(yè)務活動的主動方,在完成業(yè)務處理之后,向業(yè)務活動的被動方發(fā)送消息,允許消息丟失。

主動方可以設置時間階梯型通知規(guī)則,在通知失敗后按規(guī)則重復通知,直到通知N次后不再通知。

主動方提供校對查詢接口給被動方按需校對查詢,用于恢復丟失的業(yè)務消息。

業(yè)務活動的被動方如果正常接收了數(shù)據(jù),就正常返回響應,并結束事務。

如果被動方?jīng)]有正常接收,根據(jù)定時策略,向業(yè)務活動主動方查詢,恢復丟失的業(yè)務消息

最大努力通知方案的特點

用到的服務模式:可查詢操作、冪等操作。

被動方的處理結果不影響主動方的處理結果;

適用于對業(yè)務最終一致性的時間敏感度低的系統(tǒng);

適合跨企業(yè)的系統(tǒng)間的操作,或者企業(yè)內(nèi)部比較獨立的系統(tǒng)間的操作,比如銀行通知、商戶通知等;

方案比較

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發(fā): 721575865

群內(nèi)提供免費的Java架構學習資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容