<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>

          對比7種分布式事務(wù)方案,還是偏愛阿里開源的Seata,真香!

          共 21083字,需瀏覽 43分鐘

           ·

          2021-11-04 03:46

          點擊關(guān)注公眾號,Java干貨及時送達

          前言

          這篇文章主要介紹一些目前主流的幾種分布式解決方案以及阿里開源的一站式分布式解決方案Seata。

          文章有點長,耐心看完,看完你還不懂分布式事務(wù),歡迎來捶我......

          文章目錄如下:

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

          分布式對應(yīng)的是單體架構(gòu),互聯(lián)網(wǎng)早起單體架構(gòu)是非常流行的,好像是一個家族企業(yè),大家在一個家里勞作,單體架構(gòu)如下圖:

          但是隨著業(yè)務(wù)的復(fù)雜度提高,大家族人手不夠,此時不得不招人,這樣逐漸演變出了分布式服務(wù),互相協(xié)作,每個服務(wù)負責(zé)不同的業(yè)務(wù),架構(gòu)如下圖:

          分布式架構(gòu)

          因此需要服務(wù)與服務(wù)之間的遠程協(xié)作才能完成事務(wù),這種分布式系統(tǒng)環(huán)境下由不同的服務(wù)之間通過網(wǎng)絡(luò)遠程協(xié)作完成事務(wù)稱之為分布式事務(wù),例如用戶注冊送積分 事務(wù)、創(chuàng)建訂單減庫存事務(wù),銀行轉(zhuǎn)賬事務(wù)等都是分布式事務(wù)。

          典型的場景就是微服務(wù)架構(gòu) 微服務(wù)之間通過遠程調(diào)用完成事務(wù)操作。比如:訂單微服務(wù)庫存微服務(wù),下單的同時訂單微服務(wù)請求庫存微服務(wù)減庫存。簡言之:跨JVM進程產(chǎn)生分布式事務(wù)

          什么是CAP原則?

          CAP原則又叫CAP定理,同時又被稱作布魯爾定理(Brewer's theorem),指的是在一個分布式系統(tǒng)中,不可能同時滿足以下三點

          一致性(Consistency)

          指強一致性,在寫操作完成后開始的任何讀操作都必須返回該值,或者后續(xù)寫操作的結(jié)果。

          也就是說,在一致性系統(tǒng)中,一旦客戶端將值寫入任何一臺服務(wù)器并獲得響應(yīng),那么之后client從其他任何服務(wù)器讀取的都是剛寫入的數(shù)據(jù)

          一致性保證了不管向哪臺服務(wù)器寫入數(shù)據(jù),其他的服務(wù)器能實時同步數(shù)據(jù)

          可用性(Availability)

          可用性(高可用)是指:每次向未崩潰的節(jié)點發(fā)送請求,總能保證收到響應(yīng)數(shù)據(jù)(允許不是最新數(shù)據(jù))

          分區(qū)容忍性(Partition tolerance)

          分布式系統(tǒng)在遇到任何網(wǎng)絡(luò)分區(qū)故障的時候,仍然能夠?qū)ν馓峁M足一致性和可用性的服務(wù),也就是說,服務(wù)器AB發(fā)送給對方的任何消息都是可以放棄的,也就是說A和B可能因為各種意外情況,導(dǎo)致無法成功進行同步,分布式系統(tǒng)要能容忍這種情況。除非整個網(wǎng)絡(luò)環(huán)境都發(fā)生了故障。

          為什么只能在A和C之間做出取舍?

          分布式系統(tǒng)中,必須滿足 CAP 中的 P,此時只能在 C/A 之間作出取舍。

          如果選擇了CA,舍棄了P,說白了就是一個單體架構(gòu)。

          一致性有幾種分類?

          CAP理論告訴我們只能在C、A之間選擇,在分布式事務(wù)的最終解決方案中一般選擇犧牲一致性來獲取可用性和分區(qū)容錯性。

          這里的 “犧牲一致性” 并不是完全放棄數(shù)據(jù)的一致性,而是放棄強一致性而換取弱一致性

          一致性可以分為以下三種:

          • 強一致性
          • 弱一致性
          • 最終一致性

          強一致性

          系統(tǒng)中的某個數(shù)據(jù)被成功更新后,后續(xù)任何對該數(shù)據(jù)的讀取操作都將得到更新后的值。

          也稱為:原子一致性(Atomic Consistency)、線性一致性(Linearizable Consistency)

          簡言之,在任意時刻,所有節(jié)點中的數(shù)據(jù)是一樣的。例如,對于關(guān)系型數(shù)據(jù)庫,要求更新過的數(shù)據(jù)能被后續(xù)的訪問都能看到,這是強一致性。

          總結(jié)

          • 一個集群需要對外部提供強一致性,所以只要集群內(nèi)部某一臺服務(wù)器的數(shù)據(jù)發(fā)生了改變,那么就需要等待集群內(nèi)其他服務(wù)器的數(shù)據(jù)同步完成后,才能正常的對外提供服務(wù)。
          • 保證了強一致性,務(wù)必會損耗可用性

          弱一致性

          系統(tǒng)中的某個數(shù)據(jù)被更新后,后續(xù)對該數(shù)據(jù)的讀取操作可能得到更新后的值,也可能是更改前的值。

          但即使過了不一致時間窗口這段時間后,后續(xù)對該數(shù)據(jù)的讀取也不一定是最新值。

          所以說,可以理解為數(shù)據(jù)更新后,如果能容忍后續(xù)的訪問只能訪問到部分或者全部訪問不到,則是弱一致性。

          例如12306買火車票,雖然最后看到還剩下幾張余票,但是只要選擇購買就會提示沒票了,這就是弱一致性。

          最終一致性

          是弱一致性的特殊形式,存儲系統(tǒng)保證在沒有新的更新的條件下,最終所有的訪問都是最后更新的值。

          不保證在任意時刻任意節(jié)點上的同一份數(shù)據(jù)都是相同的,但是隨著時間的遷移,不同節(jié)點上的同一份數(shù)據(jù)總是在向趨同的方向變化。

          簡單說,就是在一段時間后,節(jié)點間的數(shù)據(jù)會最終達到一致狀態(tài)。

          總結(jié)

          弱一致性即使過了不一致時間窗口,后續(xù)的讀取也不一定能保證一致,而最終一致過了不一致窗口后,后續(xù)的讀取一定一致。

          什么是Base理論?

          BASE理論是對CAP中的一致性和可用性進行一個權(quán)衡的結(jié)果,理論的核心思想就是:我們無法做到強一致,但每個應(yīng)用都可以根據(jù)自身的業(yè)務(wù)特點,采用適當(dāng)?shù)姆绞絹硎瓜到y(tǒng)達到最終一致性。

          BA(Basic Available)基本可用

          整個系統(tǒng)在某些不可抗力的情況下,仍然能夠保證“可用性”,即一定時間內(nèi)仍然能夠返回一個明確的結(jié)果。這里是屬于基本可用。

          基本可用和高可用的區(qū)別:

          • “一定時間”可以適當(dāng)延長 當(dāng)舉行大促(比如秒殺)時,響應(yīng)時間可以適當(dāng)延長
          • 給部分用戶返回一個降級頁面 給部分用戶直接返回一個降級頁面,從而緩解服務(wù)器壓力。但要注意,返回降級頁面仍然是返回明確結(jié)果。

          S(Soft State)柔性狀態(tài)

          稱為柔性狀態(tài),是指允許系統(tǒng)中的數(shù)據(jù)存在中間狀態(tài),并認為該中間狀態(tài)的存在不會影響系統(tǒng)的整體可用性,即允許系統(tǒng)不同節(jié)點的數(shù)據(jù)副本之間進行數(shù)據(jù)同步的過程存在延時。

          E(Eventual Consisstency)最終一致性

          同一數(shù)據(jù)的不同副本的狀態(tài),可以不需要實時一致,但一定要保證經(jīng)過一定時間后仍然是一致的。

          分布式事務(wù)有哪幾種解決方案?

          在分布式架構(gòu)下,每個節(jié)點只知曉自己操作的失敗或者成功,無法得知其他節(jié)點的狀態(tài)。當(dāng)一個事務(wù)跨多個節(jié)點時,為了保持事務(wù)的原子性與一致性,而引入一個協(xié)調(diào)者來統(tǒng)一掌控所有參與者的操作結(jié)果,并指示它們是否要把操作結(jié)果進行真正的提交或者回滾(rollback)。

          2階段提交(2PC)

          二階段提交協(xié)議(Two-phase Commit,即 2PC)是常用的分布式事務(wù)解決方案,即將事務(wù)的提交過程分為兩個階段來進行處理。

          兩個階段分別為:

          • 準備階段
          • 提交階段

          參與的角色:

          • 事務(wù)協(xié)調(diào)者(事務(wù)管理器):事務(wù)的發(fā)起者
          • 事務(wù)參與者(資源管理器):事務(wù)的執(zhí)行者

          準備階段(投票階段)

          這是兩階段的第一段,這一階段只是準備階段,由事務(wù)的協(xié)調(diào)者發(fā)起詢問參與者是否可以提交事務(wù),但是這一階段并未提交事務(wù),流程圖如下圖:

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

          提交階段

          這一段階段屬于2PC的第二階段(提交 執(zhí)行階段),協(xié)調(diào)者發(fā)起正式提交事務(wù)的請求,當(dāng)所有參與者都回復(fù)同意時,則意味著完成事務(wù),流程圖如下:

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

          但是如果任意一個參與者節(jié)點在第一階段返回的消息為終止,或者協(xié)調(diào)者節(jié)點在第一階段的詢問超時之前無法獲取所有參與者節(jié)點的響應(yīng)消息時,那么這個事務(wù)將會被回滾,回滾的流程圖如下:

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

          不管最后結(jié)果如何,第二階段都會結(jié)束當(dāng)前事務(wù)。

          二階段提交的事務(wù)正常提交的完整流程如下圖:

          事務(wù)正常提交完整流程

          二階段提交事務(wù)回滾的完整流程如下圖:

          事務(wù)回滾完整流程

          舉個百米賽跑的例子來具體描述下2PC的流程:學(xué)校運動會,有三個同學(xué),分別是A,B,C,2PC流程如下:

          • 裁判:A同學(xué)準備好了嗎?準備進入第一賽道....
          • 裁判:B同學(xué)準備好了嗎?準備進入第一賽道....
          • 裁判:C同學(xué)準備好了嗎?準備進入第一賽道....
          • 如果有任意一個同學(xué)沒準備好,則裁判下達回滾指令
          • 如果裁判收到了所有同學(xué)的OK回復(fù),則再次下令跑......
          • 裁判:1,2,3 跑............
          • A同學(xué)沖刺到終點,匯報給裁判
          • B,C同學(xué)沖刺失敗,匯報給裁判

          2PC的缺點

          二階段提交看起來確實能夠提供原子性的操作,但是不幸的是,二階段提交還是有幾個缺點的:

          • 性能問題:執(zhí)行過程中,所有參與節(jié)點都是事務(wù)阻塞型的。當(dāng)參與者占有公共資源時,其他第三方節(jié)點訪問公共資源不得不處于阻塞狀態(tài)。
          • 可靠性問題:參與者發(fā)生故障。協(xié)調(diào)者需要給每個參與者額外指定超時機制,超時后整個事務(wù)失敗。協(xié)調(diào)者發(fā)生故障。參與者會一直阻塞下去。需要額外的備機進行容錯。
          • 數(shù)據(jù)一致性問題:二階段無法解決的問題:協(xié)調(diào)者在發(fā)出commit消息之后宕機,而唯一接收到這條消息的參與者同時也宕機了。那么即使協(xié)調(diào)者通過選舉協(xié)議產(chǎn)生了新的協(xié)調(diào)者,這條事務(wù)的狀態(tài)也是不確定的,沒人知道事務(wù)是否被已經(jīng)提交。
          • 實現(xiàn)復(fù)雜:犧牲了可用性,對性能影響較大,不適合高并發(fā)高性能場景。

          2PC的優(yōu)點

          • 盡量保證了數(shù)據(jù)的強一致,適合對數(shù)據(jù)強一致要求很高的關(guān)鍵領(lǐng)域。(其實也不能100%保證強一致)

          3階段提交(3PC)

          三階段提交協(xié)議,是二階段提交協(xié)議的改進版本,三階段提交有兩個改動點。

          • 在協(xié)調(diào)者和參與者中都引入超時機制
          • 在第一階段和第二階段中插入一個準備階段。保證了在最后提交階段之前各參與節(jié)點的狀態(tài)是一致的。

          也就是說,除了引入超時機制之外,3PC把2PC的準備階段再次一分為二,這樣三階段提交就有CanCommitPreCommitDoCommit三個階段。處理流程如下:

          3PC時序圖

          階段一:CanCommit階段

          3PC的CanCommit階段其實和2PC的準備階段很像。協(xié)調(diào)者向參與者發(fā)送commit請求,參與者如果可以提交就返回Yes響應(yīng),否則返回No響應(yīng)。

          • 事務(wù)詢問:協(xié)調(diào)者向所有參與者發(fā)出包含事務(wù)內(nèi)容的 canCommit 請求,詢問是否可以提交事務(wù),并等待所有參與者答復(fù)。
          • 響應(yīng)反饋:參與者收到 canCommit 請求后,如果認為可以執(zhí)行事務(wù)操作,則反饋 yes 并進入預(yù)備狀態(tài),否則反饋 no。

          CanCommit階段流程如下圖:

          CanCommit階段

          階段二:PreCommit階段

          協(xié)調(diào)者根據(jù)參與者的反應(yīng)情況來決定是否可以進行事務(wù)的PreCommit操作。根據(jù)響應(yīng)情況,有以下兩種可能。

          • 假如所有參與者均反饋 yes,協(xié)調(diào)者預(yù)執(zhí)行事務(wù)。
            1. 發(fā)送預(yù)提交請求 :協(xié)調(diào)者向參與者發(fā)送PreCommit請求,并進入準備階段
            2. 事務(wù)預(yù)提交 :參與者接收到PreCommit請求后,會執(zhí)行事務(wù)操作,并將undoredo信息記錄到事務(wù)日志中(但不提交事務(wù))
            3. 響應(yīng)反饋 :如果參與者成功的執(zhí)行了事務(wù)操作,則返回ACK響應(yīng),同時開始等待最終指令。
          PreCommit
          • 假如有任何一個參與者向協(xié)調(diào)者發(fā)送了No響應(yīng),或者等待超時之后,協(xié)調(diào)者都沒有接到參與者的響應(yīng),那么就執(zhí)行事務(wù)的中斷。
            1. 發(fā)送中斷請求 :協(xié)調(diào)者向所有參與者發(fā)送abort請求。
            2. 中斷事務(wù) :參與者收到來自協(xié)調(diào)者的abort請求之后(或超時之后,仍未收到協(xié)調(diào)者的請求),執(zhí)行事務(wù)的中斷。
          PreCommit

          階段三:doCommit階段

          該階段進行真正的事務(wù)提交,也可以分為以下兩種情況。

          進入階段 3 后,無論協(xié)調(diào)者出現(xiàn)問題,或者協(xié)調(diào)者與參與者網(wǎng)絡(luò)出現(xiàn)問題,都會導(dǎo)致參與者無法接收到協(xié)調(diào)者發(fā)出的 do Commit 請求或 abort 請求。此時,參與者都會在等待超時之后,繼續(xù)執(zhí)行事務(wù)提交。

          • 執(zhí)行提交
            1. 發(fā)送提交請求 協(xié)調(diào)接收到參與者發(fā)送的ACK響應(yīng),那么他將從預(yù)提交狀態(tài)進入到提交狀態(tài)。并向所有參與者發(fā)送doCommit請求。
            2. 事務(wù)提交 參與者接收到doCommit請求之后,執(zhí)行正式的事務(wù)提交。并在完成事務(wù)提交之后釋放所有事務(wù)資源。
            3. 響應(yīng)反饋 事務(wù)提交完之后,向協(xié)調(diào)者發(fā)送ack響應(yīng)。
            4. 完成事務(wù) 協(xié)調(diào)者接收到所有參與者的ack響應(yīng)之后,完成事務(wù)。
          docommit-提交事務(wù)
          • 中斷事務(wù):任何一個參與者反饋 no,或者等待超時后協(xié)調(diào)者尚無法收到所有參與者的反饋,即中斷事務(wù)
            1. 發(fā)送中斷請求 如果協(xié)調(diào)者處于工作狀態(tài),向所有參與者發(fā)出 abort 請求
            2. 事務(wù)回滾 參與者接收到abort請求之后,利用其在階段二記錄的undo信息來執(zhí)行事務(wù)的回滾操作,并在完成回滾之后釋放所有的事務(wù)資源。
            3. 反饋結(jié)果 參與者完成事務(wù)回滾之后,向協(xié)調(diào)者反饋ACK消息
            4. 中斷事務(wù) 協(xié)調(diào)者接收到參與者反饋的ACK消息之后,執(zhí)行事務(wù)的中斷。
          docommit-中斷事務(wù)

          優(yōu)點

          相比二階段提交,三階段提交降低了阻塞范圍,在等待超時后協(xié)調(diào)者或參與者會中斷事務(wù)。避免了協(xié)調(diào)者單點問題,階段 3 中協(xié)調(diào)者出現(xiàn)問題時,參與者會繼續(xù)提交事務(wù)。

          缺點

          數(shù)據(jù)不一致問題依然存在,當(dāng)在參與者收到 preCommit 請求后等待 doCommit 指令時,此時如果協(xié)調(diào)者請求中斷事務(wù),而協(xié)調(diào)者無法與參與者正常通信,會導(dǎo)致參與者繼續(xù)提交事務(wù),造成數(shù)據(jù)不一致。

          TCC(事務(wù)補償)

          TCC(Try Confirm Cancel)方案是一種應(yīng)用層面侵入業(yè)務(wù)的兩階段提交。是目前最火的一種柔性事務(wù)方案,其核心思想是:針對每個操作,都要注冊一個與其對應(yīng)的確認和補償(撤銷)操作

          TCC分為兩個階段,分別如下:

          • 第一階段:Try(嘗試),主要是對業(yè)務(wù)系統(tǒng)做檢測及資源預(yù)留 (加鎖,鎖住資源)
          • 第二階段:本階段根據(jù)第一階段的結(jié)果,決定是執(zhí)行confirm還是cancel
            1. Confirm(確認):執(zhí)行真正的業(yè)務(wù)(執(zhí)行業(yè)務(wù),釋放鎖)
            2. Cancle(取消):是預(yù)留資源的取消(出問題,釋放鎖)
          TCC

          為了方便理解,下面以電商下單為例進行方案解析,這里把整個過程簡單分為扣減庫存,訂單創(chuàng)建 2 個步驟,庫存服務(wù)和訂單服務(wù)分別在不同的服務(wù)器節(jié)點上。

          假設(shè)商品庫存為 100,購買數(shù)量為 2,這里檢查和更新庫存的同時,凍結(jié)用戶購買數(shù)量的庫存,同時創(chuàng)建訂單,訂單狀態(tài)為待確認。

          ①Try 階段

          TCC 機制中的 Try 僅是一個初步操作,它和后續(xù)的確認一起才能真正構(gòu)成一個完整的業(yè)務(wù)邏輯,這個階段主要完成:

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

          ②Confirm / Cancel 階段

          根據(jù) Try 階段服務(wù)是否全部正常執(zhí)行,繼續(xù)執(zhí)行確認操作(Confirm)或取消操作(Cancel)。

          Confirm 和 Cancel 操作滿足冪等性,如果 Confirm 或 Cancel 操作執(zhí)行失敗,將會不斷重試直到執(zhí)行完成。

          Confirm:當(dāng) Try 階段服務(wù)全部正常執(zhí)行, 執(zhí)行確認業(yè)務(wù)邏輯操作,業(yè)務(wù)如下圖:

          Try->Confirm

          這里使用的資源一定是 Try 階段預(yù)留的業(yè)務(wù)資源。在 TCC 事務(wù)機制中認為,如果在 Try 階段能正常的預(yù)留資源,那 Confirm 一定能完整正確的提交。

          Confirm 階段也可以看成是對 Try 階段的一個補充,Try+Confirm 一起組成了一個完整的業(yè)務(wù)邏輯。

          Cancel:當(dāng) Try 階段存在服務(wù)執(zhí)行失敗, 進入 Cancel 階段,業(yè)務(wù)如下圖:

          Try-Cancel

          Cancel 取消執(zhí)行,釋放 Try 階段預(yù)留的業(yè)務(wù)資源,上面的例子中,Cancel 操作會把凍結(jié)的庫存釋放,并更新訂單狀態(tài)為取消。

          最終一致性保證

          • TCC 事務(wù)機制以初步操作(Try)為中心的,確認操作(Confirm)和取消操作(Cancel)都是圍繞初步操作(Try)而展開。因此,Try 階段中的操作,其保障性是最好的,即使失敗,仍然有取消操作(Cancel)可以將其執(zhí)行結(jié)果撤銷。
          • Try階段執(zhí)行成功并開始執(zhí)行 Confirm階段時,默認 Confirm階段是不會出錯的。也就是說只要Try成功,Confirm一定成功(TCC設(shè)計之初的定義) 。
          • Confirm與Cancel如果失敗,由TCC框架進行==重試==補償
          • 存在極低概率在CC環(huán)節(jié)徹底失敗,則需要定時任務(wù)或人工介入

          方案總結(jié)

          TCC 事務(wù)機制相對于傳統(tǒng)事務(wù)機制(X/Open XA),TCC 事務(wù)機制相比于上面介紹的 XA 事務(wù)機制,有以下優(yōu)點:

          • 性能提升:具體業(yè)務(wù)來實現(xiàn)控制資源鎖的粒度變小,不會鎖定整個資源。
          • 數(shù)據(jù)最終一致性:基于 Confirm 和 Cancel 的冪等性,保證事務(wù)最終完成確認或者取消,保證數(shù)據(jù)的一致性。
          • 可靠性:解決了 XA 協(xié)議的協(xié)調(diào)者單點故障問題,由主業(yè)務(wù)方發(fā)起并控制整個業(yè)務(wù)活動,業(yè)務(wù)活動管理器也變成多點,引入集群。

          缺點:

          • TCC 的 Try、Confirm 和 Cancel 操作功能要按具體業(yè)務(wù)來實現(xiàn),業(yè)務(wù)耦合度較高,提高了開發(fā)成本。

          本地消息表

          本地消息表的方案最初是由 eBay 提出,核心思路是將分布式事務(wù)拆分成本地事務(wù)進行處理。

          角色:

          • 事務(wù)主動方
          • 事務(wù)被動方

          通過在事務(wù)主動發(fā)起方額外新建事務(wù)消息表,事務(wù)發(fā)起方處理業(yè)務(wù)和記錄事務(wù)消息在本地事務(wù)中完成,輪詢事務(wù)消息表的數(shù)據(jù)發(fā)送事務(wù)消息,事務(wù)被動方基于消息中間件消費事務(wù)消息表中的事務(wù)。

          這樣可以避免以下兩種情況導(dǎo)致的數(shù)據(jù)不一致性:

          • 業(yè)務(wù)處理成功、事務(wù)消息發(fā)送失敗
          • 業(yè)務(wù)處理失敗、事務(wù)消息發(fā)送成功

          整體的流程如下圖:

          本地消息表

          上圖中整體的處理步驟如下:

          • ①:事務(wù)主動方在同一個本地事務(wù)中處理業(yè)務(wù)和寫消息表操作
          • ②:事務(wù)主動方通過消息中間件,通知事務(wù)被動方處理事務(wù)通知事務(wù)待消息。消息中間件可以基于 Kafka、RocketMQ 消息隊列,事務(wù)主動方主動寫消息到消息隊列,事務(wù)消費方消費并處理消息隊列中的消息。
          • ③:事務(wù)被動方通過消息中間件,通知事務(wù)主動方事務(wù)已處理的消息。
          • ④:事務(wù)主動方接收中間件的消息,更新消息表的狀態(tài)為已處理。

          一些必要的容錯處理如下:

          • 當(dāng)①處理出錯,由于還在事務(wù)主動方的本地事務(wù)中,直接回滾即可
          • 當(dāng)②、③處理出錯,由于事務(wù)主動方本地保存了消息,只需要輪詢消息重新通過消息中間件發(fā)送,事務(wù)被動方重新讀取消息處理業(yè)務(wù)即可。
          • 如果是業(yè)務(wù)上處理失敗,事務(wù)被動方可以發(fā)消息給事務(wù)主動方回滾事務(wù)
          • 如果事務(wù)被動方已經(jīng)消費了消息,事務(wù)主動方需要回滾事務(wù)的話,需要發(fā)消息通知事務(wù)主動方進行回滾事務(wù)。

          優(yōu)點

          • 從應(yīng)用設(shè)計開發(fā)的角度實現(xiàn)了消息數(shù)據(jù)的可靠性,消息數(shù)據(jù)的可靠性不依賴于消息中間件,弱化了對 MQ 中間件特性的依賴。
          • 方案輕量,容易實現(xiàn)。

          缺點

          • 與具體的業(yè)務(wù)場景綁定,耦合性強,不可公用。
          • 消息數(shù)據(jù)與業(yè)務(wù)數(shù)據(jù)同庫,占用業(yè)務(wù)系統(tǒng)資源。
          • 業(yè)務(wù)系統(tǒng)在使用關(guān)系型數(shù)據(jù)庫的情況下,消息服務(wù)性能會受到關(guān)系型數(shù)據(jù)庫并發(fā)性能的局限。

          MQ事務(wù)方案(可靠消息事務(wù))

          基于 MQ 的分布式事務(wù)方案其實是對本地消息表的封裝,將本地消息表基于 MQ 內(nèi)部,其他方面的協(xié)議基本與本地消息表一致。

          MQ事務(wù)方案整體流程和本地消息表的流程很相似,如下圖:

          MQ事務(wù)方案

          從上圖可以看出和本地消息表方案唯一不同就是將本地消息表存在了MQ內(nèi)部,而不是業(yè)務(wù)數(shù)據(jù)庫中。

          那么MQ內(nèi)部的處理尤為重要,下面主要基于 RocketMQ 4.3 之后的版本介紹 MQ 的分布式事務(wù)方案。

          在本地消息表方案中,保證事務(wù)主動方發(fā)寫業(yè)務(wù)表數(shù)據(jù)和寫消息表數(shù)據(jù)的一致性是基于數(shù)據(jù)庫事務(wù),RocketMQ 的事務(wù)消息相對于普通 MQ提供了 2PC 的提交接口,方案如下:

          正常情況:事務(wù)主動方發(fā)消息

          事務(wù)主動方發(fā)消息

          這種情況下,事務(wù)主動方服務(wù)正常,沒有發(fā)生故障,發(fā)消息流程如下:

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

          異常情況:事務(wù)主動方消息恢復(fù)

          事務(wù)主動方消息恢復(fù)

          在斷網(wǎng)或者應(yīng)用重啟等異常情況下,圖中 4 提交的二次確認超時未到達 MQ Server,此時處理邏輯如下:

          • 步驟⑤:MQ Server 對該消息發(fā)起消息回查。
          • 步驟⑥:發(fā)送方收到消息回查后,需要檢查對應(yīng)消息的本地事務(wù)執(zhí)行的最終結(jié)果。
          • 步驟⑦:發(fā)送方根據(jù)檢查得到的本地事務(wù)的最終狀態(tài)再次提交二次確認。
          • 步驟⑧:MQ Server基于 commit/rollback 對消息進行投遞或者刪除。

          優(yōu)點

          相比本地消息表方案,MQ 事務(wù)方案優(yōu)點是:

          • 消息數(shù)據(jù)獨立存儲 ,降低業(yè)務(wù)系統(tǒng)與消息系統(tǒng)之間的耦合。
          • 吞吐量大于使用本地消息表方案。

          缺點

          • 一次消息發(fā)送需要兩次網(wǎng)絡(luò)請求(half 消息 + commit/rollback 消息) 。
          • 業(yè)務(wù)處理服務(wù)需要實現(xiàn)消息狀態(tài)回查接口。

          最大努力通知

          最大努力通知也稱為定期校對,是對MQ事務(wù)方案的進一步優(yōu)化。它在事務(wù)主動方增加了消息校對的接口,如果事務(wù)被動方?jīng)]有接收到消息,此時可以調(diào)用事務(wù)主動方提供的消息校對的接口主動獲取。

          最大努力通知的整體流程如下圖:

          最大努力通知

          在可靠消息事務(wù)中,事務(wù)主動方需要將消息發(fā)送出去,并且消息接收方成功接收,這種可靠性發(fā)送是由事務(wù)主動方保證的;

          但是最大努力通知,事務(wù)主動方盡最大努力(重試,輪詢....)將事務(wù)發(fā)送給事務(wù)接收方,但是仍然存在消息接收不到,此時需要事務(wù)被動方主動調(diào)用事務(wù)主動方的消息校對接口查詢業(yè)務(wù)消息并消費,這種通知的可靠性是由事務(wù)被動方保證的。

          最大努力通知適用于業(yè)務(wù)通知類型,例如微信交易的結(jié)果,就是通過最大努力通知方式通知各個商戶,既有回調(diào)通知,也有交易查詢接口。

          Saga 事務(wù)

          Saga 事務(wù)源于 1987 年普林斯頓大學(xué)的 Hecto 和 Kenneth 發(fā)表的如何處理 long lived transaction(長活事務(wù))論文。

          Saga 事務(wù)核心思想是將長事務(wù)拆分為多個本地短事務(wù),由 Saga 事務(wù)協(xié)調(diào)器協(xié)調(diào),如果正常結(jié)束那就正常完成,如果某個步驟失敗,則根據(jù)相反順序一次調(diào)用補償操作。

          Saga 事務(wù)基本協(xié)議如下:

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

          TCC事務(wù)補償機制有一個預(yù)留(Try)動作,相當(dāng)于先報存一個草稿,然后才提交;Saga事務(wù)沒有預(yù)留動作,直接提交。

          對于事務(wù)異常,Saga提供了兩種恢復(fù)策略,分別如下:

          向后恢復(fù)(backward recovery)

          在執(zhí)行事務(wù)失敗時,補償所有已完成的事務(wù),是“一退到底”的方式。如下圖:

          向后恢復(fù)

          從上圖可知事務(wù)執(zhí)行到了支付事務(wù)T3,但是失敗了,因此事務(wù)回滾需要從C3,C2,C1依次進行回滾補償。

          對應(yīng)的執(zhí)行順序為:T1,T2,T3,C3,C2,C1

          這種做法的效果是撤銷掉之前所有成功的子事務(wù),使得整個 Saga 的執(zhí)行結(jié)果撤銷。

          向前恢復(fù)(forward recovery)

          也稱之為:勇往直前,對于執(zhí)行不通過的事務(wù),會嘗試重試事務(wù),這里有一個假設(shè)就是每個子事務(wù)最終都會成功。

          流程如下圖:

          向前恢復(fù)

          適用于必須要成功的場景,事務(wù)失敗了重試,不需要補償。

          Saga事務(wù)有兩種不同的實現(xiàn)方式,分別如下:

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

          命令協(xié)調(diào)

          中央?yún)f(xié)調(diào)器(Orchestrator,簡稱 OSO)以命令/回復(fù)的方式與每項服務(wù)進行通信,全權(quán)負責(zé)告訴每個參與者該做什么以及什么時候該做什么。整體流程如下圖:

          命令協(xié)調(diào)

          上圖步驟如下:

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

          中央?yún)f(xié)調(diào)器必須事先知道執(zhí)行整個訂單事務(wù)所需的流程(例如通過讀取配置)。如果有任何失敗,它還負責(zé)通過向每個參與者發(fā)送命令來撤銷之前的操作來協(xié)調(diào)分布式的回滾。

          基于中央?yún)f(xié)調(diào)器協(xié)調(diào)一切時,回滾要容易得多,因為協(xié)調(diào)器默認是執(zhí)行正向流程,回滾時只要執(zhí)行反向流程即可。

          事件編排

          沒有中央?yún)f(xié)調(diào)器(沒有單點風(fēng)險)時,每個服務(wù)產(chǎn)生并觀察其他服務(wù)的事件,并決定是否應(yīng)采取行動。

          在事件編排方法中,第一個服務(wù)執(zhí)行一個事務(wù),然后發(fā)布一個事件。該事件被一個或多個服務(wù)進行監(jiān)聽,這些服務(wù)再執(zhí)行本地事務(wù)并發(fā)布(或不發(fā)布)新的事件。

          當(dāng)最后一個服務(wù)執(zhí)行本地事務(wù)并且不發(fā)布任何事件時,意味著分布式事務(wù)結(jié)束,或者它發(fā)布的事件沒有被任何 Saga 參與者聽到都意味著事務(wù)結(jié)束。

          事件編排

          上圖步驟如下:

          • 事務(wù)發(fā)起方的主業(yè)務(wù)邏輯發(fā)布開始訂單事件。
          • 庫存服務(wù)監(jiān)聽開始訂單事件,扣減庫存,并發(fā)布庫存已扣減事件。
          • 訂單服務(wù)監(jiān)聽庫存已扣減事件,創(chuàng)建訂單,并發(fā)布訂單已創(chuàng)建事件。
          • 支付服務(wù)監(jiān)聽訂單已創(chuàng)建事件,進行支付,并發(fā)布訂單已支付事件。
          • 主業(yè)務(wù)邏輯監(jiān)聽訂單已支付事件并處理。

          事件/編排是實現(xiàn) Saga 模式的自然方式,它很簡單,容易理解,不需要太多的代碼來構(gòu)建。如果事務(wù)涉及 2 至 4 個步驟,則可能是非常合適的。

          優(yōu)點

          命令協(xié)調(diào)設(shè)計的優(yōu)點如下:

          • 服務(wù)之間關(guān)系簡單,避免服務(wù)之間的循環(huán)依賴關(guān)系,因為 Saga 協(xié)調(diào)器會調(diào)用 Saga 參與者,但參與者不會調(diào)用協(xié)調(diào)器。
          • 程序開發(fā)簡單,只需要執(zhí)行命令/回復(fù)(其實回復(fù)消息也是一種事件消息),降低參與者的復(fù)雜性。
          • 易維護擴展,在添加新步驟時,事務(wù)復(fù)雜性保持線性,回滾更容易管理,更容易實施和測試。

          事件/編排設(shè)計優(yōu)點如下:

          • 避免中央?yún)f(xié)調(diào)器單點故障風(fēng)險。
          • 當(dāng)涉及的步驟較少服務(wù)開發(fā)簡單,容易實現(xiàn)。

          缺點

          命令協(xié)調(diào)設(shè)計缺點如下:

          • 中央?yún)f(xié)調(diào)器容易處理邏輯容易過于復(fù)雜,導(dǎo)致難以維護。
          • 存在協(xié)調(diào)器單點故障風(fēng)險。

          事件/編排設(shè)計缺點如下:

          • 服務(wù)之間存在循環(huán)依賴的風(fēng)險。
          • 當(dāng)涉及的步驟較多,服務(wù)間關(guān)系混亂,難以追蹤調(diào)測。

          由于 Saga 模型中沒有 Prepare 階段,因此事務(wù)間不能保證隔離性。

          當(dāng)多個 Saga 事務(wù)操作同一資源時,就會產(chǎn)生更新丟失、臟數(shù)據(jù)讀取等問題,這時需要在業(yè)務(wù)層控制并發(fā),例如:在應(yīng)用層面加鎖,或者應(yīng)用層面預(yù)先凍結(jié)資源。

          總結(jié)

          總結(jié)一下各個方案的常見的使用場景:

          • 2PC/3PC:依賴于數(shù)據(jù)庫,能夠很好的提供強一致性和強事務(wù)性,但相對來說延遲比較高,比較適合傳統(tǒng)的單體應(yīng)用,在同一個方法中存在跨庫操作的情況,不適合高并發(fā)和高性能要求的場景。
          • TCC:適用于執(zhí)行時間確定且較短,實時性要求高,對數(shù)據(jù)一致性要求高,比如互聯(lián)網(wǎng)金融企業(yè)最核心的三個服務(wù):交易、支付、賬務(wù)。
          • 本地消息表/MQ 事務(wù):都適用于事務(wù)中參與方支持操作冪等,對一致性要求不高,業(yè)務(wù)上能容忍數(shù)據(jù)不一致到一個人工檢查周期,事務(wù)涉及的參與方、參與環(huán)節(jié)較少,業(yè)務(wù)上有對賬/校驗系統(tǒng)兜底。
          • Saga 事務(wù):由于 Saga 事務(wù)不能保證隔離性,需要在業(yè)務(wù)層控制并發(fā),適合于業(yè)務(wù)場景事務(wù)并發(fā)操作同一資源較少的情況。Saga 相比缺少預(yù)提交動作,導(dǎo)致補償動作的實現(xiàn)比較麻煩,例如業(yè)務(wù)是發(fā)送短信,補償動作則得再發(fā)送一次短信說明撤銷,用戶體驗比較差。Saga 事務(wù)較適用于補償動作容易處理的場景。

          什么是Seata?

          上面講了這么多的分布式事務(wù)的理論知識,都沒看到一個落地的實現(xiàn),這不是吹牛逼嗎?

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

          • 對業(yè)務(wù)無侵入:即減少技術(shù)架構(gòu)上的微服務(wù)化所帶來的分布式事務(wù)問題對業(yè)務(wù)的侵入
          • 高性能:減少分布式事務(wù)解決方案所帶來的性能消耗

          官方文檔:https://seata.io/zh-cn/index.html

          seata的幾種術(shù)語:

          • TC(Transaction Coordinator):事務(wù)協(xié)調(diào)者。管理全局的分支事務(wù)的狀態(tài),用于全局性事務(wù)的提交和回滾。
          • TM(Transaction Manager):事務(wù)管理者。用于開啟、提交或回滾事務(wù)。
          • RM(Resource Manager):資源管理器。用于分支事務(wù)上的資源管理,向 TC 注冊分支事務(wù),上報分支事務(wù)的狀態(tài),接收 TC 的命令來提交或者回滾分支事務(wù)。

          AT模式

          seata目前支持多種事務(wù)模式,分別有ATTCCSAGAXA ,文章篇幅有限,今天只講常用的AT模式。

          AT模式的特點就是對業(yè)務(wù)無入侵式,整體機制分二階段提交(2PC)

          • 一階段:業(yè)務(wù)數(shù)據(jù)和回滾日志記錄在同一個本地事務(wù)中提交,釋放本地鎖和連接資源。
          • 二階段:
            1. 提交異步化,非常快速地完成
            2. 回滾通過一階段的回滾日志進行反向補償。

          在 AT 模式下,用戶只需關(guān)注自己的業(yè)務(wù)SQL,用戶的業(yè)務(wù)SQL 作為一階段,Seata 框架會自動生成事務(wù)的二階段提交和回滾操作。

          一個典型的分布式事務(wù)過程:

          • TM 向 TC 申請開啟一個全局事務(wù),全局事務(wù)創(chuàng)建成功并生成一個全局唯一的 XID;
          • XID 在微服務(wù)調(diào)用鏈路的上下文中傳播;
          • RM 向 TC 注冊分支事務(wù),將其納入 XID 對應(yīng)全局事務(wù)的管轄;
          • TM 向 TC 發(fā)起針對 XID 的全局提交或回滾決議;
          • TC 調(diào)度 XID 下管轄的全部分支事務(wù)完成提交或回滾請求。

          搭建Seata TC協(xié)調(diào)者

          seata的協(xié)調(diào)者其實就是阿里開源的一個服務(wù),我們只需要下載并且啟動它。

          下載地址:http://seata.io/zh-cn/blog/download.html

          陳某下載的版本是1.3.0,各位最好和我版本一致,這樣不會出現(xiàn)莫名的BUG。

          下載完成后,直接解壓即可。但是此時還不能直接運行,還需要做一些配置。

          創(chuàng)建TC所需要的表

          TC運行需要將事務(wù)的信息保存在數(shù)據(jù)庫,因此需要創(chuàng)建一些表,找到seata-1.3.0源碼的script\server\db這個目錄,將會看到以下SQL文件:

          陳某使用的是Mysql數(shù)據(jù)庫,因此直接運行mysql.sql這個文件中的sql語句,創(chuàng)建的三張表如下圖:

          修改TC的注冊中心

          找到seata-server-1.3.0\seata\conf這個目錄,其中有一個registry.conf文件,其中配置了TC的注冊中心和配置中心。

          默認的注冊中心是file形式,實際使用中肯定不能使用,需要改成Nacos形式,改動的地方如下圖:

          需要改動的地方如下:

          • type:改成nacos,表示使用nacos作為注冊中心
          • application:服務(wù)的名稱
          • serverAddr:nacos的地址
          • group:分組
          • namespace:命名空間
          • username:用戶名
          • password:密碼

          最后這份文件都會放在項目源碼的根目錄下,源碼下載方式見文末

          修改TC的配置中心

          TC的配置中心默認使用的也是file形式,當(dāng)然要是用nacos作為配置中心了。

          直接修改registry.conf文件,需要改動的地方如下圖:

          需要改動的地方如下:

          • type:改成nacos,表示使用nacos作為配置中心
          • serverAddr:nacos的地址
          • group:分組
          • namespace:命名空間
          • username:用戶名
          • password:密碼

          上述配置修改好之后,在TC啟動的時候?qū)詣幼x取nacos的配置。

          那么問題來了:TC需要存儲到Nacos中的配置都哪些,如何推送過去?

          seata-1.3.0\script\config-center中有一個config.txt文件,其中就是TC所需要的全部配置。

          seata-1.3.0\script\config-center\nacos中有一個腳本nacos-config.sh則是將config.txt中的全部配置自動推送到nacos中,運行下面命令(windows可以使用git bash運行):

          #?-h?主機,你可以使用localhost,-p?端口號?你可以使用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

          推送成功則可以在Nacos中查詢到所有的配置,如下圖:

          修改TC的數(shù)據(jù)庫連接信息

          TC是需要使用數(shù)據(jù)庫存儲事務(wù)信息的,那么如何修改相關(guān)配置呢?

          上一節(jié)的內(nèi)容已經(jīng)將所有的配置信息都推送到了Nacos中,TC啟動時會從Nacos中讀取,因此我們修改也需要在Nacos中修改。

          需要修改的配置如下:

          ## 采用db的存儲形式
          store.mode=db
          ## druid數(shù)據(jù)源
          store.db.datasource=druid
          ## mysql數(shù)據(jù)庫
          store.db.dbType=mysql
          ## mysql驅(qū)動
          store.db.driverClassName=com.mysql.jdbc.Driver
          ## TC的數(shù)據(jù)庫url
          store.db.url=jdbc:mysql://127.0.0.1:3306/seata_server?useUnicode=true
          ## 用戶名
          store.db.user=root
          ## 密碼
          store.db.password=Nov2014

          在nacos中搜索上述的配置,直接修改其中的值,比如修改store.mode,如下圖:

          當(dāng)然Seata還支持Redis作為TC的數(shù)據(jù)庫,只需要改動以下配置即可:

          store.mode=redis
          store.redis.host=127.0.0.1
          store.redis.port=6379
          store.redis.password=123456

          啟動TC

          按照上述步驟全部配置成功后,則可以啟動TC,在seata-server-1.3.0\seata\bin目錄下直接點擊seata-server.bat(windows)運行。

          啟動成功后,在Nacos的服務(wù)列表中則可以看到TC已經(jīng)注冊進入,如下圖:

          至此,Seata的TC就啟動完成了............

          Seata客戶端搭建(RM)

          上述已經(jīng)將Seata的服務(wù)端(TC)搭建完成了,下面就以電商系統(tǒng)為例介紹一下如何編碼實現(xiàn)分布式事務(wù)。

          用戶購買商品的業(yè)務(wù)邏輯。整個業(yè)務(wù)邏輯由3個微服務(wù)提供支持:

          • 倉儲服務(wù):對給定的商品扣除倉儲數(shù)量。
          • 訂單服務(wù):根據(jù)采購需求創(chuàng)建訂單。
          • 帳戶服務(wù):從用戶帳戶中扣除余額。

          需要了解的知識:Nacos和openFeign,有不清楚的可以看我的前兩章教程,如下:

          倉儲服務(wù)搭建

          陳某整個教程使用的都是同一個聚合項目,關(guān)于Spring Cloud版本有不清楚的可以看我第一篇文章的說明。

          添加依賴

          新建一個seata-storage9020項目,新增依賴如下:

          由于使用的springCloud Alibaba依賴版本是2.2.1.RELEASE,其中自帶的seata版本是1.1.0,但是我們Seata服務(wù)端使用的版本是1.3.0,因此需要排除原有的依賴,重新添加1.3.0的依賴。

          注意:seata客戶端的依賴版本必須要和服務(wù)端一致。

          創(chuàng)建數(shù)據(jù)庫

          創(chuàng)建一個數(shù)據(jù)庫seata-storage,其中新建兩個表:

          • storage:庫存的業(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?'單價,單位分',
          ??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要求必須有的,每個業(yè)務(wù)庫都應(yīng)該創(chuàng)建一個,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)配置

          對于Nacos、Mysql數(shù)據(jù)源等相關(guān)信息就省略了,項目源碼中都有。主要講一下seata如何配置,詳細配置如下:

          spring:
          ??application:
          ????##?指定服務(wù)名稱,在nacos中的名字
          ????name:?seata-storage
          ##?客戶端seata的相關(guān)配置
          seata:
          ??##?是否開啟seata,默認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在同一個注冊中心下
          ????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ù)端中的名稱相同,默認是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

          以上配置注釋已經(jīng)很清楚,這里著重強調(diào)以下幾點:

          • 客戶端seata中的nacos相關(guān)配置要和服務(wù)端相同,比如地址、命名空間..........
          • tx-service-group:這個屬性一定要注意,這個一定要和服務(wù)端的配置一致,否則不生效;比如上述配置中的,就要在nacos中新增一個配置service.vgroupMapping.seata-storage-tx-group=default,如下圖:

          注意:seata-storage-tx-group僅僅是后綴,要記得添加配置的時候要加上前綴service.vgroupMapping.

          扣減庫存的接口

          邏輯很簡單,這里僅僅是做了減庫存的操作,代碼如下:

          這里的接口并沒有不同,還是使用@Transactional開啟了本地事務(wù),并沒有涉及到分布式事務(wù)。

          到這里倉儲服務(wù)搭建好了..............

          賬戶服務(wù)搭建

          搭建完了倉儲服務(wù),賬戶服務(wù)搭建很類似了。

          添加依賴

          新建一個seata-account9021服務(wù),這里的依賴和倉儲服務(wù)的依賴相同,直接復(fù)制

          創(chuàng)建數(shù)據(jù)庫

          創(chuàng)建一個seata-account數(shù)據(jù)庫,其中新建了兩個表:

          • 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:回滾日志表,同倉儲服務(wù)

          配置seata相關(guān)配置

          Seata相關(guān)配置和倉儲服務(wù)相同,只不過需要在nacos中添加一個service.vgroupMapping.seata-account-tx-group=default,如下圖:

          扣減余額的接口

          具體邏輯自己完善,這里我直接扣減余額,代碼如下:

          依然沒有涉及到分布式事務(wù),還是使用@Transactional開啟了本地事務(wù),是不是很爽............

          訂單服務(wù)搭建(TM)

          這里為了節(jié)省篇幅,陳某直接使用訂單服務(wù)作為TM,下單、減庫存、扣款整個流程都在訂單服務(wù)中實現(xiàn)。

          添加依賴

          新建一個seata-order9022服務(wù),這里需要添加的依賴如下:

          • Nacos服務(wù)發(fā)現(xiàn)的依賴
          • seata的依賴
          • openFeign的依賴,由于要調(diào)用賬戶、倉儲的微服務(wù),因此需要額外添加一個openFeign的依賴

          創(chuàng)建數(shù)據(jù)庫

          新建一個seata_order數(shù)據(jù)庫,其中新建兩個表,如下:

          • 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:回滾日志表,同倉儲服務(wù)

          配置和seata相關(guān)配置

          Seata相關(guān)配置和倉儲服務(wù)相同,只不過需要在nacos中添加一個service.vgroupMapping.seata-order-tx-group=default,如下圖:

          扣減庫存的接口

          這里需要通過openFeign調(diào)用倉儲服務(wù)的接口進行扣減庫存,接口如下:

          以上只是簡單的通過openFeign調(diào)用,更細致的配置,比如降級,自己完善.........

          扣減余額的接口

          這里仍然是通過openFeign調(diào)用賬戶服務(wù)的接口進行扣減余額,接口如下:

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

          下訂單的接口就是一個事務(wù)發(fā)起方,作為TM,需要發(fā)起一個全局事務(wù),詳細代碼如下圖:

          有什么不同?不同之處就是使用了@GlobalTransactional而不是@Transactional

          @GlobalTransactional是Seata提供的,用于開啟才能全局事務(wù),只在TM中標注即可生效。

          測試

          分別啟動seata-account9021seata-storage9020seata-order9022,如下圖:

          下面調(diào)用下單接口,如下圖:

          從控制臺輸出的日志可以看出,流程未出現(xiàn)任何異常,事務(wù)已經(jīng)提交,如下圖:

          果然,查看訂單、余額、庫存表,數(shù)據(jù)也都是正確的。

          但是,這僅僅是流程沒問題,并不能說明分布式事務(wù)已經(jīng)配置成功了,因此需要手動造個異常。

          在扣減余額的接口睡眠2秒鐘,因為openFeign的超時時間默認是1秒,這樣肯定是超時異常了,如下圖:

          此時,調(diào)用創(chuàng)建訂單的接口,控制臺日志輸出如下圖:

          發(fā)現(xiàn)在扣減余額處理中超時了,導(dǎo)致了異常.......

          此時,看下庫存的數(shù)據(jù)有沒有扣減,很高興,庫存沒有扣減成功,說明事務(wù)已經(jīng)回滾了,分布式事務(wù)成功了。

          總結(jié)

          Seata客戶端創(chuàng)建很簡單,需要注意以下幾點內(nèi)容:

          • seata客戶端的版本需要和服務(wù)端保持一致
          • 每個服務(wù)的數(shù)據(jù)庫都要創(chuàng)建一個undo_log回滾日志表
          • 客戶端指定的事務(wù)分組名稱要和Nacos相同,比如service.vgroupMapping.seata-account-tx-group=default
            • 前綴:service.vgroupMapping.
            • 后綴:{自定義}

          項目源碼已經(jīng)上傳,關(guān)注公眾號碼猿技術(shù)專欄回復(fù)關(guān)鍵詞9528獲取!

          AT模式原理分析

          AT模式最大的優(yōu)點就是對業(yè)務(wù)代碼無侵入,一切都像在寫單體業(yè)務(wù)邏輯一樣。

          TC相關(guān)的三張表:

          • global_table:全局事務(wù)表,每當(dāng)有一個全局事務(wù)發(fā)起后,就會在該表中記錄全局事務(wù)的ID
          • branch_table:分支事務(wù)表,記錄每一個分支事務(wù)的ID,分支事務(wù)操作的哪個數(shù)據(jù)庫等信息
          • lock_table:全局鎖

          一階段步驟

          1. TM:seata-order.create()方法執(zhí)行時,由于該方法具有@GlobalTranscational標志,該TM會向TC發(fā)起全局事務(wù),生成XID(全局鎖)
          2. RM:StorageService.deduct():寫表,UNDO_LOG記錄回滾日志(Branch ID),通知TC操作結(jié)果
          3. RM:AccountService.deduct():寫表,UNDO_LOG記錄回滾日志(Branch ID),通知TC操作結(jié)果
          4. RM:OrderService.create():寫表,UNDO_LOG記錄回滾日志(Branch ID),通知TC操作結(jié)果

          RM寫表的過程,Seata 會攔截業(yè)務(wù)SQL,首先解析 SQL 語義,在業(yè)務(wù)數(shù)據(jù)被更新前,將其保存成before image(前置鏡像),然后執(zhí)行業(yè)務(wù)SQL,在業(yè)務(wù)數(shù)據(jù)更新之后,再將其保存成after image(后置鏡像),最后生成行鎖。以上操作全部在一個數(shù)據(jù)庫事務(wù)內(nèi)完成,這樣保證了一階段操作的原子性。

          二階段步驟

          因為“業(yè)務(wù) SQL”在一階段已經(jīng)提交至數(shù)據(jù)庫, 所以 Seata 框架只需將一階段保存的快照數(shù)據(jù)和行鎖刪掉,完成數(shù)據(jù)清理即可。

          正常:TM執(zhí)行成功,通知TC全局提交,TC此時通知所有的RM提交成功,刪除UNDO_LOG回滾日志

          異常:TM執(zhí)行失敗,通知TC全局回滾,TC此時通知所有的RM進行回滾,根據(jù)UNDO_LOG反向操作,使用before image還原業(yè)務(wù)數(shù)據(jù),刪除UNDO_LOG,但在還原前要首先要校驗臟寫,對比“數(shù)據(jù)庫當(dāng)前業(yè)務(wù)數(shù)據(jù)”和 “after image”,如果兩份數(shù)據(jù)完全一致就說明沒有臟寫,可以還原業(yè)務(wù)數(shù)據(jù),如果不一致就說明有臟寫,出現(xiàn)臟寫就需要轉(zhuǎn)人工處理。

          AT 模式的一階段、二階段提交和回滾均由 Seata 框架自動生成,用戶只需編寫業(yè)務(wù) SQL,便能輕松接入分布式事務(wù),AT 模式是一種對業(yè)務(wù)無任何侵入的分布式事務(wù)解決方案。

          總結(jié)

          本文介紹了七種分布式事務(wù)解決方案,以及阿里開源的Seata,從入門到實現(xiàn),文中如有錯誤之處,歡迎留言指正。

          本文只介紹了Seata的AT模式,其實Seata還支持TCC、Saga事務(wù)模式,關(guān)于這一部分內(nèi)容和Seata源碼分析會在下期文章中介紹。


          1、一天之間,我寫的腳本錯誤干掉了一萬部手機
          2、阿里巴巴建議的線程池創(chuàng)建方式,你用上了嗎?
          3、Redis 作者:每天花6小時搞開源,頂不住了!
          4、DDD到底是何方神圣?今兒聊聊DDD!
          5、上午寫了一段代碼,下午就被開除了,奇怪的知識又增加了!
          6、21 款 yyds 的 IDEA插件
          7、越老越值錢,除了程序員!!

          點分享

          點收藏

          點點贊

          點在看

          瀏覽 16
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  日本黄色视 | 超碰在线观看中文字幕版 | 中文字幕不卡一区 | 日批免费| 99视频久久 |