對(duì)比7種分布式事務(wù)方案,還是偏愛(ài)阿里開(kāi)源的Seata,真香!(原理+實(shí)戰(zhàn))
這篇文章主要介紹一些目前主流的幾種分布式解決方案以及阿里開(kāi)源的一站式分布式解決方案Seata。
文章目錄如下:

什么是分布式事務(wù)?


什么是CAP原則?

一致性(Consistency)
也就是說(shuō),在一致性系統(tǒng)中,一旦客戶端將值寫(xiě)入任何一臺(tái)服務(wù)器并獲得響應(yīng),那么之后client從其他任何服務(wù)器讀取的都是剛寫(xiě)入的數(shù)據(jù) 一致性保證了不管向哪臺(tái)服務(wù)器寫(xiě)入數(shù)據(jù),其他的服務(wù)器能實(shí)時(shí)同步數(shù)據(jù)
可用性(Availability)
分區(qū)容忍性(Partition tolerance)
為什么只能在A和C之間做出取舍?
一致性有幾種分類?
這里的 “犧牲一致性” 并不是完全放棄數(shù)據(jù)的一致性,而是放棄強(qiáng)一致性而換取弱一致性。
強(qiáng)一致性 弱一致性 最終一致性
強(qiáng)一致性
一個(gè)集群需要對(duì)外部提供強(qiáng)一致性,所以只要集群內(nèi)部某一臺(tái)服務(wù)器的數(shù)據(jù)發(fā)生了改變,那么就需要等待集群內(nèi)其他服務(wù)器的數(shù)據(jù)同步完成后,才能正常的對(duì)外提供服務(wù)。 保證了強(qiáng)一致性,務(wù)必會(huì)損耗可用性。
弱一致性
最終一致性
總結(jié)
什么是Base理論?
BA(Basic Available)基本可用
“一定時(shí)間”可以適當(dāng)延長(zhǎng) 當(dāng)舉行大促(比如秒殺)時(shí),響應(yīng)時(shí)間可以適當(dāng)延長(zhǎng) 給部分用戶返回一個(gè)降級(jí)頁(yè)面 給部分用戶直接返回一個(gè)降級(jí)頁(yè)面,從而緩解服務(wù)器壓力。但要注意,返回降級(jí)頁(yè)面仍然是返回明確結(jié)果。
S(Soft State)柔性狀態(tài)
E(Eventual Consisstency)最終一致性
分布式事務(wù)有哪幾種解決方案?
2階段提交(2PC)
準(zhǔn)備階段 提交階段
事務(wù)協(xié)調(diào)者(事務(wù)管理器):事務(wù)的發(fā)起者 事務(wù)參與者(資源管理器):事務(wù)的執(zhí)行者
準(zhǔn)備階段(投票階段)

協(xié)調(diào)者向所有參與者發(fā)送事務(wù)內(nèi)容,詢問(wèn)是否可以提交事務(wù),并等待答復(fù) 各參與者執(zhí)行事務(wù)操作,將 undo 和 redo 信息記入事務(wù)日志中(但不提交事務(wù)) 如參與者執(zhí)行成功,給協(xié)調(diào)者反饋同意,否則反饋中止
提交階段

協(xié)調(diào)者節(jié)點(diǎn)向所有參與者節(jié)點(diǎn)發(fā)出正式提交( commit)的請(qǐng)求。參與者節(jié)點(diǎn)正式完成操作,并釋放在整個(gè)事務(wù)期間內(nèi)占用的資源。 參與者節(jié)點(diǎn)向協(xié)調(diào)者節(jié)點(diǎn)發(fā)送ack完成消息。 協(xié)調(diào)者節(jié)點(diǎn)收到所有參與者節(jié)點(diǎn)反饋的ack完成消息后,完成事務(wù)。

協(xié)調(diào)者節(jié)點(diǎn)向所有參與者節(jié)點(diǎn)發(fā)出回滾操作( rollback)的請(qǐng)求。參與者節(jié)點(diǎn)利用階段1寫(xiě)入的undo信息執(zhí)行回滾,并釋放在整個(gè)事務(wù)期間內(nèi)占用的資源。 參與者節(jié)點(diǎn)向協(xié)調(diào)者節(jié)點(diǎn)發(fā)送ack回滾完成消息。 協(xié)調(diào)者節(jié)點(diǎn)受到所有參與者節(jié)點(diǎn)反饋的ack回滾完成消息后,取消事務(wù)。


