作者:鄭鄭好victorzheng
juejin.im/post/5baa54e1f265da0ac2566fb2
文章綱要
-
此次分享的緣由
-
目前分布式事務問題是怎么解決的
-
行業中有什么解決方案
-
這些解決方案分別有什么優缺點
-
別人是怎么做的
-
我們可以怎么來做
此次分享的緣由
支付重構
考慮支付重構的時候,自然想到原本屬于一個本地事務中的處理,現在要跨應用了要怎么處理,拿充值訂單舉個栗子吧,假設:原本訂單模塊和賬戶模塊是放在一起的,現在需要做服務拆分,拆分成訂單服務,賬戶服務,原本收到充值回呼后,可以將修改訂單狀態和增加金幣放在一個mysql事務中完成的,但是呢,因為服務拆分了,就面臨著需要協調2個服務才能完成這個事務

所以就帶出來,我們今天要分享和討論的話題是:怎么解決分布式場景下資料一致性****問題,暫且用分布式事務來定義吧,
同樣的問題還存在于其他的場景:
送禮:
1. 呼叫支付服務:先扣送禮用戶的金幣,然后給主播加相應的荔枝
2. 確認第一步成功后,播放特效,發聊天室送禮評論等復制代碼
充值成功訊息:
-
完成充值訂單
-
發送訂單完成的kafka訊息
在涉及支付交易等付費介面的時候,資料一致性的問題就顯得尤為重要,因為都是錢****啊
目前分布式事務是怎么解決的呢?
問題肯定不是新問題,也就是目前已經有相應的解決方案了,那就看一下現在是怎么來解決這類問題的吧,
以購買基礎商品成功后發送支付訂單完成訊息為例:
假設支付下單購買基礎商品,此刻已經收到支付回呼,訂單已經處理成功了,這個時候kafka服務故障,訊息發送失敗;而這個時候處理訂單的事務已經提交了,怎****么保證訂單完成的訊息一定能發出去呢?

解讀一下這個流程:
綠色部分,表示流程正常運行的互動程序:
-
先往JobController中提交一個job(用于故障恢復)
-
提交成功后,開始處理訂單邏輯
-
處理完訂單邏輯之后,開始發送kafka訊息
-
訊息也發送成功后,洗掉第一步提交的job
黃色部分,表示流程出現了例外,資料可能存在不一致現象,這個時候就需要進行流程恢復
-
JobController任務控制器定時去redis查詢延時任務串列(每個任務都有一個時間戳,按時間戳排序過濾)
-
將任務進行恢復(呼叫job注冊時定義的處理方法)
-
任務執行成功,表示流程完成;否則下一個定時周期重試
問題:
-
基于redis存盤恢復任務,可能存在資料丟失風險
-
架構體系中沒有統一的分布式事務規范,可否將這層邏輯獨立為分布式事務中間件
-
缺少事務執行策略管理,如:控制最大重試次數等
-
事務執行狀態沒有記錄,追查需要去翻看日志
行業中有什么解決方案
說解決方案之前,我們先了解一下這些方案的理論依據,有助于幫助我們來理解和實踐這些方案
理論依據(討論的前提)
本地事務、分布式事務
如果說本地事務是解決單個資料源上的資料操作的一致性問題的話,那么分布式事務則是為了解決跨越多個資料源上資料操作的一致性問題,
強一致性、弱一致性、最終一致性
從客戶端角度,多行程并發訪問時,更新過的資料在不同行程如何獲取的不同策略,決定了不同的一致性,對于關系型資料庫,要求更新過的資料能被后續的訪問都能看到,這是強一致性,如果能容忍后續的部分或者全部訪問不到,則是弱一致性,如果經過一段時間后要求能訪問到更新后的資料,則是最終一致性
從服務端角度,如何盡快將更新后的資料分布到整個系統,降低達到最終一致性的時間視窗,是提高系統的可用度和用戶體驗非常重要的方面,對于分布式資料系統:
-
N — 資料復制的份數
-
W — 更新資料時需要保證寫完成的節點數
-
R — 讀取資料的時候需要讀取的節點數
如果W+R>N,寫的節點和讀的節點重疊,則是強一致性,例如對于典型的一主一備同步復制的關系型資料庫,N=2,W=2,R=1,則不管讀的是主庫還是備庫的資料,都是一致的,
如果W+R<=N,則是弱一致性,例如對于一主一備異步復制的關系型資料庫,N=2,W=1,R=1,則如果讀的是備庫,就可能無法讀取主庫已經更新過的資料,所以是弱一致性,
CAP理論
分布式環境下(資料分布)要任何時刻保證資料一致性是不可能的,只能采取妥協的方案來保證資料最終一致性,這個也就是著名的CAP定理,推薦閱讀:分布式系統架構常識:CAP理論,

