<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你們想看的分布式事務(wù),三歪搞來了。

          共 9480字,需瀏覽 19分鐘

           ·

          2020-09-26 10:31

          本文公眾號來源:yes的練級攻略

          作者:是Yes呀

          本文已收錄至我的GitHub

          今天我想和大家一起盤一盤分布式事務(wù),會介紹常見的分布式事務(wù)實現(xiàn)方案和其優(yōu)缺點以及適用的場景,并會帶出它們的一些變體實現(xiàn)。

          還會捎帶一下分布式數(shù)據(jù)庫對 2PC 的改進(jìn)模型,看看分布式數(shù)據(jù)庫是如何做的。

          然后再分析一波分布式事務(wù)框架 Seata 的具體實現(xiàn),看看分布式事務(wù)究竟是如何落地的,畢竟協(xié)議要落地才是有用的。

          首先我們來提一下事務(wù)和分布式事務(wù)是什么。

          事務(wù)

          事務(wù)的 ACID 想必大家都熟知,這其實是嚴(yán)格意義上的定義,指的是事務(wù)的實現(xiàn)必須具備原子性、一致性、隔離性和持久性。

          不過嚴(yán)格意義上的事務(wù)很難達(dá)到,像我們熟知的數(shù)據(jù)庫就有各種隔離級別,隔離級別越高性能越低,所以往往我們都會從中找到屬于自己的平衡,不會遵循嚴(yán)格意義上的事務(wù)。

          并且在我們平日的談?wù)撝校^的事務(wù)往往簡單的指代一系列的操作全部執(zhí)行成功,或者全部失敗,不會出現(xiàn)一些成功一些失敗的情形。

          清晰了平日我們對事務(wù)的定義之后,再來看看什么是分布式事務(wù)。

          分布式事務(wù)

          由于互聯(lián)網(wǎng)的快速發(fā)展,以往的單體架構(gòu)頂不住這么多的需求,這么復(fù)雜的業(yè)務(wù),這么大的流量。

          單體架構(gòu)的優(yōu)勢在于前期快速搭建、快速上線,并且方法和模塊之間都是內(nèi)部調(diào)用,沒有網(wǎng)絡(luò)的開銷更加的高效。

          從某方面來說部署也方便,畢竟就一個包,扔上去。

          不過隨著企業(yè)的發(fā)展,業(yè)務(wù)的復(fù)雜度越來越高,內(nèi)部耦合極其嚴(yán)重,導(dǎo)致牽一發(fā)而動全身,開發(fā)不易,測試不易。

          并且無法根據(jù)熱點服務(wù)進(jìn)行動態(tài)的伸縮,比如商品服務(wù)訪問量特別大,如果是單體架構(gòu)的話我們只能把整個應(yīng)用復(fù)制多份集群部署,浪費資源。

          因此拆分勢在必行,微服務(wù)架構(gòu)就這么來了。

          拆分之后服務(wù)之間的邊界就清晰了,每個服務(wù)都能獨立地運行,獨立地部署,所以能以服務(wù)級別彈性伸縮了。

          服務(wù)之間的本地調(diào)用變成了遠(yuǎn)程調(diào)用,鏈路更長了,一次調(diào)用的耗時更長了,但是總體的吞吐量更大了。

          不過拆分之后還會引入其他復(fù)雜度,比如服務(wù)鏈路的監(jiān)控、整體的監(jiān)控、容錯措施、彈性伸縮等等運維監(jiān)控的問題,還有像分布式事務(wù)、分布式鎖跟業(yè)務(wù)息息相關(guān)的問題等。

          往往解決了一個痛點又會引入別的痛點,所以架構(gòu)的演進(jìn)都是權(quán)衡的結(jié)果,就看你們的系統(tǒng)更能忍受哪種痛點了。

          而今天我們談及的就是分布式事務(wù)這個痛點。

          分布式事務(wù)是由多個本地事務(wù)組成的,分布式事務(wù)跨越了多設(shè)備,之間又經(jīng)歷的復(fù)雜的網(wǎng)絡(luò),可想而知想要實現(xiàn)嚴(yán)格的事務(wù)道路阻且長。

          單機版事務(wù)都不會嚴(yán)格遵守事務(wù)的嚴(yán)格實現(xiàn),更別說分布式事務(wù)了,所以在現(xiàn)實情況下我們只能實現(xiàn)殘缺版的事務(wù)。

          在明確了事務(wù)和分布式事務(wù)之后,我們就先來看看常見的分布式事務(wù)方案:2PC、3PC、TCC、本地消息、事務(wù)消息。

          2PC

          2PC,Two-phase commit protocol,即兩階段提交協(xié)議。它引入了一個事務(wù)協(xié)調(diào)者角色,來管理各個參與者(就是各數(shù)據(jù)庫資源)。

          整體分為兩個階段,分別是準(zhǔn)備階段和提交/回滾階段。

          我們先來看看第一個階段,即準(zhǔn)備階段。

          由事務(wù)協(xié)調(diào)者給每個參與者發(fā)送準(zhǔn)備命令,每個參與者收到命令之后會執(zhí)行相關(guān)事務(wù)操作,你可以認(rèn)為除了事務(wù)的提交啥都做了。

          然后每個參與者會返回響應(yīng)告知協(xié)調(diào)者自己是否準(zhǔn)備成功。

          協(xié)調(diào)者收到每個參與者的響應(yīng)之后就進(jìn)入第二階段,根據(jù)收集的響應(yīng),如果有一個參與者響應(yīng)準(zhǔn)備失敗那么就向所有參與者發(fā)送回滾命令,反之發(fā)送提交命令。

          這個協(xié)議其實很符合正常的思維,就像我們大學(xué)上課點名的時候,其實老師就是協(xié)調(diào)者的角色,我們都是參與者。

          老師一個一個的點名,我們一個一個的喊到,最后老師收到所有同學(xué)的到之后就開始了今天的講課。

          而和點名有所不同的是,老師發(fā)現(xiàn)某幾個學(xué)生不在還是能繼續(xù)上課,而我們的事務(wù)可不允許這樣。

          事務(wù)協(xié)調(diào)者在第一階段未收到個別參與者的響應(yīng),則等待一定時間就會認(rèn)為事務(wù)失敗,會發(fā)送回滾命令,所以在 2PC 中事務(wù)協(xié)調(diào)者有超時機制。

          我們再來分析一下 2PC 的優(yōu)缺點。

          2PC 的優(yōu)點是能利用數(shù)據(jù)庫自身的功能進(jìn)行本地事務(wù)的提交和回滾,也就是說提交和回滾實際操作不需要我們實現(xiàn),不侵入業(yè)務(wù)邏輯由數(shù)據(jù)庫完成,在之后講解 TCC 之后相信大家對這點會有所體會。

          2PC 主要有三大缺點:同步阻塞、單點故障和數(shù)據(jù)不一致問題。

          同步阻塞

          可以看到在第一階段執(zhí)行了準(zhǔn)備命令后,我們每個本地資源都處于鎖定狀態(tài),因為除了事務(wù)的提交之外啥都做了。

          所以這時候如果本地的其他請求要訪問同一個資源,比如要修改商品表 id 等于 100 的那條數(shù)據(jù),那么此時是被阻塞住的,必須等待前面事務(wù)的完結(jié),收到提交/回滾命令執(zhí)行完釋放資源后,這個請求才能得以繼續(xù)。

          所以假設(shè)這個分布式事務(wù)涉及到很多參與者,然后有些參與者處理又特別復(fù)雜,特別慢,那么那些處理快的節(jié)點也得等著,所以說效率有點低。

          單點故障

          可以看到這個單點就是協(xié)調(diào)者,如果協(xié)調(diào)者掛了整個事務(wù)就執(zhí)行不下去了

          如果協(xié)調(diào)者在發(fā)送準(zhǔn)備命令前掛了還行,畢竟每個資源都還未執(zhí)行命令,那么資源是沒被鎖定的。

          可怕的是在發(fā)送完準(zhǔn)備命令之后掛了,這時候每個本地資源都執(zhí)行完處于鎖定狀態(tài)了,都杵著了,這就很僵硬了,如果是某個熱點資源都阻塞了,這估計就要GG了。

          數(shù)據(jù)不一致問題

          因為協(xié)調(diào)者和參與者之間的交流是經(jīng)過網(wǎng)絡(luò)的,而網(wǎng)絡(luò)有時候就會抽風(fēng)的或者發(fā)生局部網(wǎng)絡(luò)異常。

          那么就有可能導(dǎo)致某些參與者無法收到協(xié)調(diào)者的請求,而某些收到了。比如是提交請求,然后那些收到命令的參與者就提交事務(wù)了,此時就產(chǎn)生了數(shù)據(jù)不一致的問題。

          小結(jié)一下 2PC

          至此我們來先小結(jié)一些 2PC ,它是一個同步阻塞的強一致性兩階段提交協(xié)議,分別是準(zhǔn)備階段和提交/回滾階段。

          2PC 的優(yōu)勢在于對業(yè)務(wù)沒有侵入,可以利用數(shù)據(jù)庫自身機制來進(jìn)行事務(wù)的提交和回滾。

          它的缺點:是一個同步阻塞協(xié)議,會導(dǎo)致高延遲和性能的下降,并且存在協(xié)調(diào)者單點故障問題,極端情況下會有數(shù)據(jù)不一致的問題。

          當(dāng)然這只是協(xié)議,具體的落地還是可以變通了,比如協(xié)調(diào)者單點問題,我就搞個主從來實現(xiàn)協(xié)調(diào)者,對吧。

          分布式數(shù)據(jù)庫的 2PC 改進(jìn)模型

          可能有些人對分布式數(shù)據(jù)庫不熟悉,沒有關(guān)系,我們主要學(xué)的是思想,看看人家的思路。

          我簡單的講下 Percolator 模型,它是基于分布式存儲系統(tǒng) BigTable 建立的模型,BigTable 是啥也不清楚的同學(xué)沒有關(guān)系影響不大。

          還是拿轉(zhuǎn)賬的例子來說,我現(xiàn)在有 200 塊錢,你現(xiàn)在有 100 塊錢,為了突出重點我也不按正常的結(jié)構(gòu)來畫這個表。

          然后我要轉(zhuǎn) 100 塊給你。

          此時事務(wù)管理器發(fā)起了準(zhǔn)備請求,然后我賬上的錢就少了,你賬上的錢就多了,而且事務(wù)管理器還記錄下這次操作的日志。

          此時的數(shù)據(jù)還是私有版本,別的事務(wù)是讀不到的,簡單的理解 Lock 上有值就還是私有的。

          可以看到我的記錄 Lock 標(biāo)記的是 PK,你的記錄標(biāo)記的是指向我的記錄指針,這個 PK 是隨機選擇的。

          然后事務(wù)管理器會向被選擇作為 PK 的那條記錄發(fā)起提交指令。

          此時就會把我的記錄的鎖給抹去了,這等于我的記錄不再是私有版本了,別的事務(wù)就都能訪問了。

          那你的記錄上還有鎖???不用更新嗎?

          嘿嘿不需要及時更新,因為訪問你的這條記錄的時候會去根據(jù)指針找我的那個記錄,發(fā)現(xiàn)記錄已經(jīng)提交了所以你的記錄就可以被訪問了。

          有人說這效率不就差了,每次都要去找一次,別急。

          后臺會有個線程來掃描,然后更新把鎖記錄給去了。

          這不就穩(wěn)了嘛。

          相比于 2PC 的改進(jìn)

          首先 Percolator 在提交階段不需要和所有的參與者交互,主需要和一個參與者打交道,所以這個提交是原子的!解決了數(shù)據(jù)不一致問題

          然后事務(wù)管理器會記錄操作日志,這樣當(dāng)事務(wù)管理器掛了之后選舉的新事務(wù)管理器就可以通過日志來得知當(dāng)前的情況從而繼續(xù)工作,解決了單點故障問題。

          并且 Percolator 還會有后臺線程,會掃描事務(wù)狀況,在事務(wù)管理器宕機之后會回滾各個參與者上的事務(wù)。

          可以看到相對于 2PC 還是做了很多改進(jìn)的,也是巧妙的。

          其實分布式數(shù)據(jù)庫還有別的事務(wù)模型,不過我也不太熟悉,就不多嗶嗶了,有興趣的同學(xué)可以自行了解。

          還是挺能拓寬思想的。

          XA 規(guī)范

          讓我們再回來 2PC,既然說到 2PC 了那么也簡單的提一下 XA 規(guī)范,XA 規(guī)范是基于兩階段提交的,它實現(xiàn)了兩階段提交協(xié)議。

          在說 XA 規(guī)范之前又得先提一下 DTP 模型,即 Distributed Transaction Processing,這模型規(guī)范了分布式事務(wù)的模型設(shè)計。

          而 XA 規(guī)范又約束了 DTP 模型中的事務(wù)管理器(TM) 和資源管理器(RM)之間的交互,簡單的說就是你們兩之間要按照一定的格式規(guī)范來交流!

          我們先來看下 XA 約束下的 DTP 模型。

          • AP 應(yīng)用程序,就是我們的應(yīng)用,事務(wù)的發(fā)起者。
          • RM 資源管理器,簡單的認(rèn)為就是數(shù)據(jù)庫,具備事務(wù)提交和回滾能力,對應(yīng)我們上面的 2PC 就是參與者。
          • TM 事務(wù)管理器,就是協(xié)調(diào)者了,和每個 RM 通信。

          簡單的說就是 AP 通過 TM 來定義事務(wù)操作,TM 和 RM 之間會通過 XA 規(guī)范進(jìn)行通信,執(zhí)行兩階段提交,而 AP 的資源是從 RM 拿的。

          從模型上看有三個角色,而實際實現(xiàn)可以由一個角色實現(xiàn)兩個功能,比如 AP 來實現(xiàn) TM 的功能,TM 沒必要抽出來單獨部署。

          MySQL XA

          知曉了 DTP 之后,我們就來看看 XA 在 MySQL 中是如何操作的,不過只有 InnoDB 支持。

          簡單的說就是要先定義一個全局唯一的 XID,然后告知每個事務(wù)分支要進(jìn)行的操作。

          可以看到圖中執(zhí)行了兩個操作,分別是改名字和插入日志,等于先注冊下要做的事情,通過 XA START XID 和 XA END XID 來包裹要執(zhí)行的 SQL。

          然后需要發(fā)送準(zhǔn)備命令,來執(zhí)行第一階段,也就是除了事務(wù)的提交啥都干了的階段。

          然后根據(jù)準(zhǔn)備的情況來選擇執(zhí)行提交事務(wù)命令還是回滾事務(wù)命令。

          基本上就是這么個流程,不過 MySQL XA 的性能不高這點是需要注意的。

          可以看到雖說 2PC 有缺點,但是還是有基于 2PC 的落地實現(xiàn)的,而 3PC 的引出是為了解決 2PC 的一些缺點,但是它整體下來開銷更大,也解決不了網(wǎng)絡(luò)分區(qū)的問題,我也沒有找到 3PC 的落地實現(xiàn)。

          不過我還是稍微提一下,知曉一下就行,純理論。

          3PC

          3PC 的引入是為了解決 2PC 同步阻塞和減少數(shù)據(jù)不一致的情況。

          3PC 也就是多了一個階段,一個詢問的階段,分別是準(zhǔn)備、預(yù)提交和提交這三個階段。

          準(zhǔn)備階段單純就是協(xié)調(diào)者去訪問參與者,類似于你還好嗎?能接請求不。

          預(yù)提交其實就是 2PC 的準(zhǔn)備階段,除了事務(wù)的提交啥都干了。

          提交階段和 2PC 的提交一致。

          3PC 多了一個階段其實就是在執(zhí)行事務(wù)之前來確認(rèn)參與者是否正常,防止個別參與者不正常的情況下,其他參與者都執(zhí)行了事務(wù),鎖定資源。

          出發(fā)點是好的,但是絕大部分情況下肯定是正常的,所以每次都多了一個交互階段就很不劃算。

          然后 3PC 在參與者處也引入了超時機制,這樣在協(xié)調(diào)者掛了的情況下,如果已經(jīng)到了提交階段了,參與者等半天沒收到協(xié)調(diào)者的情況的話就會自動提交事務(wù)。

          不過萬一協(xié)調(diào)者發(fā)的是回滾命令呢?你看這就出錯了,數(shù)據(jù)不一致了。

          還有維基百科上說 2PC 參與者準(zhǔn)備階段之后,如果協(xié)調(diào)者掛了,參與者是無法得知整體的情況的,因為大局是協(xié)調(diào)者掌控的,所以參與者相互之間的狀況它們不清楚。

          而 3PC 經(jīng)過了第一階段的確認(rèn),即使協(xié)調(diào)者掛了參與者也知道自己所處預(yù)提交階段是因為已經(jīng)得到準(zhǔn)備階段所有參與者的認(rèn)可了。

          簡單的說就像加了個圍欄,使得各參與者的狀態(tài)得以統(tǒng)一。

          小結(jié) 2PC 和 3PC

          從上面已經(jīng)知曉了 2PC 是一個強一致性的同步阻塞協(xié)議,性能已經(jīng)是比較差的了。

          而 3PC 的出發(fā)點是為了解決 2PC 的缺點,但是多了一個階段就多了一次通訊的開銷,而且是絕大部分情況下無用的通訊。

          雖說引入?yún)⑴c者超時來解決協(xié)調(diào)者掛了的阻塞問題,但是數(shù)據(jù)還是會不一致。

          可以看到 3PC 的引入并沒什么實際突破,而且性能更差了,所以實際只有 2PC 的落地實現(xiàn)。

          再提一下,2PC 還是 3PC 都是協(xié)議,可以認(rèn)為是一種指導(dǎo)思想,和真正的落地還是有差別的。

          TCC

          不知道大家注意到?jīng)],不管是 2PC 還是 3PC 都是依賴于數(shù)據(jù)庫的事務(wù)提交和回滾。

          而有時候一些業(yè)務(wù)它不僅僅涉及到數(shù)據(jù)庫,可能是發(fā)送一條短信,也可能是上傳一張圖片。

          所以說事務(wù)的提交和回滾就得提升到業(yè)務(wù)層面而不是數(shù)據(jù)庫層面了,而 TCC 就是一種業(yè)務(wù)層面或者是應(yīng)用層的兩階段提交。

          TCC 分為指代 Try、Confirm、Cancel ,也就是業(yè)務(wù)層面需要寫對應(yīng)的三個方法,主要用于跨數(shù)據(jù)庫、跨服務(wù)的業(yè)務(wù)操作的數(shù)據(jù)一致性問題。

          TCC 分為兩個階段,第一階段是資源檢查預(yù)留階段即 Try,第二階段是提交或回滾,如果是提交的話就是執(zhí)行真正的業(yè)務(wù)操作,如果是回滾則是執(zhí)行預(yù)留資源的取消,恢復(fù)初始狀態(tài)。

          比如有一個扣款服務(wù),我需要寫 Try 方法,用來凍結(jié)扣款資金,還需要一個 Confirm 方法來執(zhí)行真正的扣款,最后還需要提供 Cancel 來進(jìn)行凍結(jié)操作的回滾,對應(yīng)的一個事務(wù)的所有服務(wù)都需要提供這三個方法。

          可以看到本來就一個方法,現(xiàn)在需要膨脹成三個方法,所以說 TCC 對業(yè)務(wù)有很大的侵入,像如果沒有凍結(jié)的那個字段,還需要改表結(jié)構(gòu)。

          我們來看下流程。

          雖說對業(yè)務(wù)有侵入,但是 TCC 沒有資源的阻塞,每一個方法都是直接提交事務(wù)的,如果出錯是通過業(yè)務(wù)層面的 Cancel 來進(jìn)行補償,所以也稱補償性事務(wù)方法。

          這里有人說那要是所有人 Try 都成功了,都執(zhí)行 Comfirm 了,但是個別 Confirm 失敗了怎么辦?

          這時候只能是不停地重試調(diào)失敗了的 Confirm 直到成功為止,如果真的不行只能記錄下來,到時候人工介入了。

          TCC 的注意點

          這幾個點很關(guān)鍵,在實現(xiàn)的時候一定得注意了。

          冪等問題,因為網(wǎng)絡(luò)調(diào)用無法保證請求一定能到達(dá),所以都會有重調(diào)機制,因此對于 Try、Confirm、Cancel 三個方法都需要冪等實現(xiàn),避免重復(fù)執(zhí)行產(chǎn)生錯誤。

          空回滾問題,指的是 Try 方法由于網(wǎng)絡(luò)問題沒收到超時了,此時事務(wù)管理器就會發(fā)出 Cancel 命令,那么需要支持 Cancel ?在未執(zhí)行 Try 的情況下能正常的 Cancel。

          懸掛問題,這個問題也是指 Try 方法由于網(wǎng)絡(luò)阻塞超時觸發(fā)了事務(wù)管理器發(fā)出了 Cancel 命令,但是執(zhí)行了 Cancel 命令之后 Try 請求到了,你說氣不氣。

          這都 Cancel 了你來個 Try,對于事務(wù)管理器來說這時候事務(wù)已經(jīng)是結(jié)束了的,這凍結(jié)操作就被“懸掛”了,所以空回滾之后還得記錄一下,防止 Try 的再調(diào)用。

          TCC 變體

          上面我們說的是通用型的 TCC,它需要改造以前的實現(xiàn),但是有一種情況是無法改造的,就是你調(diào)用的是別的公司的接口

          沒有 Try 的 TCC

          比如坐飛機需要換乘,換乘的又是不同的航空公司,比如從 A 飛到 B,再從 B 飛到 C,只有 A - B 和 B - C 都買到票了才有意義。

          這時候的選擇就沒得 Try 了,直接調(diào)用航空公司的買票操作,當(dāng)兩個航空公司都買成功了那就直接成功了,如果某個公司買失敗了,那就需要調(diào)用取消訂票接口。

          也就是在第一階段直接就執(zhí)行完整個業(yè)務(wù)操作了,所以要重點關(guān)注回滾操作,如果回滾失敗得有提醒,要人工介入等。

          這其實就是 TCC 的思想。

          異步 TCC

          這 TCC 還能異步?其實也是一種折中,比如某些服務(wù)很難改造,并且它又不會影響主業(yè)務(wù)決策,也就是它不那么重要,不需要及時的執(zhí)行。

          這時候可以引入可靠消息服務(wù),通過消息服務(wù)來替代個別服務(wù)來進(jìn)行 Try、Confirm、Cancel 。

          Try 的時候只是寫入消息,消息還不能被消費,Confirm 就是真正發(fā)消息的操作,Cancel 就是取消消息的發(fā)送。

          這可靠消息服務(wù)其實就類似于等下要提到的事務(wù)消息,這個方案等于糅合了事務(wù)消息和 TCC。

          TCC 小結(jié)

          可以看到 TCC 是通過業(yè)務(wù)代碼來實現(xiàn)事務(wù)的提交和回滾,對業(yè)務(wù)的侵入較大,它是業(yè)務(wù)層面的兩階段提交,

          它的性能比 2PC 要高,因為不會有資源的阻塞,并且適用范圍也大于 2PC,在實現(xiàn)上要注意上面提到的幾個注意點。

          它是業(yè)界比較常用的分布式事務(wù)實現(xiàn)方式,而且從變體也可以得知,還是得看業(yè)務(wù)變通的,不是說你要用 TCC 一定就得死板的讓所有的服務(wù)都改造成那三個方法。

          本地消息表

          本地消息就是利用了本地事務(wù),會在數(shù)據(jù)庫中存放一直本地事務(wù)消息表,在進(jìn)行本地事務(wù)操作中加入了本地消息的插入,即將業(yè)務(wù)的執(zhí)行和將消息放入消息表中的操作放在同一個事務(wù)中提交

          這樣本地事務(wù)執(zhí)行成功的話,消息肯定也插入成功,然后再調(diào)用其他服務(wù),如果調(diào)用成功就修改這條本地消息的狀態(tài)。

          如果失敗也不要緊,會有一個后臺線程掃描,發(fā)現(xiàn)這些狀態(tài)的消息,會一直調(diào)用相應(yīng)的服務(wù),一般會設(shè)置重試的次數(shù),如果一直不行則特殊記錄,待人工介入處理。

          可以看到還是很簡單的,也是一種最大努力通知思想。

          事務(wù)消息

          這個其實我寫過一篇文章,專門講事務(wù)消息,從源碼層面剖析了 RocketMQ 、Kafka 的事務(wù)消息實現(xiàn),以及兩者之間的區(qū)別。

          在這里我不再詳細(xì)闡述,因為之前的文章寫的很詳細(xì)了,大概四五千字吧。我就附上鏈接了:事務(wù)消息

          Seata 的實現(xiàn)

          首先什么是 Seata ,摘抄官網(wǎng)的一段話。

          Seata 是一款開源的分布式事務(wù)解決方案,致力于提供高性能和簡單易用的分布式事務(wù)服務(wù)。Seata 將為用戶提供了 AT、TCC、SAGA 和 XA 事務(wù)模式,為用戶打造一站式的分布式解決方案。

          可以看到提供了很多模式,我們先來看看 AT 模式。

          AT模式

          AT 模式就是兩階段提交,前面我們提到了兩階段提交有同步阻塞的問題,效率太低了,那 Seata 是怎么解決的呢?

          AT 的一階段直接就把事務(wù)提交了,直接釋放了本地鎖,這么草率直接提交的嘛?當(dāng)然不是,這里和本地消息表有點類似,就是利用本地事務(wù),執(zhí)行真正的事務(wù)操作中還會插入回滾日志,然后在一個事務(wù)中提交。

          這回滾日志怎么來的?

          通過框架代理 JDBC 的一些類,在執(zhí)行 SQL 的時候解析 SQL 得到執(zhí)行前的數(shù)據(jù)鏡像,然后執(zhí)行 SQL ,再得到執(zhí)行后的數(shù)據(jù)鏡像,然后把這些數(shù)據(jù)組裝成回滾日志。

          再伴隨的這個本地事務(wù)的提交把回滾日志也插入到數(shù)據(jù)庫的 UNDO_LOG 表中(所以數(shù)據(jù)庫需要有一張UNDO_LOG 表)。

          這波操作下來在一階段就可以沒有后顧之憂的提交事務(wù)了。

          然后一階段如果成功,那么二階段可以異步的刪除那些回滾日志,如果一階段失敗那么可以通過回滾日志來反向補償恢復(fù)。

          這時候有細(xì)心的同學(xué)想到了,萬一中間有人改了這條數(shù)據(jù)怎么辦?你這鏡像就不對了???

          所以說還有個全局鎖的概念,在事務(wù)提交前需要拿到全局鎖(可以理解為對這條數(shù)據(jù)的鎖),然后才能順利提交本地事務(wù)。

          如果一直拿不到那就需要回滾本地事務(wù)了。

          官網(wǎng)的示例很好,我就不自己編了,以下部分內(nèi)容摘抄自 Seata 官網(wǎng)的示例

          此時有兩個事務(wù),分別是 tx1、和 tx2,分別對 a 表的 m 字段進(jìn)行更新操作,m 的初始值 1000。

          tx1 先開始,開啟本地事務(wù),拿到本地鎖,更新操作 m = 1000 - 100 = 900。本地事務(wù)提交前,先拿到該記錄的 全局鎖 ,本地提交釋放本地鎖。

          tx2 后開始,開啟本地事務(wù),拿到本地鎖,更新操作 m = 900 - 100 = 800。本地事務(wù)提交前,嘗試拿該記錄的 全局鎖 ,tx1 全局提交前,該記錄的全局鎖被 tx1 持有,tx2 需要重試等待全局鎖 。

          可以看到 tx2 的修改被阻塞了,之后重試拿到全局鎖之后就能提交然后釋放本地鎖。

          如果 tx1 的二階段全局回滾,則 tx1 需要重新獲取該數(shù)據(jù)的本地鎖,進(jìn)行反向補償?shù)母虏僮?,實現(xiàn)分支的回滾。

          此時,如果 tx2 仍在等待該數(shù)據(jù)的全局鎖,同時持有本地鎖,則 tx1 的分支回滾會失敗。分支的回滾會一直重試,直到 tx2 的全局鎖等鎖超時,放棄全局鎖并回滾本地事務(wù)釋放本地鎖,tx1 的分支回滾最終成功。

          因為整個過程全局鎖在 tx1 結(jié)束前一直是被 tx1 持有的,所以不會發(fā)生臟寫的問題。

          然后 AT 模式默認(rèn)全局是讀未提交的隔離級別,如果應(yīng)用在特定場景下,必需要求全局的讀已提交 ,可以通過 SELECT FOR UPDATE 語句的代理。

          當(dāng)然前提是你本地事務(wù)隔離級別是讀已提交及以上。

          AT 模式小結(jié)

          可以看到通過代理來無侵入的得到數(shù)據(jù)的前后鏡像,組裝成回滾日志伴隨本地事務(wù)一起提交,解決了兩階段的同步阻塞問題。

          并且利用全局鎖來實現(xiàn)寫隔離。

          為了總體性能的考慮,默認(rèn)是讀未提交隔離級別,只代理了 SELECT FOR UPDATE 來進(jìn)行讀已提交的隔離。

          這其實就是兩階段提交的變體實現(xiàn)。

          TCC 模式

          沒什么花頭,就是咱們上面分析的需要搞三個方法, 然后把自定義的分支事務(wù)納入到全局事務(wù)的管理中

          我貼一張官網(wǎng)的圖應(yīng)該挺清晰了。

          Saga 模式

          這個 Saga 是 Seata 提供的長事務(wù)解決方案,適用于業(yè)務(wù)流程多且長的情況下,這種情況如果要實現(xiàn)一般的 TCC 啥的可能得嵌套多個事務(wù)了。

          并且有些系統(tǒng)無法提供 TCC 這三種接口,比如老項目或者別人公司的,所以就搞了個 Saga 模式,這個 Saga 是在 1987 年 Hector & Kenneth 發(fā)表的論?中提出的。

          那 Saga 如何做呢?來看下這個圖。

          假設(shè)有 N 個操作,直接從 T1 開始就是直接執(zhí)行提交事務(wù),然后再執(zhí)行 T2,可以看到就是無鎖的直接提交,到 T3 發(fā)現(xiàn)執(zhí)行失敗了,然后就進(jìn)入 Compenstaing 階段,開始一個一個倒回補償了。

          思想就是一開始蒙著頭干,別慫,出了問題咱們再一個一個改回去唄。

          可以看到這種情況是不保證事務(wù)的隔離性的,并且 Saga 也有 TCC 的一樣的注意點,需要空補償,防懸掛和冪等。

          而且極端情況下會因為數(shù)據(jù)被改變了導(dǎo)致無法回滾的情況。比如第一步給我打了 2 萬塊錢,我給取出來花了,這時候你回滾,我賬上余額已經(jīng) 0 了,你說怎么辦嘛?難道給我還搞負(fù)的不成?

          這種情況只能在業(yè)務(wù)流程上入手,我寫代碼其實一直是這樣寫的,就拿買皮膚的場景來說,我都是先扣錢再給皮膚。

          假設(shè)先給皮膚扣錢失敗了不就白給了嘛?這錢你來補???你覺得用戶會來反饋說皮膚給了錢沒扣嘛?

          可能有小機靈鬼說我到時候把皮膚給改回去,嘿嘿這種事情確實發(fā)生過,嘖嘖,被罵的真慘。

          所以正確的流程應(yīng)該是先扣錢再給皮膚,錢到自己袋里先,皮膚沒給成功用戶自然而然會找過來,這時候再給他唄,雖說可能你寫出了個 BUG ,但是還好不是個白給的 BUG。

          所以說這點在編碼的時候還是得注意下的。

          最后

          可以看到分布式事務(wù)還是會有各種問題,一般分布式事務(wù)的實現(xiàn)還是只能達(dá)到最終一致性。

          極端情況下還是得人工介入,所以做好日志記錄很關(guān)鍵。

          還有編碼的業(yè)務(wù)流程,要往利于公司的方向?qū)?,就例如先拿到用戶的錢,再給用戶東西這個方向,切記。

          在上分布式事務(wù)之前想想,有沒有必要,能不能改造一下避免分布式事務(wù)?

          再極端一點,你的業(yè)務(wù)有沒有必要上事務(wù)?


          原創(chuàng)電子書

          原創(chuàng)思維導(dǎo)圖

          掃碼或微信搜 Java3y?回復(fù)「888」領(lǐng)取1000+原創(chuàng)電子書和思維導(dǎo)圖。

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲成人视频网 | 欧美的中文字幕 | 天天插天天操天天干 | www.yiren99 | 日韩一级在线播放 |