裁判:A同學(xué)準(zhǔn)備好了嗎?準(zhǔn)備進(jìn)入第一賽道.... 裁判:B同學(xué)準(zhǔn)備好了嗎?準(zhǔn)備進(jìn)入第一賽道.... 裁判:C同學(xué)準(zhǔn)備好了嗎?準(zhǔn)備進(jìn)入第一賽道.... 如果有任意一個(gè)同學(xué)沒(méi)準(zhǔn)備好,則裁判下達(dá)回滾指令 如果裁判收到了所有同學(xué)的OK回復(fù),則再次下令跑...... 裁判:1,2,3 跑............ A同學(xué)沖刺到終點(diǎn),匯報(bào)給裁判 B,C同學(xué)沖刺失敗,匯報(bào)給裁判
2PC的缺點(diǎn)
性能問(wèn)題:執(zhí)行過(guò)程中,所有參與節(jié)點(diǎn)都是事務(wù)阻塞型的。當(dāng)參與者占有公共資源時(shí),其他第三方節(jié)點(diǎn)訪問(wèn)公共資源不得不處于阻塞狀態(tài)。 可靠性問(wèn)題:參與者發(fā)生故障。協(xié)調(diào)者需要給每個(gè)參與者額外指定超時(shí)機(jī)制,超時(shí)后整個(gè)事務(wù)失敗。協(xié)調(diào)者發(fā)生故障。參與者會(huì)一直阻塞下去。需要額外的備機(jī)進(jìn)行容錯(cuò)。 數(shù)據(jù)一致性問(wèn)題:二階段無(wú)法解決的問(wèn)題:協(xié)調(diào)者在發(fā)出 commit消息之后宕機(jī),而唯一接收到這條消息的參與者同時(shí)也宕機(jī)了。那么即使協(xié)調(diào)者通過(guò)選舉協(xié)議產(chǎn)生了新的協(xié)調(diào)者,這條事務(wù)的狀態(tài)也是不確定的,沒(méi)人知道事務(wù)是否被已經(jīng)提交。實(shí)現(xiàn)復(fù)雜:犧牲了可用性,對(duì)性能影響較大,不適合高并發(fā)高性能場(chǎng)景。
2PC的優(yōu)點(diǎn)
盡量保證了數(shù)據(jù)的強(qiáng)一致,適合對(duì)數(shù)據(jù)強(qiáng)一致要求很高的關(guān)鍵領(lǐng)域。(其實(shí)也不能100%保證強(qiáng)一致)
3階段提交(3PC)
在協(xié)調(diào)者和參與者中都引入超時(shí)機(jī)制 在第一階段和第二階段中插入一個(gè)準(zhǔn)備階段。保證了在最后提交階段之前各參與節(jié)點(diǎn)的狀態(tài)是一致的。
CanCommit、PreCommit、DoCommit三個(gè)階段。處理流程如下:
階段一:CanCommit階段
CanCommit階段其實(shí)和2PC的準(zhǔn)備階段很像。協(xié)調(diào)者向參與者發(fā)送commit請(qǐng)求,參與者如果可以提交就返回Yes響應(yīng),否則返回No響應(yīng)。事務(wù)詢問(wèn):協(xié)調(diào)者向所有參與者發(fā)出包含事務(wù)內(nèi)容的 canCommit請(qǐng)求,詢問(wèn)是否可以提交事務(wù),并等待所有參與者答復(fù)。響應(yīng)反饋:參與者收到 canCommit請(qǐng)求后,如果認(rèn)為可以執(zhí)行事務(wù)操作,則反饋 yes 并進(jìn)入預(yù)備狀態(tài),否則反饋 no。

階段二:PreCommit階段
PreCommit操作。根據(jù)響應(yīng)情況,有以下兩種可能。假如所有參與者均反饋 yes,協(xié)調(diào)者預(yù)執(zhí)行事務(wù)。 發(fā)送預(yù)提交請(qǐng)求 :協(xié)調(diào)者向參與者發(fā)送 PreCommit請(qǐng)求,并進(jìn)入準(zhǔn)備階段事務(wù)預(yù)提交 :參與者接收到 PreCommit請(qǐng)求后,會(huì)執(zhí)行事務(wù)操作,并將undo和redo信息記錄到事務(wù)日志中(但不提交事務(wù))響應(yīng)反饋 :如果參與者成功的執(zhí)行了事務(wù)操作,則返回ACK響應(yīng),同時(shí)開(kāi)始等待最終指令。

假如有任何一個(gè)參與者向協(xié)調(diào)者發(fā)送了No響應(yīng),或者等待超時(shí)之后,協(xié)調(diào)者都沒(méi)有接到參與者的響應(yīng),那么就執(zhí)行事務(wù)的中斷。 發(fā)送中斷請(qǐng)求 :協(xié)調(diào)者向所有參與者發(fā)送 abort請(qǐng)求。中斷事務(wù) :參與者收到來(lái)自協(xié)調(diào)者的 abort請(qǐng)求之后(或超時(shí)之后,仍未收到協(xié)調(diào)者的請(qǐng)求),執(zhí)行事務(wù)的中斷。