需要明確的一點是,對于一個分布式系統而言,磁區容錯性是一個最基本的要求,因為 既然是一個分布式系統,那么分布式系統中的組件必然需要被部署到不同的節點,否則也就無所謂分布式系統了,因此必然出現子網路,而對于分布式系統而言,網 絡問題又是一個必定會出現的例外情況,因此磁區容錯性也就成為了一個分布式系統必然需要面對和解決的問題,因此系統架構師往往需要把精力花在如何根據業務 特點在C(一致性)和A(可用性)之間尋求平衡,
BASE 理論
BASE是Basically Available(基本可用)、Soft state(軟狀態)和Eventually consistent(最終一致性)三個短語的縮寫,BASE理論是對CAP中一致性和可用性權衡的結果,其來源于對大規模互聯網系統分布式實踐的總結, 是基于CAP定理逐步演化而來的,BASE理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,采用適當的方式來使系統達到最終一致性,
BASE理論面向的是大型高可用可擴展的分布式系統,和傳統的事物ACID特性是相反的,它完全不同于ACID的強一致性模型,而是通過犧牲強一致性來獲得可用性,并****允許資料在一段時間內是不一致的,但最終達到一致狀態,但同時,在實際的分布式場景中,不同業務單元和組件對資料一致性的要求是不同的,因此在具體的分布式系統架構設計程序中,ACID特性和BASE理論往往又會結合在一起,
柔性事務
不同于ACID的剛性事務,在分布式場景下基于BASE理論,就出現了柔性事務的概念,要想通過柔性事務來達到最終的一致性,就需要依賴于一些特性,這些特性在具體的方案中不一定都要滿足,因為不同的方案要求不一樣;但是都不滿足的話,是不可能做柔性事務的,
可見性(對外可查詢)
在分布式事務執行程序中,如果某一個步驟執行出錯,就需要明確的知道其他幾個操作的處理情況,這就需要其他的服務都能夠提供查詢介面,保證可以通過查詢來判斷操作的處理情況,
為了保證操作的可查詢,需要對于每一個服務的每一次呼叫都有一個全域唯一的標識,可以是業務單據號(如訂單號)、也可以是系統分配的操作流水號(如支付記錄流水號),除此之外,操作的時間資訊也要有完整的記錄,
冪等操作
冪等性,其實是一個數學概念,冪等函式,或冪等方法,是指可以使用相同引數重復執行,并能獲得相同結果的函式,推薦閱讀:服務高可用:冪等性設計,
f(f(x)) = f(x)
在編程中一個冪等操作的特點是其任意多次執行所產生的影響均與一次執行的影響相同,也就是說,同一個方法,使用同樣的引數,呼叫多次產生的業務結果與呼叫一次產生的業務結果相同,這一個要求其實也比較好理解,因為要保證資料的最終一致性,很多解決防范都會有很多重試的操作,如果一個方法不保證冪等,那么將無法被重試,冪等操作的實作方式有多種,如在系統中快取所有的請求與處理結果、檢測到重復操作后,直接回傳上一次的處理結果等,
業界方案
兩階段提交(2PC)
XA是X/Open CAE Specification (Distributed Transaction Processing)模型中定義的TM(Transaction Manager)與RM(Resource Manager)之間進行通信的介面,
在XA規范中,資料庫充當RM角色,應用需要充當TM的角色,即生成全域的txId,呼叫XAResource介面,把多個本地事務協調為全域統一的分布式事務,

二階段提交是XA的標準實作,它將分布式事務的提交拆分為2個階段:prepare和commit/rollback,
2PC模型中,在prepare階段需要等待所有參與子事務的反饋,因此可能造成資料庫資源鎖定時間過長,不適合并發高以及子事務生命周長較長的業務場景,兩階段提交這種解決方案屬于犧牲了一部分可用性來換取的一致性,
saga
saga的提出,最早是為了解決可能會長時間運行的分布式事務(long-running process)的問題,所謂long-running的分布式事務,是指那些企業業務流程,需要跨應用、跨企業來完成某個事務,甚至在事務流程中還需要有手工操作的參與,這類事務的完成時間可能以分計,以小時計,甚至可能以天計,這類事務如果按照事務的ACID的要求去設計,勢必造成系統的可用性大大的降低,試想一個由兩臺服務器一起參與的事務,服務器A發起事務,服務器B參與事務,B的事務需要人工參與,所以處理時間可能很長,如果按照ACID的原則,要保持事務的隔離性、一致性,服務器A中發起的事務中使用到的事務資源將會被鎖定,不允許其他應用訪問到事務程序中的中間結果,直到整個事務被提交或者回滾,這就造成事務A中的資源被長時間鎖定,系統的可用性將不可接受,
而saga,則是一種基于補償的訊息驅動的用于解決long-running process的一種解決方案,目標是為了在確保系統高可用的前提下盡量確保資料的一致性,還是上面的例子,如果用saga來實作,那就是這樣的流程:服務器A的事務先執行,如果執行順利,那么事務A就先行提交;如果提交成功,那么就開始執行事務B,如果事務B也執行順利,則事務B也提交,整個事務就算完成,但是如果事務B執行失敗,那事務B本身需要回滾,這時因為事務A已經提交,所以需要執行一個補償操作,將已經提交的事務A執行的操作作反操作,恢復到未執行前事務A的狀態,這樣的基于訊息驅動的****實作思路,就是saga,我們可以看出,saga是犧牲了資料的強一致性,僅僅實作了最終一致性,但是提高了系統整體的可用性,
補償事務(TCC)
TCC 其實就是采用的補償機制,其核心思想是:針對每個操作,都要注冊一個與其對應的確認和補償(撤銷)操作,TCC模型是把鎖的粒度完全交給業務處理,它分為三個階段:
-
Try 階段主要是對業務系統做檢測及資源預留
-
Confirm 階段主要是對業務系統做確認提交,Try階段執行成功并開始執行 Confirm階段時,默認 Confirm階段是不會出錯的,即:只要Try成功,Confirm一定成功,
-
Cancel 階段主要是在業務執行錯誤,需要回滾的狀態下執行的業務取消,預留資源釋放,
下面對TCC模式下,A賬戶往B賬戶匯款100元為例子,對業務的改造進行詳細的分析:

