為什么不建議你用分布式事務(wù)?
原創(chuàng)【51CTO.com原創(chuàng)稿件】伴隨著業(yè)務(wù)的快速的發(fā)展、越來越高的業(yè)務(wù)復(fù)雜度,幾乎每個(gè)公司的系統(tǒng)都會(huì)從單體走向分布式,特別是轉(zhuǎn)向微服務(wù)架構(gòu)。
圖片來自 包圖網(wǎng)
隨之而來就必然遇到分布式事務(wù)這個(gè)難題。而我的這篇文章總結(jié)了分布式事務(wù)的解決方案,希望給大家?guī)韼椭?/p>
分布式事務(wù)基礎(chǔ)
①到底什么是事務(wù)呢?
什么是事務(wù)?舉個(gè)生活中的例子:你去小賣鋪買東西,“一手交錢,一手交貨”就是一個(gè)事務(wù)的例子,交錢和交貨必須全部成功,事務(wù)才算成功,任一個(gè)活動(dòng)失敗,事務(wù)將撤銷所有已成功的活動(dòng)。
明白上述例子,再來看事務(wù)的定義:事務(wù)可以看做是一次大的活動(dòng),它由不同的小活動(dòng)組成,這些活動(dòng)要么全部成功,要么全部失敗。
②先來回顧本地事務(wù)
在計(jì)算機(jī)系統(tǒng)中,更多的是通過關(guān)系型數(shù)據(jù)庫(kù)來控制事務(wù),這是利用數(shù)據(jù)庫(kù)本身的事務(wù)特性來實(shí)現(xiàn)的,因此叫數(shù)據(jù)庫(kù)事務(wù)。
由于應(yīng)用主要靠關(guān)系數(shù)據(jù)庫(kù)來控制事務(wù),而數(shù)據(jù)庫(kù)通常和應(yīng)用在同一個(gè)服務(wù)器,所以基于關(guān)系型數(shù)據(jù)庫(kù)的事務(wù)又被稱為本地事務(wù)。
回顧一下數(shù)據(jù)庫(kù)事務(wù)的四大特性 ACID:
- A(Atomic):原子性,構(gòu)成事務(wù)的所有操作,要么都執(zhí)行完成,要么全部不執(zhí)行,不可能出現(xiàn)部分成功部分失敗的情況。
- C(Consistency):一致性,在事務(wù)執(zhí)行前后,數(shù)據(jù)庫(kù)的一致性約束沒有被破壞。
比如:張三向李四轉(zhuǎn) 100 元,轉(zhuǎn)賬前和轉(zhuǎn)賬后的數(shù)據(jù)是正確狀態(tài)這叫一致性,如果出現(xiàn)張三轉(zhuǎn)出 100 元,李四賬戶沒有增加 100 元這就出現(xiàn)了數(shù)據(jù)錯(cuò)誤,就沒有達(dá)到一致性。
- I(Isolation):隔離性,數(shù)據(jù)庫(kù)中的事務(wù)一般都是并發(fā)的,隔離性是指并發(fā)的兩個(gè)事務(wù)的執(zhí)行互不干擾,一個(gè)事務(wù)不能看到其他事務(wù)運(yùn)行過程的中間狀態(tài)。通過配置事務(wù)隔離級(jí)別可以避臟讀、重復(fù)讀等問題。
- D(Durability):持久性,事務(wù)完成之后,該事務(wù)對(duì)數(shù)據(jù)的更改會(huì)被持久化到數(shù)據(jù)庫(kù),且不會(huì)被回滾。
數(shù)據(jù)庫(kù)事務(wù)在實(shí)現(xiàn)時(shí)會(huì)將一次事務(wù)涉及的所有操作全部納入到一個(gè)不可分割的執(zhí)行單元,該執(zhí)行單元中的所有操作要么都成功,要么都失敗,只要其中任一操作執(zhí)行失敗,都將導(dǎo)致整個(gè)事務(wù)的回滾。
③分布式事務(wù)
銀行跨行轉(zhuǎn)賬業(yè)務(wù)是一個(gè)典型分布式事務(wù)場(chǎng)景,假設(shè) A 需要跨行轉(zhuǎn)賬給 B,那么就涉及兩個(gè)銀行的數(shù)據(jù),無法通過一個(gè)數(shù)據(jù)庫(kù)的本地事務(wù)保證轉(zhuǎn)賬的 ACID,只能夠通過分布式事務(wù)來解決。
分布式事務(wù)就是指事務(wù)的發(fā)起者、資源及資源管理器和事務(wù)協(xié)調(diào)者分別位于分布式系統(tǒng)的不同節(jié)點(diǎn)之上。
在上述轉(zhuǎn)賬的業(yè)務(wù)中,用戶 A-100 操作和用戶 B+100 操作不是位于同一個(gè)節(jié)點(diǎn)上。
本質(zhì)上來說,分布式事務(wù)就是為了保證在分布式場(chǎng)景下,數(shù)據(jù)操作的正確執(zhí)行。
分布式事務(wù)在分布式環(huán)境下,為了滿足可用性、性能與降級(jí)服務(wù)的需要,降低一致性與隔離性的要求,一方面遵循 BASE 理論(BASE 相關(guān)理論,涉及內(nèi)容非常多,感興趣的程序員們,可以參考 BASE 理論)。
BASE 理論:
- 基本業(yè)務(wù)可用性(Basic Availability)
- 柔性狀態(tài)(Soft state)
- 最終一致性(Eventual consistency)
同樣的,分布式事務(wù)也部分遵循 ACID 規(guī)范:
- 原子性:嚴(yán)格遵循
- 一致性:事務(wù)完成后的一致性嚴(yán)格遵循;事務(wù)中的一致性可適當(dāng)放寬
- 隔離性:并行事務(wù)間不可影響;事務(wù)中間結(jié)果可見性允許安全放寬
- 持久性:嚴(yán)格遵循
④分布式事務(wù)場(chǎng)景
典型的場(chǎng)景就是微服務(wù)架構(gòu):微服務(wù)之間通過遠(yuǎn)程調(diào)用完成事務(wù)操作。
比如:訂單微服務(wù)和庫(kù)存微服務(wù),下單的同時(shí)訂單微服務(wù)請(qǐng)求庫(kù)存微服務(wù)減庫(kù)存。簡(jiǎn)言之:跨 JVM 進(jìn)程產(chǎn)生分布式事務(wù)。
單體系統(tǒng)訪問多個(gè)數(shù)據(jù)庫(kù)實(shí)例:當(dāng)單體系統(tǒng)需要訪問多個(gè)數(shù)據(jù)庫(kù)(實(shí)例)時(shí)就會(huì)產(chǎn)生分布式事務(wù)。
比如:用戶信息和訂單信息分別在兩個(gè) MySQL 實(shí)例存儲(chǔ),用戶管理系統(tǒng)刪除用戶信息,需要分別刪除用戶信息及用戶的訂單信息,由于數(shù)據(jù)分布在不同的數(shù)據(jù)實(shí)例,需要通過不同的數(shù)據(jù)庫(kù)鏈接去操作數(shù)據(jù),此時(shí)產(chǎn)生分布式事務(wù)。
簡(jiǎn)言之:跨數(shù)據(jù)庫(kù)實(shí)例產(chǎn)生分布式事務(wù)。
多服務(wù)訪問同一個(gè)數(shù)據(jù)庫(kù)實(shí)例:比如:訂單微服務(wù)和庫(kù)存微服務(wù)即使訪問同一個(gè)數(shù)據(jù)庫(kù)也會(huì)產(chǎn)生分布式事務(wù),原因就是跨 JVM 進(jìn)程,兩個(gè)微服務(wù)持有了不同的數(shù)據(jù)庫(kù)鏈接進(jìn)行數(shù)據(jù)庫(kù)操作,此時(shí)產(chǎn)生分布式事務(wù)。
分布式事務(wù)的解決方案
①2PC(兩階段提交)/XA
2PC(Two-phase commit protocol),中文叫二階段提交。
二階段提交是一種強(qiáng)一致性設(shè)計(jì),2PC 引入一個(gè)事務(wù)協(xié)調(diào)者的角色來協(xié)調(diào)管理各參與者(也可稱之為各本地資源)的提交和回滾,二階段分別指的是準(zhǔn)備(投票)和提交兩個(gè)階段。
XA 是由 X/Open 組織提出的分布式事務(wù)的規(guī)范,XA 規(guī)范主要定義了(全局)事務(wù)管理器(TM)和(局部)資源管理器(RM)之間的接口。本地的數(shù)據(jù)庫(kù)如 MySQL 在 XA 中扮演的是 RM 角色。
XA 一共分為兩階段:
- 第一階段(prepare):即所有的參與者 RM 準(zhǔn)備執(zhí)行事務(wù)并鎖住需要的資源。參與者 ready 時(shí),向 TM 報(bào)告已準(zhǔn)備就緒。
- 第二階段 (commit/rollback):當(dāng)事務(wù)管理者(TM)確認(rèn)所有參與者(RM)都 ready 后,向所有參與者發(fā)送 commit 命令。
目前主流的數(shù)據(jù)庫(kù)基本都支持 XA 事務(wù),包括 MySQL、Oracle、SQL Server、Postgre。
XA 事務(wù)由一個(gè)或多個(gè)資源管理器(RM)、一個(gè)事務(wù)管理器(TM)和一個(gè)應(yīng)用程序(ApplicationProgram)組成。
如果有任何一個(gè)參與者 prepare 失敗,那么 TM 會(huì)通知所有完成 prepare 的參與者進(jìn)行回滾。
XA 事務(wù)的特點(diǎn)是:
- 簡(jiǎn)單易理解,開發(fā)較容易
- 對(duì)資源進(jìn)行了長(zhǎng)時(shí)間的鎖定,并發(fā)度低
②三階段提交(3PC)
三階段提交又稱 3PC,相對(duì)于 2PC 來說增加了 CanCommit 階段和超時(shí)機(jī)制。
如果段時(shí)間內(nèi)沒有收到協(xié)調(diào)者的 commit 請(qǐng)求,那么就會(huì)自動(dòng)進(jìn)行 commit,解決了 2PC 單點(diǎn)故障的問題。但是性能問題和不一致問題仍然沒有根本解決。
下面我們還是一起看下三階段流程的是什么樣的?
第一階段:CanCommit 階段。這個(gè)階段所做的事很簡(jiǎn)單,就是協(xié)調(diào)者詢問事務(wù)參與者,你是否有能力完成此次事務(wù)。
如果都返回 yes,則進(jìn)入第二階段。有一個(gè)返回 no 或等待響應(yīng)超時(shí),則中斷事務(wù),并向所有參與者發(fā)送 abort 請(qǐng)求。
第二階段:PreCommit 階段。此時(shí)協(xié)調(diào)者會(huì)向所有的參與者發(fā)送 PreCommit 請(qǐng)求,參與者收到后開始執(zhí)行事務(wù)操作,并將 Undo 和 Redo 信息記錄到事務(wù)日志中。
參與者執(zhí)行完事務(wù)操作后(此時(shí)屬于未提交事務(wù)的狀態(tài)),就會(huì)向協(xié)調(diào)者反饋“Ack”表示我已經(jīng)準(zhǔn)備好提交了,并等待協(xié)調(diào)者的下一步指令。
第三階段:DoCommit 階段。在階段二中如果所有的參與者節(jié)點(diǎn)都可以進(jìn)行 PreCommit 提交,那么協(xié)調(diào)者就會(huì)從“預(yù)提交狀態(tài)”轉(zhuǎn)變?yōu)?ldquo;提交狀態(tài)”。
然后向所有的參與者節(jié)點(diǎn)發(fā)送"doCommit"請(qǐng)求,參與者節(jié)點(diǎn)在收到提交請(qǐng)求后就會(huì)各自執(zhí)行事務(wù)提交操作,并向協(xié)調(diào)者節(jié)點(diǎn)反饋“Ack”消息,協(xié)調(diào)者收到所有參與者的 Ack 消息后完成事務(wù)。
相反,如果有一個(gè)參與者節(jié)點(diǎn)未完成 PreCommit 的反饋或者反饋超時(shí),那么協(xié)調(diào)者都會(huì)向所有的參與者節(jié)點(diǎn)發(fā)送 abort 請(qǐng)求,從而中斷事務(wù)。
③SAGA
Saga 是這一篇數(shù)據(jù)庫(kù)論文 saga 提到的一個(gè)方案。其核心思想是將長(zhǎng)事務(wù)拆分為多個(gè)本地短事務(wù),由 Saga 事務(wù)協(xié)調(diào)器協(xié)調(diào),如果正常結(jié)束那就正常完成,如果某個(gè)步驟失敗,則根據(jù)相反順序一次調(diào)用補(bǔ)償操作。
把上面的轉(zhuǎn)賬作為例子,一個(gè)成功完成的 SAGA 事務(wù)時(shí)序圖如下:
SAGA 事務(wù)的特點(diǎn):
- 并發(fā)度高,不用像 XA 事務(wù)那樣長(zhǎng)期鎖定資源
- 需要定義正常操作以及補(bǔ)償操作,開發(fā)量比 XA 大
- 一致性較弱,對(duì)于轉(zhuǎn)賬,可能發(fā)生 A 用戶已扣款,最后轉(zhuǎn)賬又失敗的情況
④TCC
2PC 是數(shù)據(jù)庫(kù)層面的,而 TCC 是業(yè)務(wù)層面的分布式事務(wù),就像我前面說的分布式事務(wù)不僅僅包括數(shù)據(jù)庫(kù)的操作,還包括發(fā)送短信等,這時(shí)候 TCC 就派上用場(chǎng)了!
TCC 的 3 個(gè)階段:
- Try 階段:嘗試執(zhí)行,完成所有業(yè)務(wù)檢查(一致性), 預(yù)留必須業(yè)務(wù)資源(準(zhǔn)隔離性)
- Confirm 階段:確認(rèn)執(zhí)行真正執(zhí)行業(yè)務(wù),不作任何業(yè)務(wù)檢查,只使用 Try 階段預(yù)留的業(yè)務(wù)資源,Confirm 操作要求具備冪等設(shè)計(jì),Confirm 失敗后需要進(jìn)行重試。
- Cancel 階段:取消執(zhí)行,釋放 Try 階段預(yù)留的業(yè)務(wù)資源,也可以理解為可以理解為把預(yù)留階段的動(dòng)作撤銷了。Cancel 階段的異常和 Confirm 階段異常處理方案基本上一致,要求滿足冪等設(shè)計(jì)。
把上面的轉(zhuǎn)賬作為例子,通常會(huì)在 Try 里面凍結(jié)金額,但不扣款,Confirm 里面扣款,Cancel 里面解凍金額。
一個(gè)成功完成的 TCC 事務(wù)時(shí)序圖如下:
TCC 特點(diǎn)如下:
- 并發(fā)度較高,無長(zhǎng)期資源鎖定
- 開發(fā)量較大,需要提供 Try/Confirm/Cancel 接口
- 一致性較好,不會(huì)發(fā)生 SAGA 已扣款最后又轉(zhuǎn)賬失敗的情況
- TCC 適用于訂單類業(yè)務(wù),對(duì)中間狀態(tài)有約束的業(yè)務(wù)
⑤本地消息表
本地消息表其實(shí)就是利用了 各系統(tǒng)本地的事務(wù)來實(shí)現(xiàn)分布式事務(wù)。
本地消息表顧名思義就是會(huì)有一張存放本地消息的表,一般都是放在數(shù)據(jù)庫(kù)中,然后在執(zhí)行業(yè)務(wù)的時(shí)候?qū)I(yè)務(wù)的執(zhí)行和將消息放入消息表中的操作放在同一個(gè)事務(wù)中,這樣就能保證消息放入本地表中業(yè)務(wù)肯定是執(zhí)行成功的。
然后再去調(diào)用下一個(gè)操作,如果下一個(gè)操作調(diào)用成功了好說,消息表的消息狀態(tài)可以直接改成已成功。
如果調(diào)用失敗也沒事,會(huì)有后臺(tái)任務(wù)定時(shí)去讀取本地消息表,篩選出還未成功的消息再調(diào)用對(duì)應(yīng)的服務(wù),服務(wù)更新成功了再變更消息的狀態(tài)。
這時(shí)候有可能消息對(duì)應(yīng)的操作不成功,因此也需要重試,重試就得保證對(duì)應(yīng)服務(wù)的方法是冪等的,而且一般重試會(huì)有最大次數(shù),超過最大次數(shù)可以記錄下報(bào)警讓人工處理。
可以看到本地消息表其實(shí)實(shí)現(xiàn)的是最終一致性,容忍了數(shù)據(jù)暫時(shí)不一致的情況。
⑥消息事務(wù)
RocketMQ 就很好的支持了消息事務(wù),讓我們來看一下如何通過消息實(shí)現(xiàn)事務(wù)。
第一步先給 Broker 發(fā)送事務(wù)消息即半消息,半消息不是說一半消息,而是這個(gè)消息對(duì)消費(fèi)者來說不可見,然后發(fā)送成功后發(fā)送方再執(zhí)行本地事務(wù)。再根據(jù)本地事務(wù)的結(jié)果向 Broker 發(fā)送 Commit 或者 RollBack 命令。
并且 RocketMQ 的發(fā)送方會(huì)提供一個(gè)反查事務(wù)狀態(tài)接口,如果一段時(shí)間內(nèi)半消息沒有收到任何操作請(qǐng)求,那么 Broker 會(huì)通過反查接口得知發(fā)送方事務(wù)是否執(zhí)行成功,然后執(zhí)行 Commit 或者 RollBack 命令。
如果是 Commit 那么訂閱方就能收到這條消息,然后再做對(duì)應(yīng)的操作,做完了之后再消費(fèi)這條消息即可。
如果是 RollBack 那么訂閱方收不到這條消息,等于事務(wù)就沒執(zhí)行過??梢钥吹酵ㄟ^ RocketMQ 還是比較容易實(shí)現(xiàn)的,RocketMQ 提供了事務(wù)消息的功能,我們只需要定義好事務(wù)反查接口即可。
同時(shí)也可以看到消息事務(wù)實(shí)現(xiàn)的也是最終一致性。
⑦最大努力通知
發(fā)起通知方通過一定的機(jī)制最大努力將業(yè)務(wù)處理結(jié)果通知到接收方。
具體包括:
- 有一定的消息重復(fù)通知機(jī)制。因?yàn)榻邮胀ㄖ娇赡軟]有接收到通知,此時(shí)要有一定的機(jī)制對(duì)消息重復(fù)通知。
- 消息校對(duì)機(jī)制。如果盡最大努力也沒有通知到接收方,或者接收方消費(fèi)消息后要再次消費(fèi),此時(shí)可由接收方主動(dòng)向通知方查詢消息信息來滿足需求。
前面介紹的的本地消息表和消息事務(wù)都屬于可靠消息,與這里介紹的最大努力通知有什么不同?
可靠消息一致性,發(fā)起通知方需要保證將消息發(fā)出去,并且將消息發(fā)到接收通知方,消息的可靠性關(guān)鍵由發(fā)起通知方來保證。
最大努力通知,發(fā)起通知方盡最大的努力將業(yè)務(wù)處理結(jié)果通知為接收通知方,但是可能消息接收不到,此時(shí)需要接收通知方主動(dòng)調(diào)用發(fā)起通知方的接口查詢業(yè)務(wù)處理結(jié)果,通知的可靠性關(guān)鍵在接收通知方。
解決方案上,最大努力通知需要:
- 提供接口,讓接受通知放能夠通過接口查詢業(yè)務(wù)處理結(jié)果
- 消息隊(duì)列 ACK 機(jī)制,消息隊(duì)列按照間隔 1min、5min、10min、30min、1h、2h、5h、10h 的方式,逐步拉大通知間隔 ,直到達(dá)到通知要求的時(shí)間窗口上限。之后不再通知
最大努力通知適用于業(yè)務(wù)通知類型,例如微信交易的結(jié)果,就是通過最大努力通知方式通知各個(gè)商戶,既有回調(diào)通知,也有交易查詢接口。
⑧AT 事務(wù)模式
這是阿里開源項(xiàng)目 seata 中的一種事務(wù)模式,在螞蟻金服也被稱為 FMT。優(yōu)點(diǎn)是該事務(wù)模式使用方式,類似 XA 模式,業(yè)務(wù)無需編寫各類補(bǔ)償操作,回滾由框架自動(dòng)完成,缺點(diǎn)也類似 AT,存在較長(zhǎng)時(shí)間的鎖,不滿足高并發(fā)的場(chǎng)景。
在 Seata 項(xiàng)目中,最早由阿里巴巴中間件開源出的 AT 模式(Automatic Transaction) 是一套創(chuàng)新的、業(yè)務(wù)無侵入的分布式事務(wù)解決方案。
截止 Seata 的 GA 版本發(fā)布,AT 模式 已經(jīng)在開源社區(qū)引起了廣泛關(guān)注,很多家企業(yè)用戶已經(jīng)將 Seata 的 AT 模式應(yīng)用于生產(chǎn)。
AT 模式一階段:首先,在 Seata 的組件中,如果你想開啟分布式事務(wù),那么就應(yīng)該在你的業(yè)務(wù)入口或者事務(wù)發(fā)起入口加上 @GlobalTransactional 注解。
如果你是 AT 模式就要做好數(shù)據(jù)源代理(seata1.0 后全面支持自動(dòng)代理),并被 sqlsessionfactroy 使用(或者直接 jdbc 操作使用被代理數(shù)據(jù)源)。
可以發(fā)現(xiàn)比較關(guān)鍵的異步,與其他模式的區(qū)別便是代理數(shù)據(jù)源,而代理數(shù)據(jù)源又有什么奧秘呢?
AT 模式二階段提交:二階段如果是提交的話,因?yàn)?ldquo;業(yè)務(wù) SQL”在一階段已經(jīng)提交至數(shù)據(jù)庫(kù),所以 Seata 框架只需將一階段保存的快照數(shù)據(jù)和行鎖刪掉,完成數(shù)據(jù)清理即可。
AT 模式二階段回滾:二階段如果是回滾的話,Seata 就需要回滾一階段已經(jīng)執(zhí)行的“業(yè)務(wù) SQL”,還原業(yè)務(wù)數(shù)據(jù)。
回滾方式便是用“before image”還原業(yè)務(wù)數(shù)據(jù);但在還原前要首先要校驗(yàn)臟寫,對(duì)比“數(shù)據(jù)庫(kù)當(dāng)前業(yè)務(wù)數(shù)據(jù)”和 “after image”。
如果兩份數(shù)據(jù)完全一致就說明沒有臟寫,可以還原業(yè)務(wù)數(shù)據(jù),如果不一致就說明有臟寫, 出現(xiàn)臟寫就需要轉(zhuǎn)人工處理。
小結(jié)
本文介紹了分布式事務(wù)的一些基礎(chǔ)理論,并對(duì)常用的分布式事務(wù)方案進(jìn)行了講解。
分布式事務(wù)本身就是一個(gè)技術(shù)難題,業(yè)務(wù)中具體使用哪種方案還是需要不同的業(yè)務(wù)特點(diǎn)自行選擇。
但是我們也會(huì)發(fā)現(xiàn),分布式事務(wù)會(huì)大大的提高流程的復(fù)雜度,會(huì)帶來很多額外的開銷工作,「代碼量上去了,業(yè)務(wù)復(fù)雜了,性能下跌了」。
所以,當(dāng)我們真實(shí)開發(fā)的過程中,能不使用分布式事務(wù)就不使用。
作者:JackHu
簡(jiǎn)介:水滴健康基礎(chǔ)架構(gòu)資深技術(shù)專家
編輯:陶家龍
征稿:有投稿、尋求報(bào)道意向技術(shù)人請(qǐng)聯(lián)絡(luò) editor@51cto.com
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】