階段三:doCommit階段
進(jìn)入階段 3 后,無(wú)論協(xié)調(diào)者出現(xiàn)問(wèn)題,或者協(xié)調(diào)者與參與者網(wǎng)絡(luò)出現(xiàn)問(wèn)題,都會(huì)導(dǎo)致參與者無(wú)法接收到協(xié)調(diào)者發(fā)出的 do Commit 請(qǐng)求或 abort 請(qǐng)求。此時(shí),參與者都會(huì)在等待超時(shí)之后,繼續(xù)執(zhí)行事務(wù)提交。
執(zhí)行提交 發(fā)送提交請(qǐng)求 協(xié)調(diào)接收到參與者發(fā)送的ACK響應(yīng),那么他將從預(yù)提交狀態(tài)進(jìn)入到提交狀態(tài)。并向所有參與者發(fā)送 doCommit請(qǐng)求。事務(wù)提交 參與者接收到 doCommit請(qǐng)求之后,執(zhí)行正式的事務(wù)提交。并在完成事務(wù)提交之后釋放所有事務(wù)資源。響應(yīng)反饋 事務(wù)提交完之后,向協(xié)調(diào)者發(fā)送ack響應(yīng)。 完成事務(wù) 協(xié)調(diào)者接收到所有參與者的ack響應(yīng)之后,完成事務(wù)。

中斷事務(wù):任何一個(gè)參與者反饋 no,或者等待超時(shí)后協(xié)調(diào)者尚無(wú)法收到所有參與者的反饋,即中斷事務(wù) 發(fā)送中斷請(qǐng)求 如果協(xié)調(diào)者處于工作狀態(tài),向所有參與者發(fā)出 abort 請(qǐng)求 事務(wù)回滾 參與者接收到abort請(qǐng)求之后,利用其在階段二記錄的undo信息來(lái)執(zhí)行事務(wù)的回滾操作,并在完成回滾之后釋放所有的事務(wù)資源。 反饋結(jié)果 參與者完成事務(wù)回滾之后,向協(xié)調(diào)者反饋ACK消息 中斷事務(wù) 協(xié)調(diào)者接收到參與者反饋的ACK消息之后,執(zhí)行事務(wù)的中斷。

優(yōu)點(diǎn)
缺點(diǎn)
preCommit 請(qǐng)求后等待 doCommit 指令時(shí),此時(shí)如果協(xié)調(diào)者請(qǐng)求中斷事務(wù),而協(xié)調(diào)者無(wú)法與參與者正常通信,會(huì)導(dǎo)致參與者繼續(xù)提交事務(wù),造成數(shù)據(jù)不一致。TCC(事務(wù)補(bǔ)償)
第一階段:Try(嘗試),主要是對(duì)業(yè)務(wù)系統(tǒng)做檢測(cè)及資源預(yù)留 (加鎖,鎖住資源) 第二階段:本階段根據(jù)第一階段的結(jié)果,決定是執(zhí)行confirm還是cancel Confirm(確認(rèn)):執(zhí)行真正的業(yè)務(wù)(執(zhí)行業(yè)務(wù),釋放鎖) Cancle(取消):是預(yù)留資源的取消(出問(wèn)題,釋放鎖)

①Try 階段
完成所有業(yè)務(wù)檢查( 一致性 ) 。 預(yù)留必須業(yè)務(wù)資源( 準(zhǔn)隔離性 ) 。 Try 嘗試執(zhí)行業(yè)務(wù)。

②Confirm / Cancel 階段


最終一致性保證
TCC 事務(wù)機(jī)制以初步操作(Try)為中心的,確認(rèn)操作(Confirm)和取消操作(Cancel)都是圍繞初步操作(Try)而展開(kāi)。因此,Try 階段中的操作,其保障性是最好的,即使失敗,仍然有取消操作(Cancel)可以將其執(zhí)行結(jié)果撤銷。 Try階段執(zhí)行成功并開(kāi)始執(zhí)行 Confirm階段時(shí),默認(rèn)Confirm階段是不會(huì)出錯(cuò)的。也就是說(shuō)只要Try成功,Confirm一定成功(TCC設(shè)計(jì)之初的定義) 。Confirm與Cancel如果失敗,由TCC框架進(jìn)行==重試==補(bǔ)償 存在極低概率在CC環(huán)節(jié)徹底失敗,則需要定時(shí)任務(wù)或人工介入
方案總結(jié)
性能提升:具體業(yè)務(wù)來(lái)實(shí)現(xiàn)控制資源鎖的粒度變小,不會(huì)鎖定整個(gè)資源。 數(shù)據(jù)最終一致性:基于 Confirm 和 Cancel 的冪等性,保證事務(wù)最終完成確認(rèn)或者取消,保證數(shù)據(jù)的一致性。 可靠性:解決了 XA 協(xié)議的協(xié)調(diào)者單點(diǎn)故障問(wèn)題,由主業(yè)務(wù)方發(fā)起并控制整個(gè)業(yè)務(wù)活動(dòng),業(yè)務(wù)活動(dòng)管理器也變成多點(diǎn),引入集群。
TCC 的 Try、Confirm 和 Cancel 操作功能要按具體業(yè)務(wù)來(lái)實(shí)現(xiàn),業(yè)務(wù)耦合度較高,提高了開(kāi)發(fā)成本。
本地消息表
事務(wù)主動(dòng)方 事務(wù)被動(dòng)方
業(yè)務(wù)處理成功、事務(wù)消息發(fā)送失敗 業(yè)務(wù)處理失敗、事務(wù)消息發(fā)送成功