匯款服務和收款服務分別需要實作,Try-Confirm-Cancel介面,并在業務初始化階段將其注入到TCC事務管理器中,、
[匯款服務]
Try:
檢查A賬戶有效性,即查看A賬戶的狀態是否為“轉帳中”或者“凍結”;
檢查A賬戶余額是否充足;
從A賬戶中扣減100元,并將狀態置為“轉賬中”;
預留扣減資源,將從A往B賬戶轉賬100元這個事件存入訊息或者日志中;
Confirm:
不做任何操作;
Cancel:
A賬戶增加100元;
從日志或者訊息中,釋放扣減資源,
[收款服務]
Try:
檢查B賬戶賬戶是否有效;
Confirm:
讀取日志或者訊息,B賬戶增加100元;
從日志或者訊息中,釋放扣減資源;
Cancel:
不做任何操作,
由此可以看出,TCC模型對業務的侵入強,改造的難度大,
本地訊息表(異步確保)
本地訊息表這種實作方式應該是業界使用最多的,其核心思想是將分布式事務拆分成本地事務進行處理,這種思路是來源于ebay,我們可以從下面的流程圖中看出其中的一些細節:

基本思路就是:
訊息生產方,需要額外建一個訊息表,并記錄訊息發送狀態,訊息表和業務資料要在一個事務里提交,也就是說他們要在一個資料庫里面,然后訊息會經過MQ發送到訊息的消費方,如果訊息發送失敗,會進行重試發送,
訊息消費方,需要處理這個訊息,并完成自己的業務邏輯,此時如果本地事務處理成功,表明已經處理成功了,如果處理失敗,那么就會重試執行,如果是業務上面的失敗,可以給生產方發送一個業務補償訊息,通知生產方進行回滾等操作,
生產方和消費方定時掃描本地訊息表,把還沒處理完成的訊息或者失敗的訊息再發送一遍,如果有靠譜的自動對賬補賬邏輯,這種方案還是非常實用的,
事務訊息
事務訊息作為一種異步確保型事務, 將兩個事務分支通過MQ進行異步解耦,事務訊息的設計流程同樣借鑒了兩階段提交理論,整體互動流程如下圖所示:

-
事務發起方首先發送prepare訊息到MQ,
-
在發送prepare訊息成功后執行本地事務,
-
根據本地事務執行結果回傳commit或者是rollback,
-
如果訊息是rollback,MQ將洗掉該prepare訊息不進行下發,如果是commit訊息,MQ將會把這個訊息發送給consumer端,
-
如果執行本地事務程序中,執行端掛掉,或者超時,MQ將會不停的詢問其同組的其它producer來獲取狀態,
-
Consumer端的消費成功機制有MQ保證,
有一些第三方的MQ是支持事務訊息的,比如RocketMQ,但是市面上一些主流的MQ都是不支持事務訊息的,比如 RabbitMQ 和 Kafka 都不支持,
盡最大努力通知
最大努力通知方案主要也是借助MQ訊息系統來進行事務控制,這一點與可靠訊息最終一致方案一樣,看來MQ中間件確實在一個分布式系統架構中,扮演者重要的角色,最大努力通知方案是比較簡單的分布式事務方案,它本質上就是通過定期校對,實作資料一致性,
最大努力通知方案的實作
-
業務活動的主動方,在完成業務處理之后,向業務活動的被動方發送訊息,允許訊息丟失,
-
主動方可以設定時間階梯型通知規則,在通知失敗后按規則重復通知,直到通知N次后不再通知,
-
主動方提供校對查詢介面給被動方按需校對查詢,用于恢復丟失的業務訊息,
-
業務活動的被動方如果正常接收了資料,就正常回傳回應,并結束事務,
-
如果被動方沒有正常接收,根據定時策略,向業務活動主動方查詢,恢復丟失的業務訊息
最大努力通知方案的特點
-
用到的服務模式:可查詢操作、冪等操作,
-
被動方的處理結果不影響主動方的處理結果;
-
適用于對業務最終一致性的時間敏感度低的系統;
-
適合跨企業的系統間的操作,或者企業內部比較獨立的系統間的操作,比如銀行通知、商戶通知等;
方案比較

