分布式事務(wù)
1.分布式事務(wù)問題
1.1.本地事務(wù)
本地事務(wù),也就是傳統(tǒng)的單機(jī)事務(wù)。在傳統(tǒng)數(shù)據(jù)庫(kù)事務(wù)中,必須要滿足四個(gè)原則:
1.2.分布式事務(wù)
分布式事務(wù),就是指不是在單個(gè)服務(wù)或單個(gè)數(shù)據(jù)庫(kù)架構(gòu)下,產(chǎn)生的事務(wù),例如:
- 跨數(shù)據(jù)源的分布式事務(wù)
- 跨服務(wù)的分布式事務(wù)
- 綜合情況
在數(shù)據(jù)庫(kù)水平拆分、服務(wù)垂直拆分之后,一個(gè)業(yè)務(wù)操作通常要跨多個(gè)數(shù)據(jù)庫(kù)、服務(wù)才能完成。例如電商行業(yè)中比較常見的下單付款案例,包括下面幾個(gè)行為:
- 創(chuàng)建新訂單
- 扣減商品庫(kù)存
- 從用戶賬戶余額扣除金額
完成上面的操作需要訪問三個(gè)不同的微服務(wù)和三個(gè)不同的數(shù)據(jù)庫(kù)。
訂單的創(chuàng)建、庫(kù)存的扣減、賬戶扣款在每一個(gè)服務(wù)和數(shù)據(jù)庫(kù)內(nèi)是一個(gè)本地事務(wù),可以保證ACID原則。
但是當(dāng)我們把三件事情看做一個(gè)"業(yè)務(wù)",要滿足保證“業(yè)務(wù)”的原子性,要么所有操作全部成功,要么全部失敗,不允許出現(xiàn)部分成功部分失敗的現(xiàn)象,這就是分布式系統(tǒng)下的事務(wù)了。
此時(shí)ACID難以滿足,這是分布式事務(wù)要解決的問題
1.3.演示分布式事務(wù)問題
我們通過一個(gè)案例來演示分布式事務(wù)的問題:
1)創(chuàng)建數(shù)據(jù)庫(kù),名為seata_demo,然后導(dǎo)入課前資料提供的SQL文件:
2)導(dǎo)入課前資料提供的微服務(wù):
微服務(wù)結(jié)構(gòu)如下:
其中:
seata-demo:父工程,負(fù)責(zé)管理項(xiàng)目依賴
- account-service:賬戶服務(wù),負(fù)責(zé)管理用戶的資金賬戶。提供扣減余額的接口
- storage-service:庫(kù)存服務(wù),負(fù)責(zé)管理商品庫(kù)存。提供扣減庫(kù)存的接口
- order-service:訂單服務(wù),負(fù)責(zé)管理訂單。創(chuàng)建訂單時(shí),需要調(diào)用account-service和storage-service
3)啟動(dòng)nacos、所有微服務(wù)
4)測(cè)試下單功能,發(fā)出Post請(qǐng)求:
請(qǐng)求如下:
curl --location --request POST 'http://localhost:8082/order?userId=user202103032042012&commodityCode=100202003032041&count=20&money=200'
如圖:
測(cè)試發(fā)現(xiàn),當(dāng)庫(kù)存不足時(shí),如果余額已經(jīng)扣減,并不會(huì)回滾,出現(xiàn)了分布式事務(wù)問題。
2.理論基礎(chǔ)
解決分布式事務(wù)問題,需要一些分布式系統(tǒng)的基礎(chǔ)知識(shí)作為理論指導(dǎo)。
2.1.CAP定理
1998年,加州大學(xué)的計(jì)算機(jī)科學(xué)家 Eric Brewer 提出,分布式系統(tǒng)有三個(gè)指標(biāo)。
- Consistency(一致性)
- Availability(可用性)
- Partition tolerance (分區(qū)容錯(cuò)性)
它們的第一個(gè)字母分別是 C、A、P。
Eric Brewer 說,這三個(gè)指標(biāo)不可能同時(shí)做到。這個(gè)結(jié)論就叫做 CAP 定理。
2.1.1.一致性
Consistency(一致性):用戶訪問分布式系統(tǒng)中的任意節(jié)點(diǎn),得到的數(shù)據(jù)必須一致。
比如現(xiàn)在包含兩個(gè)節(jié)點(diǎn),其中的初始數(shù)據(jù)是一致的:
當(dāng)我們修改其中一個(gè)節(jié)點(diǎn)的數(shù)據(jù)時(shí),兩者的數(shù)據(jù)產(chǎn)生了差異:
要想保住一致性,就必須實(shí)現(xiàn)node01 到 node02的數(shù)據(jù) 同步:
2.1.2.可用性
Availability (可用性):用戶訪問集群中的任意健康節(jié)點(diǎn),必須能得到響應(yīng),而不是超時(shí)或拒絕。
如圖,有三個(gè)節(jié)點(diǎn)的集群,訪問任何一個(gè)都可以及時(shí)得到響應(yīng):
當(dāng)有部分節(jié)點(diǎn)因?yàn)榫W(wǎng)絡(luò)故障或其它原因無法訪問時(shí),代表節(jié)點(diǎn)不可用:
2.1.3.分區(qū)容錯(cuò)
Partition(分區(qū)):因?yàn)榫W(wǎng)絡(luò)故障或其它原因?qū)е路植际较到y(tǒng)中的部分節(jié)點(diǎn)與其它節(jié)點(diǎn)失去連接,形成獨(dú)立分區(qū)。
Tolerance(容錯(cuò)):在集群出現(xiàn)分區(qū)時(shí),整個(gè)系統(tǒng)也要持續(xù)對(duì)外提供服務(wù)
2.1.4.矛盾
在分布式系統(tǒng)中,系統(tǒng)間的網(wǎng)絡(luò)不能100%保證健康,一定會(huì)有故障的時(shí)候,而服務(wù)有必須對(duì)外保證服務(wù)。因此Partition Tolerance不可避免。
當(dāng)節(jié)點(diǎn)接收到新的數(shù)據(jù)變更時(shí),就會(huì)出現(xiàn)問題了:
如果此時(shí)要保證一致性,就必須等待網(wǎng)絡(luò)恢復(fù),完成數(shù)據(jù)同步后,整個(gè)集群才對(duì)外提供服務(wù),服務(wù)處于阻塞狀態(tài),不可用。
如果此時(shí)要保證可用性,就不能等待網(wǎng)絡(luò)恢復(fù),那node01、node02與node03之間就會(huì)出現(xiàn)數(shù)據(jù)不一致。
也就是說,在P一定會(huì)出現(xiàn)的情況下,A和C之間只能實(shí)現(xiàn)一個(gè)。
2.2.BASE理論
BASE理論是對(duì)CAP的一種解決思路,包含三個(gè)思想:
- Basically Available (基本可用):分布式系統(tǒng)在出現(xiàn)故障時(shí),允許損失部分可用性,即保證核心可用。
- **Soft State(軟狀態(tài)):**在一定時(shí)間內(nèi),允許出現(xiàn)中間狀態(tài),比如臨時(shí)的不一致狀態(tài)。
- Eventually Consistent(最終一致性):雖然無法保證強(qiáng)一致性,但是在軟狀態(tài)結(jié)束后,最終達(dá)到數(shù)據(jù)一致。
2.3.解決分布式事務(wù)的思路
分布式事務(wù)最大的問題是各個(gè)子事務(wù)的一致性問題,因此可以借鑒CAP定理和BASE理論,有兩種解決思路:
-
AP模式:各子事務(wù)分別執(zhí)行和提交,允許出現(xiàn)結(jié)果不一致,然后采用彌補(bǔ)措施恢復(fù)數(shù)據(jù)即可,實(shí)現(xiàn)最終一致。
-
CP模式:各個(gè)子事務(wù)執(zhí)行后互相等待,同時(shí)提交,同時(shí)回滾,達(dá)成強(qiáng)一致。但事務(wù)等待過程中,處于弱可用狀態(tài)。
但不管是哪一種模式,都需要在子系統(tǒng)事務(wù)之間互相通訊,協(xié)調(diào)事務(wù)狀態(tài),也就是需要一個(gè)事務(wù)協(xié)調(diào)者(TC):
這里的子系統(tǒng)事務(wù),稱為分支事務(wù);有關(guān)聯(lián)的各個(gè)分支事務(wù)在一起稱為全局事務(wù)。
3.初識(shí)Seata
Seata是 2019 年 1 月份螞蟻金服和阿里巴巴共同開源的分布式事務(wù)解決方案。致力于提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù),為用戶打造一站式的分布式解決方案。
官網(wǎng)地址:http://seata.io/,其中的文檔、播客中提供了大量的使用說明、源碼分析。
3.1.Seata的架構(gòu)
Seata事務(wù)管理中有三個(gè)重要的角色:
-
TC (Transaction Coordinator) - **事務(wù)協(xié)調(diào)者:**維護(hù)全局和分支事務(wù)的狀態(tài),協(xié)調(diào)全局事務(wù)提交或回滾。
-
TM (Transaction Manager) - **事務(wù)管理器:**定義全局事務(wù)的范圍、開始全局事務(wù)、提交或回滾全局事務(wù)。
-
RM (Resource Manager) - **資源管理器:**管理分支事務(wù)處理的資源,與TC交談以注冊(cè)分支事務(wù)和報(bào)告分支事務(wù)的狀態(tài),并驅(qū)動(dòng)分支事務(wù)提交或回滾。
整體的架構(gòu)如圖:
Seata基于上述架構(gòu)提供了四種不同的分布式事務(wù)解決方案:
- XA模式:強(qiáng)一致性分階段事務(wù)模式,犧牲了一定的可用性,無業(yè)務(wù)侵入
- TCC模式:最終一致的分階段事務(wù)模式,有業(yè)務(wù)侵入
- AT模式:最終一致的分階段事務(wù)模式,無業(yè)務(wù)侵入,也是Seata的默認(rèn)模式
- SAGA模式:長(zhǎng)事務(wù)模式,有業(yè)務(wù)侵入
無論哪種方案,都離不開TC,也就是事務(wù)的協(xié)調(diào)者。
3.2.部署TC服務(wù)
參考 seata的部署和集成
3.3.微服務(wù)集成Seata
我們以order-service為例來演示。
3.3.1.引入依賴
首先,在order-service中引入依賴:
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<!--版本較低,1.3.0,因此排除-->
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<!--seata starter 采用1.4.2版本-->
<version>${seata.version}</version>
</dependency>
3.3.2.配置TC地址
在order-service中的application.yml中,配置TC服務(wù)信息,通過注冊(cè)中心nacos,結(jié)合服務(wù)名稱獲取TC地址:
seata:
registry: # TC服務(wù)注冊(cè)中心的配置,微服務(wù)根據(jù)這些信息去注冊(cè)中心獲取tc服務(wù)地址
type: nacos # 注冊(cè)中心類型 nacos
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
namespace: "" # namespace,默認(rèn)為空
group: DEFAULT_GROUP # 分組,默認(rèn)是DEFAULT_GROUP
application: seata-tc-server # seata服務(wù)名稱
username: nacos
password: nacos
tx-service-group: seata-demo # 事務(wù)組名稱
service:
vgroup-mapping: # 事務(wù)組與cluster的映射關(guān)系
seata-demo: SH
微服務(wù)如何根據(jù)這些配置尋找TC的地址呢?
我們知道注冊(cè)到Nacos中的微服務(wù),確定一個(gè)具體實(shí)例需要四個(gè)信息:
- namespace:命名空間
- group:分組
- application:服務(wù)名
- cluster:集群名
以上四個(gè)信息,在剛才的yaml文件中都能找到:
namespace為空,就是默認(rèn)的public
結(jié)合起來,TC服務(wù)的信息就是:public@DEFAULT_GROUP@seata-tc-server@SH,這樣就能確定TC服務(wù)集群了。然后就可以去Nacos拉取對(duì)應(yīng)的實(shí)例信息了。
3.3.3.其它服務(wù)
其它兩個(gè)微服務(wù)也都參考o(jì)rder-service的步驟來做,完全一樣。
4.動(dòng)手實(shí)踐
下面我們就一起學(xué)習(xí)下Seata中的四種不同的事務(wù)模式。
4.1.XA模式
XA 規(guī)范 是 X/Open 組織定義的分布式事務(wù)處理(DTP,Distributed Transaction Processing)標(biāo)準(zhǔn),XA 規(guī)范 描述了全局的TM與局部的RM之間的接口,幾乎所有主流的數(shù)據(jù)庫(kù)都對(duì) XA 規(guī)范 提供了支持。
4.1.1.兩階段提交
XA是規(guī)范,目前主流數(shù)據(jù)庫(kù)都實(shí)現(xiàn)了這種規(guī)范,實(shí)現(xiàn)的原理都是基于兩階段提交。
正常情況:
異常情況:
一階段:
- 事務(wù)協(xié)調(diào)者通知每個(gè)事物參與者執(zhí)行本地事務(wù)
- 本地事務(wù)執(zhí)行完成后報(bào)告事務(wù)執(zhí)行狀態(tài)給事務(wù)協(xié)調(diào)者,此時(shí)事務(wù)不提交,繼續(xù)持有數(shù)據(jù)庫(kù)鎖
二階段:
- 事務(wù)協(xié)調(diào)者基于一階段的報(bào)告來判斷下一步操作
- 如果一階段都成功,則通知所有事務(wù)參與者,提交事務(wù)
- 如果一階段任意一個(gè)參與者失敗,則通知所有事務(wù)參與者回滾事務(wù)
4.1.2.Seata的XA模型
Seata對(duì)原始的XA模式做了簡(jiǎn)單的封裝和改造,以適應(yīng)自己的事務(wù)模型,基本架構(gòu)如圖:
RM一階段的工作:
? ① 注冊(cè)分支事務(wù)到TC
? ② 執(zhí)行分支業(yè)務(wù)sql但不提交
? ③ 報(bào)告執(zhí)行狀態(tài)到TC
TC二階段的工作:
-
TC檢測(cè)各分支事務(wù)執(zhí)行狀態(tài)
a.如果都成功,通知所有RM提交事務(wù)
b.如果有失敗,通知所有RM回滾事務(wù)
RM二階段的工作:
- 接收TC指令,提交或回滾事務(wù)
4.1.3.優(yōu)缺點(diǎn)
XA模式的優(yōu)點(diǎn)是什么?
- 事務(wù)的強(qiáng)一致性,滿足ACID原則。
- 常用數(shù)據(jù)庫(kù)都支持,實(shí)現(xiàn)簡(jiǎn)單,并且沒有代碼侵入
XA模式的缺點(diǎn)是什么?
- 因?yàn)橐浑A段需要鎖定數(shù)據(jù)庫(kù)資源,等待二階段結(jié)束才釋放,性能較差
- 依賴關(guān)系型數(shù)據(jù)庫(kù)實(shí)現(xiàn)事務(wù)
4.1.4.實(shí)現(xiàn)XA模式
Seata的starter已經(jīng)完成了XA模式的自動(dòng)裝配,實(shí)現(xiàn)非常簡(jiǎn)單,步驟如下:
1)修改application.yml文件(每個(gè)參與事務(wù)的微服務(wù)),開啟XA模式:
seata:
data-source-proxy-mode: XA
2)給發(fā)起全局事務(wù)的入口方法添加@GlobalTransactional注解:
本例中是OrderServiceImpl中的create方法.
3)重啟服務(wù)并測(cè)試
重啟order-service,再次測(cè)試,發(fā)現(xiàn)無論怎樣,三個(gè)微服務(wù)都能成功回滾。
4.2.AT模式
AT模式同樣是分階段提交的事務(wù)模型,不過缺彌補(bǔ)了XA模型中資源鎖定周期過長(zhǎng)的缺陷。
4.2.1.Seata的AT模型
基本流程圖:
階段一RM的工作:
- 注冊(cè)分支事務(wù)
- 記錄undo-log(數(shù)據(jù)快照)
- 執(zhí)行業(yè)務(wù)sql并提交
- 報(bào)告事務(wù)狀態(tài)
階段二提交時(shí)RM的工作:
- 刪除undo-log即可
階段二回滾時(shí)RM的工作:
- 根據(jù)undo-log恢復(fù)數(shù)據(jù)到更新前
4.2.2.流程梳理
我們用一個(gè)真實(shí)的業(yè)務(wù)來梳理下AT模式的原理。
比如,現(xiàn)在又一個(gè)數(shù)據(jù)庫(kù)表,記錄用戶余額:
id | money |
---|---|
1 | 100 |
其中一個(gè)分支業(yè)務(wù)要執(zhí)行的SQL為:
update tb_account set money = money - 10 where id = 1
AT模式下,當(dāng)前分支事務(wù)執(zhí)行流程如下:
一階段:
1)TM發(fā)起并注冊(cè)全局事務(wù)到TC
2)TM調(diào)用分支事務(wù)
3)分支事務(wù)準(zhǔn)備執(zhí)行業(yè)務(wù)SQL
4)RM攔截業(yè)務(wù)SQL,根據(jù)where條件查詢?cè)紨?shù)據(jù),形成快照。
{
"id": 1, "money": 100
}
5)RM執(zhí)行業(yè)務(wù)SQL,提交本地事務(wù),釋放數(shù)據(jù)庫(kù)鎖。此時(shí) money = 90
6)RM報(bào)告本地事務(wù)狀態(tài)給TC
二階段:
1)TM通知TC事務(wù)結(jié)束
2)TC檢查分支事務(wù)狀態(tài)
? a)如果都成功,則立即刪除快照
? b)如果有分支事務(wù)失敗,需要回滾。讀取快照數(shù)據(jù)({"id": 1, "money": 100}
),將快照恢復(fù)到數(shù)據(jù)庫(kù)。此時(shí)數(shù)據(jù)庫(kù)再次恢復(fù)為100
流程圖:
4.2.3.AT與XA的區(qū)別
簡(jiǎn)述AT模式與XA模式最大的區(qū)別是什么?
- XA模式一階段不提交事務(wù),鎖定資源;AT模式一階段直接提交,不鎖定資源。
- XA模式依賴數(shù)據(jù)庫(kù)機(jī)制實(shí)現(xiàn)回滾;AT模式利用數(shù)據(jù)快照實(shí)現(xiàn)數(shù)據(jù)回滾。
- XA模式強(qiáng)一致;AT模式最終一致
4.2.4.臟寫問題
在多線程并發(fā)訪問AT模式的分布式事務(wù)時(shí),有可能出現(xiàn)臟寫問題,如圖:
解決思路就是引入了全局鎖的概念。在釋放DB鎖之前,先拿到全局鎖。避免同一時(shí)刻有另外一個(gè)事務(wù)來操作當(dāng)前數(shù)據(jù)。
4.2.5.優(yōu)缺點(diǎn)
AT模式的優(yōu)點(diǎn):
- 一階段完成直接提交事務(wù),釋放數(shù)據(jù)庫(kù)資源,性能比較好
- 利用全局鎖實(shí)現(xiàn)讀寫隔離
- 沒有代碼侵入,框架自動(dòng)完成回滾和提交
AT模式的缺點(diǎn):
- 兩階段之間屬于軟狀態(tài),屬于最終一致
- 框架的快照功能會(huì)影響性能,但比XA模式要好很多
4.2.6.實(shí)現(xiàn)AT模式
AT模式中的快照生成、回滾等動(dòng)作都是由框架自動(dòng)完成,沒有任何代碼侵入,因此實(shí)現(xiàn)非常簡(jiǎn)單。
只不過,AT模式需要一個(gè)表來記錄全局鎖、另一張表來記錄數(shù)據(jù)快照undo_log。
1)導(dǎo)入數(shù)據(jù)庫(kù)表,記錄全局鎖
導(dǎo)入課前資料提供的Sql文件:seata-at.sql,其中l(wèi)ock_table導(dǎo)入到TC服務(wù)關(guān)聯(lián)的數(shù)據(jù)庫(kù),undo_log表導(dǎo)入到微服務(wù)關(guān)聯(lián)的數(shù)據(jù)庫(kù):
2)修改application.yml文件,將事務(wù)模式修改為AT模式即可:
seata:
data-source-proxy-mode: AT # 默認(rèn)就是AT
3)重啟服務(wù)并測(cè)試
4.3.TCC模式
TCC模式與AT模式非常相似,每階段都是獨(dú)立事務(wù),不同的是TCC通過人工編碼來實(shí)現(xiàn)數(shù)據(jù)恢復(fù)。需要實(shí)現(xiàn)三個(gè)方法:
-
Try:資源的檢測(cè)和預(yù)留;
-
Confirm:完成資源操作業(yè)務(wù);要求 Try 成功 Confirm 一定要能成功。
-
Cancel:預(yù)留資源釋放,可以理解為try的反向操作。
4.3.1.流程分析
舉例,一個(gè)扣減用戶余額的業(yè)務(wù)。假設(shè)賬戶A原來余額是100,需要余額扣減30元。
- 階段一( Try ):檢查余額是否充足,如果充足則凍結(jié)金額增加30元,可用余額扣除30
初始余額:
余額充足,可以凍結(jié):
此時(shí),總金額 = 凍結(jié)金額 + 可用金額,數(shù)量依然是100不變。事務(wù)直接提交無需等待其它事務(wù)。
- 階段二(Confirm):假如要提交(Confirm),則凍結(jié)金額扣減30
確認(rèn)可以提交,不過之前可用金額已經(jīng)扣減過了,這里只要清除凍結(jié)金額就好了:
此時(shí),總金額 = 凍結(jié)金額 + 可用金額 = 0 + 70 = 70元
- 階段二(Canncel):如果要回滾(Cancel),則凍結(jié)金額扣減30,可用余額增加30
需要回滾,那么就要釋放凍結(jié)金額,恢復(fù)可用金額:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-9Fup2a0S-1686045878545)(assets/image-20210724182810734.png)]
4.3.2.Seata的TCC模型
Seata中的TCC模型依然延續(xù)之前的事務(wù)架構(gòu),如圖:
4.3.3.優(yōu)缺點(diǎn)
TCC模式的每個(gè)階段是做什么的?
- Try:資源檢查和預(yù)留
- Confirm:業(yè)務(wù)執(zhí)行和提交
- Cancel:預(yù)留資源的釋放
TCC的優(yōu)點(diǎn)是什么?
- 一階段完成直接提交事務(wù),釋放數(shù)據(jù)庫(kù)資源,性能好
- 相比AT模型,無需生成快照,無需使用全局鎖,性能最強(qiáng)
- 不依賴數(shù)據(jù)庫(kù)事務(wù),而是依賴補(bǔ)償操作,可以用于非事務(wù)型數(shù)據(jù)庫(kù)
TCC的缺點(diǎn)是什么?
- 有代碼侵入,需要人為編寫try、Confirm和Cancel接口,太麻煩
- 軟狀態(tài),事務(wù)是最終一致
- 需要考慮Confirm和Cancel的失敗情況,做好冪等處理
4.3.4.事務(wù)懸掛和空回滾
1)空回滾
當(dāng)某分支事務(wù)的try階段阻塞時(shí),可能導(dǎo)致全局事務(wù)超時(shí)而觸發(fā)二階段的cancel操作。在未執(zhí)行try操作時(shí)先執(zhí)行了cancel操作,這時(shí)cancel不能做回滾,就是空回滾。
如圖:
執(zhí)行cancel操作時(shí),應(yīng)當(dāng)判斷try是否已經(jīng)執(zhí)行,如果尚未執(zhí)行,則應(yīng)該空回滾。
2)業(yè)務(wù)懸掛
對(duì)于已經(jīng)空回滾的業(yè)務(wù),之前被阻塞的try操作恢復(fù),繼續(xù)執(zhí)行try,就永遠(yuǎn)不可能confirm或cancel ,事務(wù)一直處于中間狀態(tài),這就是業(yè)務(wù)懸掛。
執(zhí)行try操作時(shí),應(yīng)當(dāng)判斷cancel是否已經(jīng)執(zhí)行過了,如果已經(jīng)執(zhí)行,應(yīng)當(dāng)阻止空回滾后的try操作,避免懸掛
4.3.5.實(shí)現(xiàn)TCC模式
解決空回滾和業(yè)務(wù)懸掛問題,必須要記錄當(dāng)前事務(wù)狀態(tài),是在try、還是cancel?
1)思路分析
這里我們定義一張表:
CREATE TABLE `account_freeze_tbl` (
`xid` varchar(128) NOT NULL,
`user_id` varchar(255) DEFAULT NULL COMMENT '用戶id',
`freeze_money` int(11) unsigned DEFAULT '0' COMMENT '凍結(jié)金額',
`state` int(1) DEFAULT NULL COMMENT '事務(wù)狀態(tài),0:try,1:confirm,2:cancel',
PRIMARY KEY (`xid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT;
其中:
- xid:是全局事務(wù)id
- freeze_money:用來記錄用戶凍結(jié)金額
- state:用來記錄事務(wù)狀態(tài)
那此時(shí),我們的業(yè)務(wù)開怎么做呢?
- Try業(yè)務(wù):
- 記錄凍結(jié)金額和事務(wù)狀態(tài)到account_freeze表
- 扣減account表可用金額
- Confirm業(yè)務(wù)
- 根據(jù)xid刪除account_freeze表的凍結(jié)記錄
- Cancel業(yè)務(wù)
- 修改account_freeze表,凍結(jié)金額為0,state為2
- 修改account表,恢復(fù)可用金額
- 如何判斷是否空回滾?
- cancel業(yè)務(wù)中,根據(jù)xid查詢account_freeze,如果為null則說明try還沒做,需要空回滾
- 如何避免業(yè)務(wù)懸掛?
- try業(yè)務(wù)中,根據(jù)xid查詢account_freeze ,如果已經(jīng)存在則證明Cancel已經(jīng)執(zhí)行,拒絕執(zhí)行try業(yè)務(wù)
接下來,我們改造account-service,利用TCC實(shí)現(xiàn)余額扣減功能。
2)聲明TCC接口
TCC的Try、Confirm、Cancel方法都需要在接口中基于注解來聲明,
我們?cè)赼ccount-service項(xiàng)目中的cn.itcast.account.service
包中新建一個(gè)接口,聲明TCC三個(gè)接口:
package cn.itcast.account.service;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
@LocalTCC
public interface AccountTCCService {
@TwoPhaseBusinessAction(name = "deduct", commitMethod = "confirm", rollbackMethod = "cancel")
void deduct(@BusinessActionContextParameter(paramName = "userId") String userId,
@BusinessActionContextParameter(paramName = "money")int money);
boolean confirm(BusinessActionContext ctx);
boolean cancel(BusinessActionContext ctx);
}
3)編寫實(shí)現(xiàn)類
在account-service服務(wù)中的cn.itcast.account.service.impl
包下新建一個(gè)類,實(shí)現(xiàn)TCC業(yè)務(wù):
package cn.itcast.account.service.impl;
import cn.itcast.account.entity.AccountFreeze;
import cn.itcast.account.mapper.AccountFreezeMapper;
import cn.itcast.account.mapper.AccountMapper;
import cn.itcast.account.service.AccountTCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
public class AccountTCCServiceImpl implements AccountTCCService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private AccountFreezeMapper freezeMapper;
@Override
@Transactional
public void deduct(String userId, int money) {
// 0.獲取事務(wù)id
String xid = RootContext.getXID();
// 1.扣減可用余額
accountMapper.deduct(userId, money);
// 2.記錄凍結(jié)金額,事務(wù)狀態(tài)
AccountFreeze freeze = new AccountFreeze();
freeze.setUserId(userId);
freeze.setFreezeMoney(money);
freeze.setState(AccountFreeze.State.TRY);
freeze.setXid(xid);
freezeMapper.insert(freeze);
}
@Override
public boolean confirm(BusinessActionContext ctx) {
// 1.獲取事務(wù)id
String xid = ctx.getXid();
// 2.根據(jù)id刪除凍結(jié)記錄
int count = freezeMapper.deleteById(xid);
return count == 1;
}
@Override
public boolean cancel(BusinessActionContext ctx) {
// 0.查詢凍結(jié)記錄
String xid = ctx.getXid();
AccountFreeze freeze = freezeMapper.selectById(xid);
// 1.恢復(fù)可用余額
accountMapper.refund(freeze.getUserId(), freeze.getFreezeMoney());
// 2.將凍結(jié)金額清零,狀態(tài)改為CANCEL
freeze.setFreezeMoney(0);
freeze.setState(AccountFreeze.State.CANCEL);
int count = freezeMapper.updateById(freeze);
return count == 1;
}
}
4.4.SAGA模式
Saga 模式是 Seata 即將開源的長(zhǎng)事務(wù)解決方案,將由螞蟻金服主要貢獻(xiàn)。
其理論基礎(chǔ)是Hector & Kenneth 在1987年發(fā)表的論文Sagas。
Seata官網(wǎng)對(duì)于Saga的指南:https://seata.io/zh-cn/docs/user/saga.html
4.4.1.原理
在 Saga 模式下,分布式事務(wù)內(nèi)有多個(gè)參與者,每一個(gè)參與者都是一個(gè)沖正補(bǔ)償服務(wù),需要用戶根據(jù)業(yè)務(wù)場(chǎng)景實(shí)現(xiàn)其正向操作和逆向回滾操作。
分布式事務(wù)執(zhí)行過程中,依次執(zhí)行各參與者的正向操作,如果所有正向操作均執(zhí)行成功,那么分布式事務(wù)提交。如果任何一個(gè)正向操作執(zhí)行失敗,那么分布式事務(wù)會(huì)去退回去執(zhí)行前面各參與者的逆向回滾操作,回滾已提交的參與者,使分布式事務(wù)回到初始狀態(tài)。
Saga也分為兩個(gè)階段:
- 一階段:直接提交本地事務(wù)
- 二階段:成功則什么都不做;失敗則通過編寫補(bǔ)償業(yè)務(wù)來回滾
4.4.2.優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 事務(wù)參與者可以基于事件驅(qū)動(dòng)實(shí)現(xiàn)異步調(diào)用,吞吐高
- 一階段直接提交事務(wù),無鎖,性能好
- 不用編寫TCC中的三個(gè)階段,實(shí)現(xiàn)簡(jiǎn)單
缺點(diǎn):
- 軟狀態(tài)持續(xù)時(shí)間不確定,時(shí)效性差
- 沒有鎖,沒有事務(wù)隔離,會(huì)有臟寫
4.5.四種模式對(duì)比
我們從以下幾個(gè)方面來對(duì)比四種實(shí)現(xiàn):
- 一致性:能否保證事務(wù)的一致性?強(qiáng)一致還是最終一致?
- 隔離性:事務(wù)之間的隔離性如何?
- 代碼侵入:是否需要對(duì)業(yè)務(wù)代碼改造?
- 性能:有無性能損耗?
- 場(chǎng)景:常見的業(yè)務(wù)場(chǎng)景
如圖:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-lP7ly8dX-1686045878547)(assets/image-20210724185021819.png)]
5.高可用
Seata的TC服務(wù)作為分布式事務(wù)核心,一定要保證集群的高可用性。
5.1.高可用架構(gòu)模型
搭建TC服務(wù)集群非常簡(jiǎn)單,啟動(dòng)多個(gè)TC服務(wù),注冊(cè)到nacos即可。
但集群并不能確保100%安全,萬一集群所在機(jī)房故障怎么辦?所以如果要求較高,一般都會(huì)做異地多機(jī)房容災(zāi)。
比如一個(gè)TC集群在上海,另一個(gè)TC集群在杭州:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-p7t5Pnw2-1686045878548)(assets/image-20210724185240957.png)]
微服務(wù)基于事務(wù)組(tx-service-group)與TC集群的映射關(guān)系,來查找當(dāng)前應(yīng)該使用哪個(gè)TC集群。當(dāng)SH集群故障時(shí),只需要將vgroup-mapping中的映射關(guān)系改成HZ。則所有微服務(wù)就會(huì)切換到HZ的TC集群了。文章來源:http://www.zghlxwxcb.cn/news/detail-474087.html
5.2.實(shí)現(xiàn)高可用
具體實(shí)現(xiàn)請(qǐng)參考《seata的部署和集成》文章來源地址http://www.zghlxwxcb.cn/news/detail-474087.html
到了這里,關(guān)于【高級(jí)篇】分布式事務(wù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!