①:事務(wù)主動(dòng)方在同一個(gè)本地事務(wù)中處理業(yè)務(wù)和寫(xiě)消息表操作 ②:事務(wù)主動(dòng)方通過(guò)消息中間件,通知事務(wù)被動(dòng)方處理事務(wù)通知事務(wù)待消息。消息中間件可以基于 Kafka、RocketMQ 消息隊(duì)列,事務(wù)主動(dòng)方主動(dòng)寫(xiě)消息到消息隊(duì)列,事務(wù)消費(fèi)方消費(fèi)并處理消息隊(duì)列中的消息。 ③:事務(wù)被動(dòng)方通過(guò)消息中間件,通知事務(wù)主動(dòng)方事務(wù)已處理的消息。 ④:事務(wù)主動(dòng)方接收中間件的消息,更新消息表的狀態(tài)為已處理。
當(dāng)①處理出錯(cuò),由于還在事務(wù)主動(dòng)方的本地事務(wù)中,直接回滾即可 當(dāng)②、③處理出錯(cuò),由于事務(wù)主動(dòng)方本地保存了消息,只需要輪詢消息重新通過(guò)消息中間件發(fā)送,事務(wù)被動(dòng)方重新讀取消息處理業(yè)務(wù)即可。 如果是業(yè)務(wù)上處理失敗,事務(wù)被動(dòng)方可以發(fā)消息給事務(wù)主動(dòng)方回滾事務(wù) 如果事務(wù)被動(dòng)方已經(jīng)消費(fèi)了消息,事務(wù)主動(dòng)方需要回滾事務(wù)的話,需要發(fā)消息通知事務(wù)主動(dòng)方進(jìn)行回滾事務(wù)。
優(yōu)點(diǎn)
從應(yīng)用設(shè)計(jì)開(kāi)發(fā)的角度實(shí)現(xiàn)了消息數(shù)據(jù)的可靠性,消息數(shù)據(jù)的可靠性不依賴于消息中間件,弱化了對(duì) MQ 中間件特性的依賴。 方案輕量,容易實(shí)現(xiàn)。
缺點(diǎn)
與具體的業(yè)務(wù)場(chǎng)景綁定,耦合性強(qiáng),不可公用。 消息數(shù)據(jù)與業(yè)務(wù)數(shù)據(jù)同庫(kù),占用業(yè)務(wù)系統(tǒng)資源。 業(yè)務(wù)系統(tǒng)在使用關(guān)系型數(shù)據(jù)庫(kù)的情況下,消息服務(wù)性能會(huì)受到關(guān)系型數(shù)據(jù)庫(kù)并發(fā)性能的局限。
MQ事務(wù)方案(可靠消息事務(wù))


步驟①:發(fā)送方向 MQ 服務(wù)端(MQ Server)發(fā)送 half 消息。 步驟②:MQ Server 將消息持久化成功之后,向發(fā)送方 ack 確認(rèn)消息已經(jīng)發(fā)送成功。 步驟③:發(fā)送方開(kāi)始執(zhí)行本地事務(wù)邏輯。 步驟④:發(fā)送方根據(jù)本地事務(wù)執(zhí)行結(jié)果向 MQ Server 提交二次確認(rèn)(commit 或是 rollback)。 步驟⑤:MQ Server 收到 commit 狀態(tài)則將半消息標(biāo)記為可投遞,訂閱方最終將收到該消息;MQ Server 收到 rollback 狀態(tài)則刪除半消息,訂閱方將不會(huì)接受該消息。

步驟⑤:MQ Server 對(duì)該消息發(fā)起消息回查。 步驟⑥:發(fā)送方收到消息回查后,需要檢查對(duì)應(yīng)消息的本地事務(wù)執(zhí)行的最終結(jié)果。 步驟⑦:發(fā)送方根據(jù)檢查得到的本地事務(wù)的最終狀態(tài)再次提交二次確認(rèn)。 步驟⑧:MQ Server基于 commit/rollback 對(duì)消息進(jìn)行投遞或者刪除。
優(yōu)點(diǎn)
消息數(shù)據(jù)獨(dú)立存儲(chǔ) ,降低業(yè)務(wù)系統(tǒng)與消息系統(tǒng)之間的耦合。 吞吐量大于使用本地消息表方案。
缺點(diǎn)
一次消息發(fā)送需要兩次網(wǎng)絡(luò)請(qǐng)求(half 消息 + commit/rollback 消息) 。 業(yè)務(wù)處理服務(wù)需要實(shí)現(xiàn)消息狀態(tài)回查接口。
最大努力通知

Saga 事務(wù)
每個(gè) Saga 事務(wù)由一系列冪等的有序子事務(wù)(sub-transaction) Ti 組成。 每個(gè) Ti 都有對(duì)應(yīng)的冪等補(bǔ)償動(dòng)作 Ci,補(bǔ)償動(dòng)作用于撤銷 Ti 造成的結(jié)果。


命令協(xié)調(diào)(Order Orchestrator) 事件編排(Event Choreographyo)
命令協(xié)調(diào)