別人是怎么做的
alipay的分布式事務服務DTS
分布式事務服務(Distributed Transaction Service,簡稱 DTS)是一個分布式事務框架,用來保障在大規模分布式環境下事務的最終一致性,DTS 從架構上分為 xts-client 和 xts-server 兩部分,前者是一個嵌入客戶端應用的 Jar 包,主要負責事務資料的寫入和處理;后者是一個獨立的系統,主要負責例外事務的恢復,
核心概念
在 DTS 內部,我們將一個分布式事務的關聯方,分為發起方和參與者兩類:
發起方: 分布式事務的發起方負責啟動分布式事務,觸發創建相應的主事務記錄,發起方是分布式事務的協調者,負責呼叫參與者的服務,并記錄相應的事務日志,感知整個分布式事務狀態來決定整個事務是 COMMIT 還是 ROLLBACK,
參與者:參與者是分布式事務中的一個原子單位,所有參與者都必須在一階段介面(Prepare)中標注(Annotation)參與者的標識,它定義了 prepare、commit、rollback 3個基本介面,業務系統需要實作這3個介面,并保證其業務資料的冪等性,也必須保證 prepare 中的資料操作能夠被提交(COMMIT)或者回滾(ROLLBACK),從存盤結構上,DTS 的事務狀態資料可以分為主事務記錄(Activity)和分支事務記錄(Action)兩類:
主事務記錄 Activity:主事務記錄是整個分布式事務的主體,其最核心的資料結構是事務號(TX_ID)和事務狀態(STATE),它是在啟動分布式事務的時候持久化寫入資料庫的,它的狀態決定了這筆分布式事務的狀態,
分支事務記錄 Action:分支事務記錄是主事務記錄的一個子集,它記錄了一個參與者的資訊,其中包括參與者的 NAME 名稱,DTS 通過這個 NAME 來唯一定位一個參與者,通過這個分支事務資訊,我們就可以對參與者進行提交或者回滾操作,
這應該屬于我們上面所說的TCC模式,
eBay 本地訊息表
本地訊息表這種實作方式的思路,其實是源于ebay,后來通過支付寶等公司的布道,在業內廣泛使用,其基本的設計思想是將遠程分布式事務拆分成一系列的本地事務,如果不考慮性能及設計優雅,借助關系型資料庫中的表即可實作,
舉個經典的跨行轉賬的例子來描述,第一步,扣款1W,通過本地事務保證了憑證訊息插入到訊息表中,第二步,通知對方銀行賬戶上加1W了,那問題來了,如何通知到對方呢?
通常采用兩種方式:
-
采用時效性高的MQ,由對方訂閱訊息并監聽,有訊息時自動觸發事件
-
采用定時輪詢掃描的方式,去檢查訊息表的資料,
類似使用本地訊息表+訊息通知的還有去哪兒,蘑菇街
各種第三方支付回呼
最大努力通知型,如支付寶、微信的支付回呼介面方式,不斷回呼直至成功,或直至呼叫次數衰減至失敗狀態,
我們可以怎么來做
2PC/3PC需要資源管理器(mysql, redis)支持XA協議,且整個事務的執行期間需要鎖住事務資源,會降低性能,故先排除,
TCC的模式,需要事務介面提供try,confirm,cancel三個介面,提高了編程的復雜性,需要依賴于業務方來配合提供這樣的介面,推行難度大,暫時排除,
最大努力通知型,應用于異構或者服務平臺當中
可以看到ebay的經典模式中,分布式的事務,是通過本地事務+可靠訊息,來達到事務的最終一致性的,但是出現了事務訊息,就把本地事務的作業給涵蓋在事務訊息當中了,所以,接下來要基于事務訊息來套我們的應用場景,看起是否滿足我們對分布式事務產品的要求,
關注公眾號Java技術堆疊回復"面試"獲取我整理的2020最全面試題及答案,
推薦去我的博客閱讀更多:
1.Java JVM、集合、多執行緒、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架構、阿里巴巴等大廠最新面試題
覺得不錯,別忘了點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/155274.html
標籤:Java
