分布式事務,有很多種解決方案,你知道啥場景用哪種解決方案嗎?有啥優勢?瞧瞧這篇文章!
作者:鄭鄭好victorzheng
原文地址 https://juejin.im/post/5baa54e1f265da0ac2566fb2
文章綱要此次分享的緣由
目前分布式事務問題是怎麼解決的
行業中有什麼解決方案
這些解決方案分別有什麼優缺點
別人是怎麼做的
我們可以怎麼來做
此次分享的緣由支付重構考慮支付重構的時候,自然想到原本屬於一個本地事務中的處理,現在要跨應用了要怎麼處理。拿充值訂單舉個慄子吧,假設:原本訂單模塊和帳戶模塊是放在一起的,現在需要做服務拆分,拆分成訂單服務,帳戶服務。原本收到充值回調後,可以將修改訂單狀態和增加金幣放在一個 mysql 事務中完成的,但是呢,因為服務拆分了,就面臨著需要協調 2 個服務才能完成這個事務
所以就帶出來,我們今天要分享和討論的話題是:怎麼解決分布式場景下數據一致性問題,暫且用分布式事務 來定義吧。
同樣的問題還存在於其他的場景:
送禮:
1. 調用支付服務:先扣送禮用戶的金幣,然後給主播加相應的荔枝
2. 確認第一步成功後,播放特效,發聊天室送禮評論等
充值成功消息:
完成充值訂單
發送訂單完成的 kafka 消息
在涉及支付交易等付費接口的時候,數據一致性的問題就顯得尤為重要,因為都是錢啊
目前分布式事務是怎麼解決的呢?問題肯定不是新問題,也就是目前已經有相應的解決方案了,那就看一下現在是怎麼來解決這類問題的吧。
以購買基礎商品成功後發送支付訂單完成消息為例:
假設支付下單購買基礎商品,此刻已經收到支付回調,訂單已經處理成功了,這個時候 kafka 服務故障,消息發送失敗;而這個時候處理訂單的事務已經提交了,怎麼保證訂單完成的消息一定能發出去呢?
解讀一下這個流程:
綠色部分,表示流程正常運行的交互過程:
先往 JobController 中提交一個 job(用於故障恢復)
提交成功後,開始處理訂單邏輯
處理完訂單邏輯之後,開始發送 kafka 消息
消息也發送成功後,刪除第一步提交的 job
黃色部分,表示流程出現了異常,數據可能存在不一致現象。這個時候就需要進行流程恢復
JobController 任務控制器定時去 redis 查詢延時任務列表(每個任務都有一個時間戳,按時間戳排序過濾)
將任務進行恢復(調用 job 註冊時定義的處理方法)
任務執行成功,表示流程完成;否則下一個定時周期重試
問題:
基於 redis 存儲恢復任務,可能存在數據丟失風險
架構體系中沒有統一的分布式事務規範,可否將這層邏輯獨立為分布式事務中間件
缺少事務執行策略管理,如:控制最大重試次數等
事務執行狀態沒有記錄,追查需要去翻看日誌
行業中有什麼解決方案說解決方案之前,我們先了解一下這些方案的理論依據,有助於幫助我們來理解和實踐這些方案
理論依據(討論的前提)本地事務、分布式事務如果說本地事務是解決單個數據源上的數據操作的一致性問題的話,那麼分布式事務則是為了解決跨越多個數據源上數據操作的一致性問題。
超詳細!4小時開發一個SpringBoot+vue前後端分離博客項目!!
從客戶端角度,多進程並發訪問時,更新過的數據在不同進程如何獲取的不同策略,決定了不同的一致性。對於關係型資料庫,要求更新過的數據能被後續的訪問都能看到,這是強一致性。如果能容忍後續的部分或者全部訪問不到,則是弱一致性。如果經過一段時間後要求能訪問到更新後的數據,則是最終一致性
從服務端角度,如何儘快將更新後的數據分布到整個系統,降低達到最終一致性的時間窗口,是提高系統的可用度和用戶體驗非常重要的方面。對於分布式數據系統:
N — 數據複製的份數
W — 更新數據時需要保證寫完成的節點數
R — 讀取數據的時候需要讀取的節點數
如果 W+R>N,寫的節點和讀的節點重疊,則是強一致性。例如對於典型的一主一備同步複製的關係型資料庫,N=2,W=2,R=1,則不管讀的是主庫還是備庫的數據,都是一致的。
如果 W+R<=N,則是弱一致性。例如對於一主一備異步複製的關係型資料庫,N=2,W=1,R=1,則如果讀的是備庫,就可能無法讀取主庫已經更新過的數據,所以是弱一致性。
CAP 理論分布式環境下(數據分布)要任何時刻保證數據一致性是不可能的,只能採取妥協的方案來保證數據最終一致性。這個也就是著名的 CAP 定理。
需要明確的一點是,對於一個分布式系統而言,分區容錯性是一個最基本的要求。因為 既然是一個分布式系統,那麼分布式系統中的組件必然需要被部署到不同的節點,否則也就無所謂分布式系統了,因此必然出現子網絡。而對於分布式系統而言,網 絡問題又是一個必定會出現的異常情況,因此分區容錯性也就成為了一個分布式系統必然需要面對和解決的問題。因此系統架構師往往需要把精力花在如何根據業務 特點在 C(一致性)和 A(可用性)之間尋求平衡。
BASE 理論BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent(最終一致性)三個短語的縮寫。BASE 理論是對 CAP 中一致性和可用性權衡的結果,其來源於對大規模網際網路系統分布式實踐的總結, 是基於 CAP 定理逐步演化而來的。BASE 理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,採用適當的方式來使系統達到最終一致性。
BASE 理論面向的是大型高可用可擴展的分布式系統,和傳統的事物 ACID 特性是相反的,它完全不同於 ACID 的強一致性模型,而是通過犧牲強一致性來獲得可用性,並允許數據在一段時間內是不一致的,但最終達到一致狀態。但同時,在實際的分布式場景中,不同業務單元和組件對數據一致性的要求是不同的,因此在具體的分布式系統架構設計過程中,ACID 特性和 BASE 理論往往又會結合在一起。
柔性事務不同於 ACID 的剛性事務,在分布式場景下基於 BASE 理論,就出現了柔性事務的概念。要想通過柔性事務來達到最終的一致性,就需要依賴於一些特性,這些特性在具體的方案中不一定都要滿足,因為不同的方案要求不一樣;但是都不滿足的話,是不可能做柔性事務的。
可見性 (對外可查詢)在分布式事務執行過程中,如果某一個步驟執行出錯,就需要明確的知道其他幾個操作的處理情況,這就需要其他的服務都能夠提供查詢接口,保證可以通過查詢來判斷操作的處理情況。
為了保證操作的可查詢,需要對於每一個服務的每一次調用都有一個全局唯一的標識,可以是業務單據號(如訂單號)、也可以是系統分配的操作流水號(如支付記錄流水號)。除此之外,操作的時間信息也要有完整的記錄。
分享一套SpringBoot開發博客系統源碼,以及完整開發文檔!速度保存!
冪等操作冪等性,其實是一個數學概念。冪等函數,或冪等方法,是指可以使用相同參數重複執行,並能獲得相同結果的函數。
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 階段需要等待所有參與子事務的反饋,因此可能造成資料庫資源鎖定時間過長,不適合併發高以及子事務生命周長較長的業務場景。兩階段提交這種解決方案屬於犧牲了一部分可用性來換取的一致性。
sagasaga 的提出,最早是為了解決可能會長時間運行的分布式事務(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 事務管理器中。
由此可以看出,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 次後不再通知。
主動方提供校對查詢接口給被動方按需校對查詢,用於恢復丟失的業務消息。
業務活動的被動方如果正常接收了數據,就正常返迴響應,並結束事務。
如果被動方沒有正常接收,根據定時策略,向業務活動主動方查詢,恢復丟失的業務消息
最大努力通知方案的特點
用到的服務模式:可查詢操作、冪等操作。
被動方的處理結果不影響主動方的處理結果;
適用於對業務最終一致性的時間敏感度低的系統;
適合跨企業的系統間的操作,或者企業內部比較獨立的系統間的操作,比如銀行通知、商戶通知等;
方案比較屬性2PCTCC本地消息表事務消息盡最大努力通知事務一致性強弱弱弱弱複雜性中高低低低業務侵入性小大中中中使用局限性大大小中中性能低中高高高維護成本低高低中中別人是怎麼做的alipay 的分布式事務服務 DTSwww.cloud.alipay.com/docs/2/4688…
分布式事務服務(Distributed Transaction Service,簡稱 DTS)是一個分布式事務框架,用來保障在大規模分布式環境下事務的最終一致性。DTS 從架構上分為 xts-client 和 xts-server 兩部分,前者是一個嵌入客戶端應用的 Jar 包,主要負責事務數據的寫入和處理;後者是一個獨立的系統,主要負責異常事務的恢復。
Github上最值得學習的100個Java開源項目,涵蓋各種技術棧!
核心概念
在 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 本地消息表www.infoq.com/cn/articles… weibo.com/ttarticle/p…
本地消息表這種實現方式的思路,其實是源於 ebay,後來通過支付寶等公司的布道,在業內廣泛使用。其基本的設計思想是將遠程分布式事務拆分成一系列的本地事務。如果不考慮性能及設計優雅,藉助關係型資料庫中的表即可實現。
舉個經典的跨行轉帳的例子來描述。第一步,扣款 1W,通過本地事務保證了憑證消息插入到消息表中。第二步,通知對方銀行帳戶上加 1W 了。那問題來了,如何通知到對方呢?
通常採用兩種方式:
採用時效性高的 MQ,由對方訂閱消息並監聽,有消息時自動觸發事件
採用定時輪詢掃描的方式,去檢查消息表的數據。
類似使用本地消息表 + 消息通知的還有去哪兒,蘑菇街
各種第三方支付回調最大努力通知型。如支付寶、微信的支付回調接口方式,不斷回調直至成功,或直至調用次數衰減至失敗狀態。
我們可以怎麼來做2PC/3PC 需要資源管理器 (mysql, redis) 支持 XA 協議,且整個事務的執行期間需要鎖住事務資源,會降低性能。故先排除。
TCC 的模式,需要事務接口提供 try,confirm,cancel 三個接口,提高了編程的複雜性。需要依賴於業務方來配合提供這樣的接口。推行難度大,暫時排除。
最大努力通知型,應用於異構或者服務平臺當中
可以看到 ebay 的經典模式中,分布式的事務,是通過本地事務 + 可靠消息,來達到事務的最終一致性的。但是出現了事務消息,就把本地事務的工作給涵蓋在事務消息當中了。所以,接下來要基於事務消息來套我們的應用場景,看起是否滿足我們對分布式事務產品的要求
Q&A討論 DDD (一臉懵逼)
saga 落地
參考Sharding-Sphere:mp.weixin.qq.com/s/LpebzrHU3…
RocketMQ 正式開源分布式事務消息:mp.weixin.qq.com/s/Kxk2Ag-7d…
最大努力通知:blog.csdn.net/zsh2050/art…
聊聊分布式事務:www.cnblogs.com/savorboard/…
saga: www.cnblogs.com/netfocus/p/…
(完)
MarkerHub文章索引:(點擊閱讀原文直達)
https://github.com/MarkerHub/JavaIndex
【推薦閱讀】
又一經典開源項目Guns完全解讀,小Hub好棒!
前、後端分離權限控制設計與實現
高中生也能讀懂的Docker入門教程
終於有人把 HTTPS 原理講清楚了!
超詳細!4小時開發一個SpringBoot+vue前後端分離博客項目!!
知乎問答:難道非得包裝簡歷?可是做不來~