事務(wù)發(fā)起方的主業(yè)務(wù)邏輯請(qǐng)求 OSO 服務(wù)開(kāi)啟訂單事務(wù) OSO 向庫(kù)存服務(wù)請(qǐng)求扣減庫(kù)存,庫(kù)存服務(wù)回復(fù)處理結(jié)果。 OSO 向訂單服務(wù)請(qǐng)求創(chuàng)建訂單,訂單服務(wù)回復(fù)創(chuàng)建結(jié)果。 OSO 向支付服務(wù)請(qǐng)求支付,支付服務(wù)回復(fù)處理結(jié)果。 主業(yè)務(wù)邏輯接收并處理 OSO 事務(wù)處理結(jié)果回復(fù)。
事件編排

事務(wù)發(fā)起方的主業(yè)務(wù)邏輯發(fā)布開(kāi)始訂單事件。 庫(kù)存服務(wù)監(jiān)聽(tīng)開(kāi)始訂單事件,扣減庫(kù)存,并發(fā)布庫(kù)存已扣減事件。 訂單服務(wù)監(jiān)聽(tīng)?zhēng)齑嬉芽蹨p事件,創(chuàng)建訂單,并發(fā)布訂單已創(chuàng)建事件。 支付服務(wù)監(jiān)聽(tīng)訂單已創(chuàng)建事件,進(jìn)行支付,并發(fā)布訂單已支付事件。 主業(yè)務(wù)邏輯監(jiān)聽(tīng)訂單已支付事件并處理。
優(yōu)點(diǎn)
服務(wù)之間關(guān)系簡(jiǎn)單,避免服務(wù)之間的循環(huán)依賴關(guān)系,因?yàn)?Saga 協(xié)調(diào)器會(huì)調(diào)用 Saga 參與者,但參與者不會(huì)調(diào)用協(xié)調(diào)器。 程序開(kāi)發(fā)簡(jiǎn)單,只需要執(zhí)行命令/回復(fù)(其實(shí)回復(fù)消息也是一種事件消息),降低參與者的復(fù)雜性。 易維護(hù)擴(kuò)展,在添加新步驟時(shí),事務(wù)復(fù)雜性保持線性,回滾更容易管理,更容易實(shí)施和測(cè)試。
避免中央?yún)f(xié)調(diào)器單點(diǎn)故障風(fēng)險(xiǎn)。 當(dāng)涉及的步驟較少服務(wù)開(kāi)發(fā)簡(jiǎn)單,容易實(shí)現(xiàn)。
缺點(diǎn)
中央?yún)f(xié)調(diào)器容易處理邏輯容易過(guò)于復(fù)雜,導(dǎo)致難以維護(hù)。 存在協(xié)調(diào)器單點(diǎn)故障風(fēng)險(xiǎn)。
服務(wù)之間存在循環(huán)依賴的風(fēng)險(xiǎn)。 當(dāng)涉及的步驟較多,服務(wù)間關(guān)系混亂,難以追蹤調(diào)測(cè)。
由于 Saga 模型中沒(méi)有 Prepare 階段,因此事務(wù)間不能保證隔離性。
總結(jié)
2PC/3PC:依賴于數(shù)據(jù)庫(kù),能夠很好的提供強(qiáng)一致性和強(qiáng)事務(wù)性,但相對(duì)來(lái)說(shuō)延遲比較高,比較適合傳統(tǒng)的單體應(yīng)用,在同一個(gè)方法中存在跨庫(kù)操作的情況,不適合高并發(fā)和高性能要求的場(chǎng)景。 TCC:適用于執(zhí)行時(shí)間確定且較短,實(shí)時(shí)性要求高,對(duì)數(shù)據(jù)一致性要求高,比如互聯(lián)網(wǎng)金融企業(yè)最核心的三個(gè)服務(wù):交易、支付、賬務(wù)。 本地消息表/MQ 事務(wù):都適用于事務(wù)中參與方支持操作冪等,對(duì)一致性要求不高,業(yè)務(wù)上能容忍數(shù)據(jù)不一致到一個(gè)人工檢查周期,事務(wù)涉及的參與方、參與環(huán)節(jié)較少,業(yè)務(wù)上有對(duì)賬/校驗(yàn)系統(tǒng)兜底。 Saga 事務(wù):由于 Saga 事務(wù)不能保證隔離性,需要在業(yè)務(wù)層控制并發(fā),適合于業(yè)務(wù)場(chǎng)景事務(wù)并發(fā)操作同一資源較少的情況。Saga 相比缺少預(yù)提交動(dòng)作,導(dǎo)致補(bǔ)償動(dòng)作的實(shí)現(xiàn)比較麻煩,例如業(yè)務(wù)是發(fā)送短信,補(bǔ)償動(dòng)作則得再發(fā)送一次短信說(shuō)明撤銷,用戶體驗(yàn)比較差。Saga 事務(wù)較適用于補(bǔ)償動(dòng)作容易處理的場(chǎng)景。
什么是Seata?
對(duì)業(yè)務(wù)無(wú)侵入:即減少技術(shù)架構(gòu)上的微服務(wù)化所帶來(lái)的分布式事務(wù)問(wèn)題對(duì)業(yè)務(wù)的侵入 高性能:減少分布式事務(wù)解決方案所帶來(lái)的性能消耗
TC(Transaction Coordinator):事務(wù)協(xié)調(diào)者。管理全局的分支事務(wù)的狀態(tài),用于全局性事務(wù)的提交和回滾。 TM(Transaction Manager):事務(wù)管理者。用于開(kāi)啟、提交或回滾事務(wù)。 RM(Resource Manager):資源管理器。用于分支事務(wù)上的資源管理,向 TC 注冊(cè)分支事務(wù),上報(bào)分支事務(wù)的狀態(tài),接收 TC 的命令來(lái)提交或者回滾分支事務(wù)。
AT模式
一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交,釋放本地鎖和連接資源。 二階段: 提交異步化,非??焖俚赝瓿?/span> 回滾通過(guò)一階段的回滾日志進(jìn)行反向補(bǔ)償。

