哈嘍,大家好,我是指北君。
不知道大家有沒有發(fā)現(xiàn),無論是 Dubbo的推出與使用,又或者是SpringCloud橫空出世,可能自19年以來,分布式,微服務(wù)這些詞語就不絕于耳。
對(duì)于微服務(wù)來說 簡(jiǎn)單理解起來就是每一個(gè)服務(wù)都能夠獨(dú)立的部署與運(yùn)行,服務(wù)之前通過RPC來進(jìn)行互相交互,每一個(gè)微服務(wù)都是由獨(dú)立的小團(tuán)隊(duì)開發(fā),測(cè)試,部署以及上線。對(duì)于分布式 又是一個(gè)整體的概念,顧名思義就是服務(wù)都是分散部署在不同的機(jī)器上,每一個(gè)服務(wù)可能負(fù)責(zé)幾個(gè)功能等……
然后這些也都是服務(wù)級(jí)別,但是小伙伴們有沒有思考一個(gè)數(shù)據(jù)庫層面的問題: 我們?cè)谑褂?code>Dubbo 時(shí)候進(jìn)行dubbo接口調(diào)用的時(shí)候,調(diào)用完dubbo接口進(jìn)行了上游數(shù)據(jù)的修改,下游出現(xiàn)了異常,但是這種時(shí)候,我們使用傳統(tǒng)的 @Transactional注解,并不能夠使得上游已經(jīng)修改過的數(shù)據(jù)進(jìn)行回滾。這樣也就很有可能出現(xiàn)數(shù)據(jù)不一致的情況。
這個(gè)時(shí)候就引出了我們今天介紹的主角:Seata——是一款開源的分布式事務(wù)解決方案,致力于在微服務(wù)架構(gòu)下提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù)。
前驅(qū)知識(shí)
什么是分布式事務(wù)
如下圖所示:分布式事務(wù)是指事務(wù)的參與者、支持事務(wù)的服務(wù)器、資源服務(wù)器以及事務(wù)管理器分別位于不同的分布式系統(tǒng)的不同節(jié)點(diǎn)之上。
注意:本篇文章所講的分布式事務(wù)特指在多個(gè)服務(wù)同時(shí)訪問多個(gè)數(shù)據(jù)源的事務(wù)處理機(jī)制,請(qǐng)注意它與DTP 模型中“分布式事務(wù)”的差異。DTP 模型所指的“分布式”是相對(duì)于數(shù)據(jù)源而言的,并不涉及服務(wù)。下面所指的“分布式”是相對(duì)于服務(wù)而言的,如果嚴(yán)謹(jǐn)?shù)卣f,它更應(yīng)該被稱為“在分布式服務(wù)環(huán)境下的事務(wù)處理機(jī)制”。
(百度百科)
通俗解釋
就是 在我們的系統(tǒng)體量還沒有達(dá)到一定的規(guī)模的時(shí)候,我們單體架構(gòu)是能夠支撐起來所有的服務(wù)請(qǐng)求,此時(shí)所有對(duì)于數(shù)據(jù)庫的操作都在一個(gè)庫中,此時(shí)無論是出錯(cuò)的回滾還是說異常的處理也更加容易處理。
但是在業(yè)務(wù)邏輯更加復(fù)雜的情況下,很多時(shí)候我們都是采用RPC遠(yuǎn)端調(diào)用來完成業(yè)務(wù)邏輯的拆分,將不同的業(yè)務(wù)邏輯和功能模塊進(jìn)行拆分,使得其在各自實(shí)現(xiàn)自己的邏輯的情況下還能夠?yàn)槠渌討?yīng)用提供服務(wù),這也是目前為止市面上上常用到的對(duì)于業(yè)務(wù)邏輯增加的應(yīng)對(duì)方法,但是導(dǎo)致的問題就是,在不同的子模塊中,我們無法保證在本地事務(wù)成功之后,調(diào)用其他服務(wù)是否成功,不成功數(shù)據(jù)能否進(jìn)行一個(gè)正常的回滾,所以也就引入了分布式事務(wù)-使其能夠在不同的服務(wù)和不同的數(shù)據(jù)源中保證數(shù)據(jù)的一致性。
CAP是什么以及原理
既然討論到了分布式事務(wù),就不得不來簡(jiǎn)單概述一下CAP原理,其最開始起源于在 2000 年 7 月,是加州大學(xué)伯克利分校的一個(gè)教授于“ACM 分布式計(jì)算原理研討會(huì)(PODC)”上提出的一個(gè)猜想。
兩年之后,麻省理工學(xué)院的兩名教授以嚴(yán)謹(jǐn)?shù)臄?shù)學(xué)推理證明了 CAP 猜想。自此,CAP 正式從猜想變?yōu)榉植际接?jì)算領(lǐng)域所公認(rèn)的著名定理。這個(gè)定理里描述了一個(gè)分布式的系統(tǒng)中,涉及共享數(shù)據(jù)問題時(shí),以下三個(gè)特性最多只能同時(shí)滿足其中兩個(gè):
- 一致性(Consistency):代表數(shù)據(jù)在任何時(shí)刻、任何分布式節(jié)點(diǎn)中所看到的都是符合預(yù)期的。
- 可用性(Availability):代表系統(tǒng)不間斷地提供服務(wù)的能力,理解可用性要先理解與其密切相關(guān)兩個(gè)指標(biāo):可靠性(Reliability)和可維護(hù)性(Serviceability)。可靠性使用平均無故障時(shí)間(Mean Time Between Failure,MTBF)來度量;可維護(hù)性使用平均可修復(fù)時(shí)間(Mean Time To Repair,MTTR)來度量??捎眯院饬肯到y(tǒng)可以正常使用的時(shí)間與總時(shí)間之比,其表征為:A=MTBF/(MTBF+MTTR),即可用性是由可靠性和可維護(hù)性計(jì)算得出的比例值,譬如 99.9999%可用,即代表平均年故障修復(fù)時(shí)間為 32 秒。
- 分區(qū)容忍性(Partition Tolerance):代表分布式環(huán)境中部分節(jié)點(diǎn)因網(wǎng)絡(luò)原因而彼此失聯(lián)后,即與其他節(jié)點(diǎn)形成“網(wǎng)絡(luò)分區(qū)”時(shí),系統(tǒng)仍能正確地提供服務(wù)的能力。
但是僅僅論述概念,對(duì)于CAP的理解來說還是比較難的,下面我們以一個(gè)實(shí)際的案例(如下場(chǎng)景案例)來說明,這三種不同的特質(zhì)對(duì)于分布式系統(tǒng)來說意味著什么。
場(chǎng)景案例:
有一個(gè)在線書店。每當(dāng)一本書被成功售出時(shí),需要確保以下三件事情被正確地處理:
1 用戶的賬號(hào)扣減相應(yīng)的商品款項(xiàng)。
2 商品倉庫中扣減庫存,將商品標(biāo)識(shí)為待配送狀態(tài)。
3 商家的賬號(hào)增加相應(yīng)的商品款項(xiàng)。
如下圖所示,一個(gè)用戶購買一本書籍的過程將由賬號(hào)服務(wù)、商家服務(wù)和庫存服務(wù)對(duì)應(yīng)各自集群中的某一個(gè)節(jié)點(diǎn)來完成響應(yīng)。
在這套系統(tǒng)中,每一個(gè)單獨(dú)的服務(wù)節(jié)點(diǎn)都有自己的數(shù)據(jù)庫(這里是為了便于說明問題的假設(shè),在實(shí)際生產(chǎn)系統(tǒng)中,一般應(yīng)避免將用戶余額這樣的數(shù)據(jù)設(shè)計(jì)成存儲(chǔ)在多個(gè)可寫的數(shù)據(jù)庫中),假設(shè)某次交易請(qǐng)求分別由“賬號(hào)節(jié)點(diǎn) 1”、“商家節(jié)點(diǎn) 2”、“倉庫節(jié)點(diǎn) N”聯(lián)合進(jìn)行響應(yīng)。當(dāng)用戶購買一件價(jià)值 100 元的商品后,賬號(hào)節(jié)點(diǎn) 1 首先應(yīng)給該用戶賬號(hào)扣減 100 元貨款,它在自己數(shù)據(jù)庫扣減 100 元很容易,但它還要把這次交易變動(dòng)告知本集群的節(jié)點(diǎn) 2 到節(jié)點(diǎn) N,并要確保能正確變更商家和倉庫集群其他賬號(hào)節(jié)點(diǎn)中的關(guān)聯(lián)數(shù)據(jù),此時(shí)將面臨以下可能的情況。
- 如果該變動(dòng)信息沒有及時(shí)同步給其他賬號(hào)節(jié)點(diǎn),將導(dǎo)致有可能發(fā)生用戶購買另一商品時(shí),被分配給到另一個(gè)節(jié)點(diǎn)處理,由于看到賬號(hào)上有不正確的余額而錯(cuò)誤地發(fā)生了原本無法進(jìn)行的交易,此為一致性問題。
- 如果由于要把該變動(dòng)信息同步給其他賬號(hào)節(jié)點(diǎn),必須暫時(shí)停止對(duì)該用戶的交易服務(wù),直至數(shù)據(jù)同步一致后再重新恢復(fù),將可能導(dǎo)致用戶在下一次購買商品時(shí),因系統(tǒng)暫時(shí)無法提供服務(wù)而被拒絕交易,此為可用性問題。
- 如果由于賬號(hào)服務(wù)集群中某一部分節(jié)點(diǎn),因出現(xiàn)網(wǎng)絡(luò)問題,無法正常與另一部分節(jié)點(diǎn)交換賬號(hào)變動(dòng)信息,此時(shí)服務(wù)集群中無論哪一部分節(jié)點(diǎn)對(duì)外提供的服務(wù)都可能是不正確的,整個(gè)集群能否承受由于部分節(jié)點(diǎn)之間的連接中斷而仍然能夠正確地提供服務(wù),此為分區(qū)容忍性。
以上還僅僅涉及了賬號(hào)服務(wù)集群自身的 CAP 問題,對(duì)于整個(gè) 站點(diǎn)來說,它更是面臨著來自于賬號(hào)、商家和倉庫服務(wù)集群帶來的 CAP 問題,譬如,用戶賬號(hào)扣款后,可能出現(xiàn)的如下問題:
- 由于未及時(shí)通知倉庫服務(wù)中的全部節(jié)點(diǎn),導(dǎo)致另一次交易中看到倉庫里有不正確的庫存數(shù)據(jù)而發(fā)生
超售。 - 又譬如因涉及倉庫中某個(gè)商品的交易正在進(jìn)行,為了同步用戶、商家和倉庫的交易變動(dòng),而暫時(shí)鎖定該商品的交易服務(wù),導(dǎo)致了的可用性問題,等等。
由于 CAP 定理已有嚴(yán)格的證明,我們這里不去探討如何進(jìn)行嚴(yán)格的證明,而是直接分析如果舍棄 C、A、P 時(shí)所帶來的不同影響。
-
如果放棄分區(qū)容忍性(CA without P),意味著我們將假設(shè)節(jié)點(diǎn)之間通信永遠(yuǎn)是可靠的。永遠(yuǎn)可靠的通信在分布式系統(tǒng)中必定不成立的,這不是你想不想的問題,而是只要用到網(wǎng)絡(luò)來共享數(shù)據(jù),分區(qū)現(xiàn)象就會(huì)始終存在。在現(xiàn)實(shí)中,最容易找到放棄分區(qū)容忍性的例子便是傳統(tǒng)的關(guān)系數(shù)據(jù)庫集群,這樣的集群雖然依然采用由網(wǎng)絡(luò)連接的多個(gè)節(jié)點(diǎn)來協(xié)同工作,但數(shù)據(jù)卻不是通過網(wǎng)絡(luò)來實(shí)現(xiàn)共享的。以 Oracle 的
RAC集群為例,它的每一個(gè)節(jié)點(diǎn)均有自己獨(dú)立的SGA、重做日志、回滾日志等部件,但各個(gè)節(jié)點(diǎn)是通過共享存儲(chǔ)中的同一份數(shù)據(jù)文件和控制文件來獲取數(shù)據(jù)的,通過共享磁盤的方式來避免出現(xiàn)網(wǎng)絡(luò)分區(qū)。因而Oracle RAC雖然也是由多個(gè)實(shí)例組成的數(shù)據(jù)庫,但它并不能稱作是分布式數(shù)據(jù)庫。 - 如果放棄可用性(CP without A),意味著我們將假設(shè)一旦網(wǎng)絡(luò)發(fā)生分區(qū),節(jié)點(diǎn)之間的信息同步時(shí)間可以無限制地延長,因?yàn)槲覀兎艞壛丝捎眯浴?/li>
- 如果放棄一致性(AP without C),意味著我們將假設(shè)一旦發(fā)生分區(qū),節(jié)點(diǎn)之間所提供的數(shù)據(jù)可能不一致。選擇放棄一致性的 AP 系統(tǒng)目前是設(shè)計(jì)分布式系統(tǒng)的主流選擇,因?yàn)?P 是分布式網(wǎng)絡(luò)的天然屬性,你再不想要也無法丟棄;而 A 通常是建設(shè)分布式的目的,如果可用性隨著節(jié)點(diǎn)數(shù)量增加反而降低的話,很多分布式系統(tǒng)可能就失去了存在的價(jià)值,除非銀行、證券這些涉及金錢交易的服務(wù),寧可中斷也不能出錯(cuò),否則多數(shù)系統(tǒng)是不能容忍節(jié)點(diǎn)越多可用性反而越低的。目前大多數(shù) NoSQL 庫和支持分布式的緩存框架都是 AP 系統(tǒng),以 Redis 集群為例,如果某個(gè) Redis 節(jié)點(diǎn)出現(xiàn)網(wǎng)絡(luò)分區(qū),那仍不妨礙各個(gè)節(jié)點(diǎn)以自己本地存儲(chǔ)的數(shù)據(jù)對(duì)外提供緩存服務(wù),但這時(shí)有可能出現(xiàn)請(qǐng)求分配到不同節(jié)點(diǎn)時(shí)返回給客戶端的是不一致的數(shù)據(jù)。
上訴討論到了CAP在實(shí)際使用過程中,如何具體的使用和如何具體的分析,雖然我們一直心心念念的分布式事務(wù)最終要保存的“一致性”,但是最終的結(jié)論確是“選擇放棄一致性的 AP 系統(tǒng)目前是設(shè)計(jì)分布式系統(tǒng)的主流選擇”,在分布式環(huán)境中“一致性”卻不得不成為通常被犧牲,被放棄的那一項(xiàng)屬性,但是無論如何,最終確保操作結(jié)果在最終交付的時(shí)候是正確的,這句話的意思是允許數(shù)據(jù)在中間過程出錯(cuò)(不一致),但應(yīng)該在輸出時(shí)被修正過來。為此,人們又重新給一致性下了定義,將前面我們?cè)?CAP、ACID 中討論的一致性稱為“強(qiáng)一致性”(Strong Consistency),有時(shí)也稱為“線性一致性”(通常是在討論共識(shí)算法的場(chǎng)景中),而把犧牲了 C 的 AP 系統(tǒng)又要盡可能獲得正確的結(jié)果的行為稱為追求“弱一致性”。不過,如果單純只說“弱一致性”那其實(shí)就是“不保證一致性”的意思。在弱一致性里,人們又總結(jié)出了一種稍微強(qiáng)一點(diǎn)的特例,被稱為“最終一致性”(Eventual Consistency),它是指:如果數(shù)據(jù)在一段時(shí)間之內(nèi)沒有被另外的操作所更改,那它最終將會(huì)達(dá)到與強(qiáng)一致性過程相同的結(jié)果,有時(shí)候面向最終一致性的算法也被稱為“樂觀復(fù)制算法”。
如上,我們討論的分布式事務(wù)中,最開始我們引入各種分布式的處理方式,本意上是追求強(qiáng)一致性,到現(xiàn)在降低成為了“最終一致性”。所以說,在一致性變動(dòng)的基礎(chǔ)上,“事務(wù)”這個(gè)詞的含義我們也同樣需要為其進(jìn)行擴(kuò)展。我們通常把事務(wù)中的ACID稱為剛性事務(wù),于是我們?cè)诜植际较到y(tǒng)中的處理也就可以隨之稱其為柔性事務(wù),他們包括 可靠事件隊(duì)列、以及AT、TCC、SAGA和 XA 等事務(wù)模式,于是也就引出來我們今天的主人公 分布式事務(wù)的解決方案者 seata。
Seata
什么是二段提交
如圖所示:
就是說執(zhí)行的流程如下:
- 首先想要修改A數(shù)據(jù)的時(shí)候把對(duì)應(yīng)的記錄A(之前的記錄)加載到Buffer Pool(就是說緩沖區(qū)里面) 然后把記錄A的舊值寫入到 undo log 日志里面 便于回滾的操作。
- 執(zhí)行器來更新內(nèi)存里面的數(shù)據(jù)
- 首先會(huì)先執(zhí)行redo log 日志 此時(shí)是第一階段的提交,然后 將記錄寫入到 bin log 里面 最后一步就是說 再執(zhí)行器寫 redo log(commit)
兩個(gè)階段的第一個(gè)階段: (prepare) 寫redo log 并將其標(biāo)記為prepare狀態(tài)
然后寫bin log
兩個(gè)階段的第二個(gè)階段:(commit)寫redo log 并將其標(biāo)記為commit狀態(tài)
因?yàn)樵诓僮鞯倪^程中 MySQL的日志有兩個(gè) 我們需要在操作的時(shí)候 兩個(gè)日志都保證是一致的,下面來分析一下在不使用兩段提交的時(shí)候直接寫入redo log 或者說 bin log的時(shí)候會(huì)出現(xiàn)什么樣的問題:
- 就是說在寫完redo log 日志之后 系統(tǒng)出現(xiàn)了異常,這個(gè)時(shí)候重啟之后 InnoDB存儲(chǔ)引擎會(huì)根據(jù)redo log 里面的數(shù)據(jù)進(jìn)行數(shù)據(jù)的恢復(fù),這個(gè)時(shí)候 主數(shù)據(jù)庫是沒有問題的,但是若是我們還有從庫,這個(gè)時(shí)候就會(huì)出現(xiàn)從數(shù)據(jù)庫并不能夠?qū)?duì)應(yīng)的數(shù)據(jù)值與主庫進(jìn)行一個(gè)正確的匹配,因?yàn)槲覀兊膹膸爝M(jìn)行數(shù)據(jù)同步的時(shí)候依賴的是我們的bin log。
- 若是我們把兩者顛倒過來,先寫入bin log 之后在一段提交redo log 出現(xiàn)的問題就是沒有能夠正確的寫入redo log 日志,從庫能夠根據(jù) bin log 進(jìn)行值的正確恢復(fù),但是主庫還是不能夠拿到正確的數(shù)據(jù),所以最后出現(xiàn)的情況還是不能夠正確匹配。
所以就需要 兩段式 來進(jìn)行一個(gè)確定處理,如果只是在prepare 階段沒有提交,重啟之后因?yàn)?redo 和bin 都沒有數(shù)據(jù),所以說也不會(huì)有影響,此時(shí)也會(huì)回滾事務(wù),如果都寫入,但是沒有提交,此時(shí)重啟之后就會(huì)對(duì)事務(wù)進(jìn)一個(gè)提交。
Seata是什么
Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡(jiǎn)單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了AT、TCC、SAGA和 XA事務(wù)模式,為用戶打造一站式的分布式解決方案。
Seata能夠做什么
是一款開源的分布式事務(wù)解決方案,能夠幫助我們解決前面提及到的分布式事務(wù)中出現(xiàn)的問題。即在分布式系統(tǒng)中,能夠保證事物的一致性。 具體更多的內(nèi)容可以去官網(wǎng)查閱相關(guān)資料
Seata TC(Transaction Coordinator),事務(wù)協(xié)調(diào)者,會(huì)接受全局事務(wù)的開啟、提交、回滾,分支事務(wù)的注冊(cè)。通過它的協(xié)調(diào),達(dá)到多事務(wù)的一致性。
Seata TM(Transaction Manager),事務(wù)管理器,或者可以理解成事務(wù)的發(fā)起者,負(fù)責(zé)向 TC 發(fā)起全局事務(wù)的開啟、提交、回滾。
Seata RM(Resource Manager),資源管理器,或者可以理解成事務(wù)的參與者,負(fù)責(zé)向 TC 發(fā)起分支事務(wù)的注冊(cè)、提交,接收自 TC 請(qǐng)求的分支事務(wù)的提交、回滾。
不同模式
AT
前提
- 基于支持本地 ACID 事務(wù)的關(guān)系型數(shù)據(jù)庫。
- Java 應(yīng)用,通過 JDBC 訪問數(shù)據(jù)庫。
整體機(jī)制
前面我們已經(jīng)介紹過兩段提交,現(xiàn)在來在兩段提交的基礎(chǔ)上進(jìn)行具體的演變
兩階段提交協(xié)議的演變:
- 一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個(gè)本地事務(wù)中提交,釋放本地鎖和連接資源。
- 二階段:
- 提交異步化,非常快速地完成。
- 回滾通過一階段的回滾日志進(jìn)行反向補(bǔ)償。
寫隔離
- 一階段本地事務(wù)提交前,需要確保先拿到 全局鎖 。
- 拿不到 全局鎖 ,不能提交本地事務(wù)。
- 拿 全局鎖 的嘗試被限制在一定范圍內(nèi),超出范圍將放棄,并回滾本地事務(wù),釋放本地鎖。
以一個(gè)示例來說明:
兩個(gè)全局事務(wù) tx1 和 tx2,分別對(duì) a 表的 m 字段進(jìn)行更新操作,m 的初始值 1000。
tx1 先開始,開啟本地事務(wù),拿到本地鎖,更新操作 m = 1000 - 100 = 900。
本地事務(wù)提交前,先拿到該記錄的 全局鎖 ,本地提交釋放本地鎖。 tx2 后開始,開啟本地事務(wù),拿到本地鎖,更新操作 m = 900 - 100 = 800。本地事務(wù)提交前,嘗試拿該記錄的 全局鎖 ,tx1 全局提交前,該記錄的全局鎖被 tx1 持有,tx2 需要重試等待 全局鎖 。
tx1 二階段全局提交,釋放 全局鎖 。tx2 拿到 全局鎖 提交本地事務(wù)。
如果 tx1 的二階段全局回滾,則 tx1 需要重新獲取該數(shù)據(jù)的本地鎖,進(jìn)行反向補(bǔ)償?shù)母虏僮?,?shí)現(xiàn)分支的回滾。
此時(shí),如果 tx2 仍在等待該數(shù)據(jù)的 全局鎖,同時(shí)持有本地鎖,則 tx1 的分支回滾會(huì)失敗。分支的回滾會(huì)一直重試,直到 tx2 的 全局鎖 等鎖超時(shí),放棄 全局鎖 并回滾本地事務(wù)釋放本地鎖,tx1 的分支回滾最終成功。
因?yàn)檎麄€(gè)過程 全局鎖 在 tx1 結(jié)束前一直是被 tx1 持有的,所以不會(huì)發(fā)生 臟寫 的問題。
讀隔離
在數(shù)據(jù)庫本地事務(wù)隔離級(jí)別 讀已提交(Read Committed) 或以上的基礎(chǔ)上,Seata(AT 模式)的默認(rèn)全局隔離級(jí)別是 讀未提交(Read Uncommitted) 。
如果應(yīng)用在特定場(chǎng)景下,必需要求全局的 讀已提交 ,目前 Seata 的方式是通過 SELECT FOR UPDATE 語句的代理。
SELECT FOR UPDATE 語句的執(zhí)行會(huì)申請(qǐng) 全局鎖 ,如果 全局鎖 被其他事務(wù)持有,則釋放本地鎖(回滾 SELECT FOR UPDATE 語句的本地執(zhí)行)并重試。這個(gè)過程中,查詢是被 block 住的,直到 全局鎖 拿到,即讀取的相關(guān)數(shù)據(jù)是 已提交 的,才返回。
出于總體性能上的考慮,Seata 目前的方案并沒有對(duì)所有 SELECT 語句都進(jìn)行代理,僅針對(duì) FOR UPDATE 的 SELECT 語句。
工作機(jī)制
以一個(gè)示例來說明整個(gè) AT 分支的工作過程。
業(yè)務(wù)表:product
| Field | Type | Key |
|---|---|---|
| id | bigint(20) | PRI |
| name | varchar(100) | |
| since | varchar(100) |
AT 分支事務(wù)的業(yè)務(wù)邏輯:
update product set name = 'GTS' where name = 'TXC';
一階段
過程:
- 解析 SQL:得到 SQL 的類型(UPDATE),表(product),條件(where name = 'TXC')等相關(guān)的信息。
- 查詢前鏡像:根據(jù)解析得到的條件信息,生成查詢語句,定位數(shù)據(jù)。
select id, name, since from product where name = 'TXC';
得到前鏡像:
| id | name | since |
|---|---|---|
| 1 | TXC | 2014 |
- 執(zhí)行業(yè)務(wù) SQL:更新這條記錄的 name 為 'GTS'。
- 查詢后鏡像:根據(jù)前鏡像的結(jié)果,通過 主鍵 定位數(shù)據(jù)。
select id, name, since from product where id = 1;
得到后鏡像:
| id | name | since |
|---|---|---|
| 1 | GTS | 2014 |
- 插入回滾日志:把前后鏡像數(shù)據(jù)以及業(yè)務(wù) SQL 相關(guān)的信息組成一條回滾日志記錄,插入到
UNDO_LOG表中。
{
"branchId": 641789253,
"undoItems": [{
"afterImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "GTS"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"beforeImage": {
"rows": [{
"fields": [{
"name": "id",
"type": 4,
"value": 1
}, {
"name": "name",
"type": 12,
"value": "TXC"
}, {
"name": "since",
"type": 12,
"value": "2014"
}]
}],
"tableName": "product"
},
"sqlType": "UPDATE"
}],
"xid": "xid:xxx"
}
- 提交前,向 TC 注冊(cè)分支:申請(qǐng)
product表中,主鍵值等于 1 的記錄的 全局鎖 。 - 本地事務(wù)提交:業(yè)務(wù)數(shù)據(jù)的更新和前面步驟中生成的 UNDO LOG 一并提交。
- 將本地事務(wù)提交的結(jié)果上報(bào)給 TC。
二階段-回滾
- 收到 TC 的分支回滾請(qǐng)求,開啟一個(gè)本地事務(wù),執(zhí)行如下操作。
- 通過 XID 和 Branch ID 查找到相應(yīng)的 UNDO LOG 記錄。
- 數(shù)據(jù)校驗(yàn):拿 UNDO LOG 中的后鏡與當(dāng)前數(shù)據(jù)進(jìn)行比較,如果有不同,說明數(shù)據(jù)被當(dāng)前全局事務(wù)之外的動(dòng)作做了修改。這種情況,需要根據(jù)配置策略來做處理,詳細(xì)的說明在另外的文檔中介紹。
- 根據(jù) UNDO LOG 中的前鏡像和業(yè)務(wù) SQL 的相關(guān)信息生成并執(zhí)行回滾的語句:
update product set name = 'TXC' where id = 1;
- 提交本地事務(wù)。并把本地事務(wù)的執(zhí)行結(jié)果(即分支事務(wù)回滾的結(jié)果)上報(bào)給 TC。
二階段-提交
- 收到 TC 的分支提交請(qǐng)求,把請(qǐng)求放入一個(gè)異步任務(wù)的隊(duì)列中,馬上返回提交成功的結(jié)果給 TC。
- 異步任務(wù)階段的分支提交請(qǐng)求將異步和批量地刪除相應(yīng) UNDO LOG 記錄。
TCC
回顧總覽中的描述:一個(gè)分布式的全局事務(wù),整體是 兩階段提交 的模型。全局事務(wù)是由若干分支事務(wù)組成的,分支事務(wù)要滿足 兩階段提交 的模型要求,即需要每個(gè)分支事務(wù)都具備自己的:
- 一階段 prepare 行為
- 二階段 commit 或 rollback 行為
根據(jù)兩階段行為模式的不同,我們將分支事務(wù)劃分為 Automatic (Branch) Transaction Mode 和 Manual (Branch) Transaction Mode.
AT 模式(參考鏈接 TBD)基于 支持本地 ACID 事務(wù) 的 關(guān)系型數(shù)據(jù)庫:
- 一階段 prepare 行為:在本地事務(wù)中,一并提交業(yè)務(wù)數(shù)據(jù)更新和相應(yīng)回滾日志記錄。
- 二階段 commit 行為:馬上成功結(jié)束,自動(dòng) 異步批量清理回滾日志。
- 二階段 rollback 行為:通過回滾日志,自動(dòng) 生成補(bǔ)償操作,完成數(shù)據(jù)回滾。
相應(yīng)的,TCC 模式,不依賴于底層數(shù)據(jù)資源的事務(wù)支持:
- 一階段 prepare 行為:調(diào)用 自定義 的 prepare 邏輯。
- 二階段 commit 行為:調(diào)用 自定義 的 commit 邏輯。
- 二階段 rollback 行為:調(diào)用 自定義 的 rollback 邏輯。
所謂 TCC 模式,是指支持把 自定義 的分支事務(wù)納入到全局事務(wù)的管理中。
SAGA
TCC 事務(wù)具有較強(qiáng)的隔離性,避免了“超售”的問題,而且其性能一般來說是本篇提及的幾種柔性事務(wù)模式中最高的,但它仍不能滿足所有的場(chǎng)景。TCC 的最主要限制是它的業(yè)務(wù)侵入性很強(qiáng),這里并不是重復(fù)上一節(jié)提到的它需要開發(fā)編碼配合所帶來的工作量,而更多的是指它所要求的技術(shù)可控性上的約束。
譬如,把我們的場(chǎng)景事例修改如下:由于中國網(wǎng)絡(luò)支付日益盛行,現(xiàn)在用戶和商家在書店系統(tǒng)中可以選擇不再開設(shè)充值賬號(hào),至少不會(huì)強(qiáng)求一定要先從銀行充值到系統(tǒng)中才能進(jìn)行消費(fèi),允許直接在購物時(shí)通過 U 盾或掃碼支付,在銀行賬號(hào)中劃轉(zhuǎn)貨款。這個(gè)需求完全符合國內(nèi)網(wǎng)絡(luò)支付盛行的現(xiàn)狀,卻給系統(tǒng)的事務(wù)設(shè)計(jì)增加了額外的限制:如果用戶、商家的賬號(hào)余額由銀行管理的話,其操作權(quán)限和數(shù)據(jù)結(jié)構(gòu)就不可能再隨心所欲的地自行定義,通常也就無法完成凍結(jié)款項(xiàng)、解凍、扣減這樣的操作,因?yàn)殂y行一般不會(huì)配合你的操作。所以 TCC 中的第一步 Try 階段往往無法施行。我們只能考慮采用另外一種柔性事務(wù)方案:SAGA 事務(wù)。SAGA 在英文中是“長篇故事、長篇記敘、一長串事件”的意思。對(duì)應(yīng)的由如下兩個(gè)部分組成:
大事務(wù)拆分若干個(gè)小事務(wù),將整個(gè)分布式事務(wù) T 分解為 n 個(gè)子事務(wù),命名為 T1,T2,…,Ti,…,Tn。每個(gè)子事務(wù)都應(yīng)該是或者能被視為是原子行為。如果分布式事務(wù)能夠正常提交,其對(duì)數(shù)據(jù)的影響(最終一致性)應(yīng)與連續(xù)按順序成功提交 Ti等價(jià)。
-
為每一個(gè)子事務(wù)設(shè)計(jì)對(duì)應(yīng)的補(bǔ)償動(dòng)作,命名為 C1,C2,…,Ci,…,Cn。Ti與 Ci
必須滿足以下條件:
- Ti與 Ci都具備冪等性。
- Ti與 Ci滿足交換律(Commutative),即先執(zhí)行 Ti還是先執(zhí)行 Ci,其效果都是一樣的。
- Ci必須能成功提交,即不考慮 Ci本身提交失敗被回滾的情形,如出現(xiàn)就必須持續(xù)重試直至成功,或者要人工介入。
如果 T1到 Tn均成功提交,那事務(wù)順利完成,否則,要采取以下兩種恢復(fù)策略之一:
- 正向恢復(fù)(Forward Recovery):如果 Ti事務(wù)提交失敗,則一直對(duì) Ti進(jìn)行重試,直至成功為止(最大努力交付)。這種恢復(fù)方式不需要補(bǔ)償,適用于事務(wù)最終都要成功的場(chǎng)景,譬如在別人的銀行賬號(hào)中扣了款,就一定要給別人發(fā)貨。正向恢復(fù)的執(zhí)行模式為:T1,T2,…,Ti(失敗),Ti(重試)…,Ti+1,…,Tn。
- 反向恢復(fù)(Backward Recovery):如果 Ti事務(wù)提交失敗,則一直執(zhí)行 Ci對(duì) Ti進(jìn)行補(bǔ)償,直至成功為止(最大努力交付)。這里要求 Ci必須(在持續(xù)重試后)執(zhí)行成功。反向恢復(fù)的執(zhí)行模式為:T1,T2,…,Ti(失敗),Ci(補(bǔ)償),…,C2,C1。
與 TCC 相比,SAGA 不需要為資源設(shè)計(jì)凍結(jié)狀態(tài)和撤銷凍結(jié)的操作,補(bǔ)償操作往往要比凍結(jié)操作容易實(shí)現(xiàn)得多。譬如,前面提到的賬號(hào)余額直接在銀行維護(hù)的場(chǎng)景,從銀行劃轉(zhuǎn)貨款到 Fenix's Bookstore 系統(tǒng)中,這步是經(jīng)由用戶支付操作(掃碼或 U 盾)來促使銀行提供服務(wù);如果后續(xù)業(yè)務(wù)操作失敗,盡管我們無法要求銀行撤銷掉之前的用戶轉(zhuǎn)賬操作,但是由 Fenix's Bookstore 系統(tǒng)將貨款轉(zhuǎn)回到用戶賬上作為補(bǔ)償措施卻是完全可行的。
實(shí)戰(zhàn)
這里使用到A項(xiàng)目調(diào)用B項(xiàng)目的dubbo接口,查看在下游邏輯出錯(cuò)和不出錯(cuò)情況下上游的修改會(huì)不會(huì)回滾。
前提準(zhǔn)備
環(huán)境展示
這里使用nacos作為seata的注冊(cè)中心,將主要數(shù)據(jù)配置搭載在注冊(cè)中心上,對(duì)于每一個(gè)項(xiàng)目的入侵性最小。這里就跳過如何搭建本地的nacos和本地的seata服務(wù),具體如何實(shí)施和搭建,可以參見指向君的個(gè)人seata 無坑搭建博客。
完成基礎(chǔ)環(huán)境的啟動(dòng)之后,可以看到如下nacos上基礎(chǔ)配置:
和已經(jīng)注冊(cè)到nacos上的seata服務(wù):
配置
seata.enabled=true
# 默認(rèn)配置數(shù)據(jù)分組
seata.registry.nacos.group=DEFAULT_GROUP
# 注冊(cè)數(shù)據(jù)信息
seata.registry.type=nacos
seata.registry.nacos.application=seata-server
seata.registry.nacos.server-addr=127.0.0.1
seata.registry.nacos.namespace=3c628611-8dc8-4968-b99e-f7873e3350b6
seata.registry.nacos.cluster=default
# 配置數(shù)據(jù)信息
seata.config.type=nacos
seata.config.nacos.namespace=3c628611-8dc8-4968-b99e-f7873e3350b6
seata.config.nacos.server-addr=127.0.0.1
seata.config.nacos.group=SEATA_GROUP
依賴
<seata.version>1.4.2</seata.version>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>${seata.version}</version>
</dependency>
食用
事物分組概念
如下所示 其實(shí)就是類比于服務(wù)實(shí)例,通過這個(gè)配置的事務(wù)分組去找尋真正的后端集群。在服務(wù)啟動(dòng)的時(shí)候,拿到對(duì)應(yīng)的配置項(xiàng)即對(duì)應(yīng)的集群名字,然后通過特定的前后綴+集群服務(wù)名稱去構(gòu)造服務(wù)名,各配置中心的服務(wù)名實(shí)現(xiàn)不同。拿到服務(wù)名去相應(yīng)的注冊(cè)中心去拉取相應(yīng)服務(wù)名的服務(wù)列表,獲得后端真實(shí)的 TC 服務(wù)列表。
service.vgroupMapping.{your-server-name}-seata-service-group
對(duì)于每一個(gè)環(huán)境,都會(huì)有自己的applicationID,需要在nacos中添加對(duì)應(yīng)的配置項(xiàng):
演示
- 在上游對(duì)應(yīng)的代碼上添加對(duì)應(yīng)的注解:
@GlobalTransactional
- 如上如圖所示,上游調(diào)用下游
dubbo接口,在上游接口出現(xiàn)異常之后,下游接口進(jìn)行的修改應(yīng)該進(jìn)行一個(gè)回滾。
提問
- 通過實(shí)戰(zhàn)發(fā)現(xiàn)上游獲取到的全局XID同下游獲取到的XID是相同的,但是在配置信息中,我們并沒有發(fā)現(xiàn)有關(guān)
dubbo相關(guān)聯(lián)的配置,又或者說seata是如何與dubbo進(jìn)行一個(gè)調(diào)度處理,使得上下游項(xiàng)目能夠獲取到一致的全局XID。 - 通過實(shí)戰(zhàn)發(fā)現(xiàn),在對(duì)下游數(shù)據(jù)的修改之后,數(shù)據(jù)真的進(jìn)入到了數(shù)據(jù)庫中,并不像我們傳統(tǒng)的事務(wù)(鎖住數(shù)據(jù),全局提交),在這個(gè)時(shí)候,數(shù)據(jù)真的進(jìn)入到了數(shù)據(jù)庫,并且也能夠被查看(表示有讀權(quán)限)。那么不會(huì)出現(xiàn)不可重復(fù)讀事件的發(fā)生嗎?又或者說 seata還是已經(jīng)默許了這種事情的存在?
- 什么是事務(wù)分組的概念? 為什么在nacos配置中不添加
service.vgroupMapping.{your-server-name}-seata-service-group就會(huì)一直出現(xiàn)錯(cuò)誤日志的打??? 作用是什么?
注: 以下內(nèi)容部分出自鳳凰架構(gòu)以及seata 官網(wǎng)
指北君有話說
可以說seata算是目前為止用于解決分布式事務(wù)的最好方法啦,無論是從文檔的完善性與各種情況下的解決方案,又或者是社區(qū)的活躍度與大廠光環(huán)的加持等。
所以目前為止有相關(guān)困惑的小伙伴快些用起來吧!
關(guān)注開源指北,后臺(tái)回復(fù)seata獲取資源。
這里是開源指北,立志做最好的開源分享平臺(tái),分享有趣實(shí)用的開源項(xiàng)目。
同時(shí)也歡迎加入開源指北交流群,群里你可以摸魚、劃水、吐槽、咨詢,還有簡(jiǎn)歷模板、各種技術(shù)面試資料等100G的資源等著你領(lǐng)取哦??靵硪黄鹆囊涣陌桑?/p>
以上就是本次推薦的全部內(nèi)容,我是指北君,感謝各位的觀看。