TM 向 TC 申請(qǐng)開(kāi)啟一個(gè)全局事務(wù),全局事務(wù)創(chuàng)建成功并生成一個(gè)全局唯一的 XID; XID 在微服務(wù)調(diào)用鏈路的上下文中傳播; RM 向 TC 注冊(cè)分支事務(wù),將其納入 XID 對(duì)應(yīng)全局事務(wù)的管轄; TM 向 TC 發(fā)起針對(duì) XID 的全局提交或回滾決議; TC 調(diào)度 XID 下管轄的全部分支事務(wù)完成提交或回滾請(qǐng)求。
搭建Seata TC協(xié)調(diào)者
陳某下載的版本是 1.3.0,各位最好和我版本一致,這樣不會(huì)出現(xiàn)莫名的BUG。
創(chuàng)建TC所需要的表
script\server\db這個(gè)目錄,將會(huì)看到以下SQL文件:

修改TC的注冊(cè)中心
seata-server-1.3.0\seata\conf這個(gè)目錄,其中有一個(gè)registry.conf文件,其中配置了TC的注冊(cè)中心和配置中心。file形式,實(shí)際使用中肯定不能使用,需要改成Nacos形式,改動(dòng)的地方如下圖:
type:改成nacos,表示使用nacos作為注冊(cè)中心 application:服務(wù)的名稱 serverAddr:nacos的地址 group:分組 namespace:命名空間 username:用戶名 password:密碼
最后這份文件都會(huì)放在項(xiàng)目源碼的根目錄下,源碼下載方式見(jiàn)文末
修改TC的配置中心
file形式,當(dāng)然要是用nacos作為配置中心了。registry.conf文件,需要改動(dòng)的地方如下圖:
type:改成nacos,表示使用nacos作為配置中心 serverAddr:nacos的地址 group:分組 namespace:命名空間 username:用戶名 password:密碼
seata-1.3.0\script\config-center中有一個(gè)config.txt文件,其中就是TC所需要的全部配置。seata-1.3.0\script\config-center\nacos中有一個(gè)腳本nacos-config.sh則是將config.txt中的全部配置自動(dòng)推送到nacos中,運(yùn)行下面命令(windows可以使用git bash運(yùn)行):#?-h?主機(jī),你可以使用localhost,-p?端口號(hào)?你可以使用8848,-t?命名空間ID,-u?用戶名,-p?密碼
$?sh?nacos-config.sh?-h?127.0.0.1?-p?8080?-g?SEATA_GROUP?-t?7a7581ef-433d-46f3-93f9-5fdc18239c65?-u?nacos?-w?nacos

修改TC的數(shù)據(jù)庫(kù)連接信息
## 采用db的存儲(chǔ)形式
store.mode=db
## druid數(shù)據(jù)源
store.db.datasource=druid
## mysql數(shù)據(jù)庫(kù)
store.db.dbType=mysql
## mysql驅(qū)動(dòng)
store.db.driverClassName=com.mysql.jdbc.Driver
## TC的數(shù)據(jù)庫(kù)url
store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true
## 用戶名
store.db.user=root
## 密碼
store.db.password=Nov2014
store.mode,如下圖:
store.mode=redis
store.redis.host=127.0.0.1
store.redis.port=6379
store.redis.password=123456
啟動(dòng)TC
seata-server-1.3.0\seata\bin目錄下直接點(diǎn)擊seata-server.bat(windows)運(yùn)行。
Seata客戶端搭建(RM)
倉(cāng)儲(chǔ)服務(wù):對(duì)給定的商品扣除倉(cāng)儲(chǔ)數(shù)量。 訂單服務(wù):根據(jù)采購(gòu)需求創(chuàng)建訂單。 帳戶服務(wù):從用戶帳戶中扣除余額。
倉(cāng)儲(chǔ)服務(wù)搭建
添加依賴
seata-storage9020項(xiàng)目,新增依賴如下:
springCloud Alibaba依賴版本是2.2.1.RELEASE,其中自帶的seata版本是1.1.0,但是我們Seata服務(wù)端使用的版本是1.3.0,因此需要排除原有的依賴,重新添加1.3.0的依賴。注意:seata客戶端的依賴版本必須要和服務(wù)端一致。
創(chuàng)建數(shù)據(jù)庫(kù)
seata-storage,其中新建兩個(gè)表:storage:庫(kù)存的業(yè)務(wù)表,SQL如下:
CREATE?TABLE?`storage`??(
??`id`?bigint(11)?NOT?NULL?AUTO_INCREMENT,
??`name`?varchar(100)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?NULL?DEFAULT?NULL,
??`num`?bigint(11)?NULL?DEFAULT?NULL?COMMENT?'數(shù)量',
??`create_time`?datetime(0)?NULL?DEFAULT?NULL,
??`price`?bigint(10)?NULL?DEFAULT?NULL?COMMENT?'單價(jià),單位分',
??PRIMARY?KEY?(`id`)?USING?BTREE
)?ENGINE?=?InnoDB?AUTO_INCREMENT?=?2?CHARACTER?SET?=?utf8mb4?COLLATE?=?utf8mb4_general_ci?ROW_FORMAT?=?Compact;
INSERT?INTO?`storage`?VALUES?(1,?'碼猿技術(shù)專欄',?1000,?'2021-10-15?22:32:40',?100);
undo_log:回滾日志表,這是Seata要求必須有的,每個(gè)業(yè)務(wù)庫(kù)都應(yīng)該創(chuàng)建一個(gè),SQL如下:
CREATE?TABLE?`undo_log`??(
??`branch_id`?bigint(20)?NOT?NULL?COMMENT?'branch?transaction?id',
??`xid`?varchar(100)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL?COMMENT?'global?transaction?id',
??`context`?varchar(128)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NOT?NULL?COMMENT?'undo_log?context,such?as?serialization',
??`rollback_info`?longblob?NOT?NULL?COMMENT?'rollback?info',
??`log_status`?int(11)?NOT?NULL?COMMENT?'0:normal?status,1:defense?status',
??`log_created`?datetime(6)?NOT?NULL?COMMENT?'create?datetime',
??`log_modified`?datetime(6)?NOT?NULL?COMMENT?'modify?datetime',
??UNIQUE?INDEX?`ux_undo_log`(`xid`,?`branch_id`)?USING?BTREE
)?ENGINE?=?InnoDB?CHARACTER?SET?=?utf8?COLLATE?=?utf8_general_ci?COMMENT?=?'AT?transaction?mode?undo?table'?ROW_FORMAT?=?Compact;
配置seata相關(guān)配置
spring:
??application:
????##?指定服務(wù)名稱,在nacos中的名字
????name:?seata-storage
##?客戶端seata的相關(guān)配置
seata:
??##?是否開(kāi)啟seata,默認(rèn)true
??enabled:?true
??application-id:?${spring.application.name}
??##?seata事務(wù)組的名稱,一定要和config.tx(nacos)中配置的相同
??tx-service-group:?${spring.application.name}-tx-group
??##?配置中心的配置
??config:
????##?使用類型nacos
????type:?nacos
????##?nacos作為配置中心的相關(guān)配置,需要和server在同一個(gè)注冊(cè)中心下
????nacos:
??????##?命名空間,需要server端(registry和config)、nacos配置client端(registry和config)保持一致
??????namespace:?7a7581ef-433d-46f3-93f9-5fdc18239c65
??????##?地址
??????server-addr:?localhost:8848
??????##?組,?需要server端(registry和config)、nacos配置client端(registry和config)保持一致
??????group:?SEATA_GROUP
??????##?用戶名和密碼
??????username:?nacos
??????password:?nacos
??registry:
????type:?nacos
????nacos:
??????##?這里的名字一定要和seata服務(wù)端中的名稱相同,默認(rèn)是seata-server
??????application:?seata-server
??????##?需要server端(registry和config)、nacos配置client端(registry和config)保持一致
??????group:?SEATA_GROUP
??????namespace:?7a7581ef-433d-46f3-93f9-5fdc18239c65
??????username:?nacos
??????password:?nacos
??????server-addr:?localhost:8848
客戶端seata中的nacos相關(guān)配置要和服務(wù)端相同,比如地址、命名空間.......... tx-service-group:這個(gè)屬性一定要注意,這個(gè)一定要和服務(wù)端的配置一致,否則不生效;比如上述配置中的,就要在nacos中新增一個(gè)配置 service.vgroupMapping.seata-storage-tx-group=default,如下圖:

注意: seata-storage-tx-group僅僅是后綴,要記得添加配置的時(shí)候要加上前綴service.vgroupMapping.
扣減庫(kù)存的接口

@Transactional開(kāi)啟了本地事務(wù),并沒(méi)有涉及到分布式事務(wù)。賬戶服務(wù)搭建
添加依賴
seata-account9021服務(wù),這里的依賴和倉(cāng)儲(chǔ)服務(wù)的依賴相同,直接復(fù)制創(chuàng)建數(shù)據(jù)庫(kù)
seata-account數(shù)據(jù)庫(kù),其中新建了兩個(gè)表:account:賬戶業(yè)務(wù)表,SQL如下:
CREATE?TABLE?`account`??(
??`id`?bigint(11)?NOT?NULL,
??`user_id`?varchar(32)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?NULL?DEFAULT?NULL?COMMENT?'用戶userId',
??`money`?bigint(11)?NULL?DEFAULT?NULL?COMMENT?'余額,單位分',
??`create_time`?datetime(0)?NULL?DEFAULT?NULL,
??PRIMARY?KEY?(`id`)?USING?BTREE
)?ENGINE?=?InnoDB?CHARACTER?SET?=?utf8mb4?COLLATE?=?utf8mb4_general_ci?ROW_FORMAT?=?Compact;
INSERT?INTO?`account`?VALUES?(1,?'abc123',?1000,?'2021-10-19?17:49:53');
undo_log:回滾日志表,同倉(cāng)儲(chǔ)服務(wù)
配置seata相關(guān)配置
service.vgroupMapping.seata-account-tx-group=default,如下圖:
扣減余額的接口

@Transactional開(kāi)啟了本地事務(wù),是不是很爽............訂單服務(wù)搭建(TM)
添加依賴
seata-order9022服務(wù),這里需要添加的依賴如下:Nacos服務(wù)發(fā)現(xiàn)的依賴 seata的依賴 openFeign的依賴,由于要調(diào)用賬戶、倉(cāng)儲(chǔ)的微服務(wù),因此需要額外添加一個(gè)openFeign的依賴
創(chuàng)建數(shù)據(jù)庫(kù)
seata_order數(shù)據(jù)庫(kù),其中新建兩個(gè)表,如下:t_order:訂單的業(yè)務(wù)表
CREATE?TABLE?`t_order`??(
??`id`?bigint(11)?NOT?NULL?AUTO_INCREMENT,
??`product_id`?bigint(11)?NULL?DEFAULT?NULL?COMMENT?'商品Id',
??`num`?bigint(11)?NULL?DEFAULT?NULL?COMMENT?'數(shù)量',
??`user_id`?varchar(32)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?NULL?DEFAULT?NULL?COMMENT?'用戶唯一Id',
??`create_time`?datetime(0)?NULL?DEFAULT?NULL,
??`status`?int(1)?NULL?DEFAULT?NULL?COMMENT?'訂單狀態(tài)?1?未付款?2?已付款?3?已完成',
??PRIMARY?KEY?(`id`)?USING?BTREE
)?ENGINE?=?InnoDB?AUTO_INCREMENT?=?7?CHARACTER?SET?=?utf8mb4?COLLATE?=?utf8mb4_general_ci?ROW_FORMAT?=?Compact;
undo_log:回滾日志表,同倉(cāng)儲(chǔ)服務(wù)
配置和seata相關(guān)配置
service.vgroupMapping.seata-order-tx-group=default,如下圖:
扣減庫(kù)存的接口

扣減余額的接口

創(chuàng)建訂單的接口

@GlobalTransactional而不是@Transactional。@GlobalTransactional是Seata提供的,用于開(kāi)啟才能全局事務(wù),只在TM中標(biāo)注即可生效。測(cè)試
seata-account9021、seata-storage9020、seata-order9022,如下圖:




總結(jié)
seata客戶端的版本需要和服務(wù)端保持一致 每個(gè)服務(wù)的數(shù)據(jù)庫(kù)都要?jiǎng)?chuàng)建一個(gè) undo_log回滾日志表客戶端指定的事務(wù)分組名稱要和Nacos相同,比如 service.vgroupMapping.seata-account-tx-group=default前綴: service.vgroupMapping.后綴: {自定義}
項(xiàng)目源碼已經(jīng)上傳,關(guān)注公眾號(hào) 碼猿技術(shù)專欄回復(fù)關(guān)鍵詞9528獲??!
AT模式原理分析
global_table:全局事務(wù)表,每當(dāng)有一個(gè)全局事務(wù)發(fā)起后,就會(huì)在該表中記錄全局事務(wù)的IDbranch_table:分支事務(wù)表,記錄每一個(gè)分支事務(wù)的ID,分支事務(wù)操作的哪個(gè)數(shù)據(jù)庫(kù)等信息lock_table:全局鎖
一階段步驟
TM:seata-order.create()方法執(zhí)行時(shí),由于該方法具有@GlobalTranscational標(biāo)志,該TM會(huì)向TC發(fā)起全局事務(wù),生成XID(全局鎖)RM:StorageService.deduct():寫(xiě)表,UNDO_LOG記錄回滾日志(Branch ID),通知TC操作結(jié)果RM:AccountService.deduct():寫(xiě)表,UNDO_LOG記錄回滾日志(Branch ID),通知TC操作結(jié)果RM:OrderService.create():寫(xiě)表,UNDO_LOG記錄回滾日志(Branch ID),通知TC操作結(jié)果

二階段步驟


AT 模式的一階段、二階段提交和回滾均由 Seata 框架自動(dòng)生成,用戶只需編寫(xiě)業(yè)務(wù) SQL,便能輕松接入分布式事務(wù),AT 模式是一種對(duì)業(yè)務(wù)無(wú)任何侵入的分布式事務(wù)解決方案。
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
評(píng)論
圖片
表情
