<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ù)解決方案

          共 9055字,需瀏覽 19分鐘

           ·

          2021-02-03 07:31

          1. 基礎(chǔ)知識


          1)?事務(wù)

          事務(wù)由一組操作構(gòu)成,我們希望這組操作能夠全部正確執(zhí)行,如果這一組操作中的任意一個步驟發(fā)生錯誤,那么就需要回滾之前已經(jīng)完成的操作。也就是同一個事務(wù)中的所有操作,要么全都正確執(zhí)行,要么全都不要執(zhí)行。

          2)?事務(wù)的四大特性ACID

          • 原子性:事務(wù)是一個不可分割的執(zhí)行單元,事務(wù)中的所有操作那么全部執(zhí)行,要么全部不執(zhí)行;

          • 隔離性:事務(wù)的執(zhí)行是相互獨(dú)立的,它們不會相互干擾,一個事務(wù)不會看到另一個正在運(yùn)行過程中的事務(wù)的數(shù)據(jù);

          • 持久性:持久性要求,一個事務(wù)完成之后,事務(wù)的執(zhí)行結(jié)果必須是持久化保存的。即使數(shù)據(jù)庫發(fā)生崩潰,在數(shù)據(jù)庫恢復(fù)后事務(wù)提交的結(jié)果仍然不會丟失;

          • 一致性:事務(wù)在開始前和結(jié)束后,數(shù)據(jù)庫的完整性約束沒有被破壞。

          3)?臟讀、幻讀、虛讀及不可重復(fù)讀

          • 臟讀:如果一個事務(wù)中對數(shù)據(jù)進(jìn)行了更新,但事務(wù)還沒有提交,另一個事務(wù)可以“看到”該事務(wù)沒有提交的更新結(jié)果,這樣造成的問題就是,如果第一個事務(wù)回滾,那么,第二個事務(wù)在此之前所“看到”的數(shù)據(jù)就是一筆臟數(shù)據(jù)。

          不可重復(fù)讀:包括幻讀和虛讀兩種情況

          • 幻讀:事務(wù)1在兩次查詢的過程中,事務(wù)2對該表進(jìn)行了插入、刪除操作,從而事務(wù)1第二次查詢的結(jié)果發(fā)生了變化。

          • 虛讀:在事務(wù)1兩次讀取同一記錄的過程中,事務(wù)2對該記錄進(jìn)行了修改,從而事務(wù)1第二次讀到了不一樣的記錄。

          4)?數(shù)據(jù)庫的四種隔離級別

          • 讀未提交(read uncommitted): 在該級別下,一個事務(wù)對一行數(shù)據(jù)修改的過程中,不允許另一個事務(wù)對該行數(shù)據(jù)進(jìn)行修改,但是允許另一個事務(wù)對該行數(shù)據(jù)讀。因此在本級別下,不會出現(xiàn)更新丟失,但會出現(xiàn)臟讀,不可重復(fù)讀。

          • 讀提交(Read Committed): 在該隔離級別下,不允許兩個未提交的事務(wù)之間并行執(zhí)行,但它允許在一個事務(wù)執(zhí)行的過程中,另外一個事務(wù)得到執(zhí)行并提交。這樣,會出現(xiàn)一種情況,第一個事務(wù)前后兩次select出來的某行數(shù)據(jù),值可能不一樣。值改變的原因是,穿插執(zhí)行的事務(wù)2對該行數(shù)據(jù)進(jìn)行了update操作。在同一個事務(wù)中,兩次select出來的值不相同的問題稱為不可重復(fù)讀問題。要解決不可重復(fù)讀問題,需要把數(shù)據(jù)的隔離級別設(shè)置為可重復(fù)讀。

          • 重復(fù)讀(Repeatable read): 在該隔離級別下,在一個事務(wù)使用某行數(shù)據(jù)的過程中,不允許別的事務(wù)再對該行數(shù)據(jù)進(jìn)行操作。可重復(fù)讀應(yīng)該是給數(shù)據(jù)庫的行加上了鎖。這種隔離級別下,依舊允許別的事務(wù)在該表中插入和刪除數(shù)據(jù),于是就會出現(xiàn),在事務(wù)1執(zhí)行的過程中,如果先后兩次select出符合某個條件的行,如果在這兩次select過程中另一個事務(wù)得到了執(zhí)行,insert或者delete了某些行,就會出現(xiàn)先后兩次select出來的符合同一條件的結(jié)果不一樣,第一次select好像出現(xiàn)了幻覺一樣,因此這個問題也被稱為幻讀。要解決幻讀問題,需要將數(shù)據(jù)庫的隔離級別設(shè)置為串行化。

          • 序列化(serializable):該級別要求所有事務(wù)都必須串行執(zhí)行,因此能避免一切因并發(fā)引起的問題,但效率很低。

          注:mysql默認(rèn)的隔離級別是重復(fù)讀級別,oracle是讀提交

          5)?樂觀鎖和悲觀鎖

          • 樂觀鎖:總是假設(shè)最好的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號機(jī)制和CAS算法實(shí)現(xiàn)。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量。

          • 悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程)。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨(dú)占鎖就是悲觀鎖思想的實(shí)現(xiàn)。

          2. mysql如何保證持久性和原子性


          在數(shù)據(jù)庫系統(tǒng)中,既有存放數(shù)據(jù)的文件,也有存放日志的文件。日志在內(nèi)存中也是有緩存Log buffer,也有磁盤文件log file。

          MySQL中的日志文件,有這么兩種與事務(wù)有關(guān):undo日志與redo日志。

          2.1 undo日志

          數(shù)據(jù)庫事務(wù)具備原子性(atomicity),如果事務(wù)執(zhí)行失敗,需要把數(shù)據(jù)回滾。事務(wù)同時還具備持久性(durability),事務(wù)對數(shù)據(jù)所做的變更需要保存到硬盤,不能因?yàn)楣收隙鴣G失。

          事務(wù)的原子性可以利用undo日志來實(shí)現(xiàn)。

          undo log的原理很簡單,為了滿足事務(wù)的原子性,在操作任何數(shù)據(jù)之前,首先將數(shù)據(jù)備份到undo log,然后進(jìn)行數(shù)據(jù)的修改。如果出現(xiàn)了錯誤或者用戶執(zhí)行了rollback語句,系統(tǒng)可以利用undo log中的備份將數(shù)據(jù)恢復(fù)到事務(wù)開始之前的狀態(tài)。

          數(shù)據(jù)庫寫入數(shù)據(jù)到磁盤之前,會把數(shù)據(jù)先緩存到內(nèi)存中,事務(wù)提交時才會寫入磁盤中。用undo log實(shí)現(xiàn)原子性和持久化的事務(wù)的簡化過程如下:

          假設(shè)有A、B兩個數(shù)據(jù),值分別為1、2:

          1) 事務(wù)開始

          2) 記錄A=1到undo log buffer

          3) 修改A=3

          4) 記錄B=2到undo log buffer

          5) 修改B=4

          6) 將undo log寫到磁盤

          7) 將數(shù)據(jù)寫到磁盤

          8) 事務(wù)提交
          • 如何保證持久性?

          事務(wù)提交前,會把修改數(shù)據(jù)刷到磁盤,也就是說只要事務(wù)提交了,數(shù)據(jù)肯定持久化了。

          • 如何保證原子性?

          每次對數(shù)據(jù)庫修改,都會把修改前數(shù)據(jù)記錄在undo log中,那么需要回滾時,可以讀取undo log,恢復(fù)數(shù)據(jù)。

          • 若系統(tǒng)在7) 和8) 之間崩潰,如何處理?

          此時事務(wù)并未提交,需要回滾。而undo log已經(jīng)被持久化,可以根據(jù)undo log來恢復(fù)數(shù)據(jù)。

          • 若系統(tǒng)在7)之前崩潰,如何處理?

          此時數(shù)據(jù)并未持久化到硬盤,依然保持在事務(wù)之前的狀態(tài)。


          缺陷:?每個事務(wù)提交前將數(shù)據(jù)和undo log寫入磁盤,這樣會導(dǎo)致大量的磁盤IO,因此性能很低。

          如果能夠?qū)?shù)據(jù)緩存一段時間,就能減少IO提高性能,但是這樣就會喪失事務(wù)的持久性,因此引入了另外一種機(jī)制來實(shí)現(xiàn)持久化,即redo log。

          2.2 redo log

          和undo log相反,redo log記錄的是新數(shù)據(jù)的備份。在事務(wù)提交前,只要將redo log持久化即可,不需要將數(shù)據(jù)持久化,減少了IO的次數(shù)。

          先來看一下基本原理,undo + redo事務(wù)的簡化過程:

          假設(shè)有A、B兩個數(shù)據(jù),值分別為1,2:

          1) 事務(wù)開始;

          2) 記錄A=1到undo log buffer;

          3) 修改A=3;

          4) 記錄A=3到redo log buffer;

          5) 記錄B=2到undo log buffer;

          6) 修改B=4;

          7) 記錄B=4到redo log buffer;

          8) 將undo log寫入磁盤;

          9) 將redo log寫入磁盤;

          10) 事務(wù)提交
          2.2.1 安全性和性能問題
          • 如何保證原子性?

          如果在事務(wù)提交前故障,通過undo log日志恢復(fù)數(shù)據(jù)。如果undo log都還沒寫入,那么數(shù)據(jù)就尚未持久化,無需回滾。

          • 如何保證持久化?

          大家會發(fā)現(xiàn),這里并沒有出現(xiàn)數(shù)據(jù)的持久化。因?yàn)閿?shù)據(jù)已經(jīng)寫入redo log,而redo log持久化到了硬盤,因此只要到了步驟9)以后,事務(wù)是可以提交的。

          • 內(nèi)存中的數(shù)據(jù)庫數(shù)據(jù)何時持久化到磁盤?

          因?yàn)閞edo log已經(jīng)持久化,因此數(shù)據(jù)庫數(shù)據(jù)寫入磁盤與否影響不大,不過為了避免出現(xiàn)臟數(shù)據(jù)(內(nèi)存中與磁盤不一致),事務(wù)提交后也會將內(nèi)存數(shù)據(jù)刷入磁盤(也可以按照設(shè)定的頻率刷新內(nèi)存數(shù)據(jù)到磁盤中)。

          • redo log何時寫入磁盤?

          redo log會在事務(wù)提交之前,或者redo log buffer滿了的時候?qū)懭氪疟P。

          2.2.2 存在的問題

          這里存在兩個問題:

          1)?問題1:之前是寫undo和數(shù)據(jù)庫數(shù)據(jù)到硬盤,現(xiàn)在是寫undo和redo到磁盤,似乎沒有減少IO次數(shù)

          • 數(shù)據(jù)庫數(shù)據(jù)寫入是隨機(jī)IO,性能很差;

          • redo log在初始化時會開辟一段連續(xù)的空間,寫入是順序IO,性能很好;

          • 實(shí)際上undo log并不是直接寫入磁盤,而是先寫入到undo log buffer中,當(dāng)redo log持久化時,undo log就同時持久化到硬盤了。

          因此事務(wù)提交前,只需要對redo log持久化即可。

          另外,redo log并不是寫入一次就持久化一次,redo log在內(nèi)存中也有自己的緩沖池redo log buffer。每次寫redo log都是寫入到buffer,在提交時一次性持久化到磁盤,減少IO次數(shù)。

          2)?問題2:redo log數(shù)據(jù)是寫入內(nèi)存buffer中,當(dāng)buffer滿或者事務(wù)提交時,將buffer數(shù)據(jù)寫入磁盤。redo log中記錄的數(shù)據(jù),有可能包含尚未提交的事務(wù),如果此時數(shù)據(jù)庫崩潰,那么如何完成數(shù)據(jù)恢復(fù)?

          數(shù)據(jù)恢復(fù)有兩種策略:

          • 恢復(fù)時,只重做已經(jīng)提交了的事務(wù)

          • 恢復(fù)時,重做所有事務(wù), 包括未提交的事務(wù)和回滾了的事務(wù)。然后通過undo log回滾那些未提交的事務(wù)。

          InnoDB引擎采用的是第二種方案,因此undo log要在redo log前持久化。

          2.3 總結(jié)

          • undo log記錄更新前數(shù)據(jù),用于保證事務(wù)原子性

          • redo log記錄更新后數(shù)據(jù),用于保證事務(wù)的持久性

          • redo log有自己的內(nèi)存buffer,先寫入到buffer,事務(wù)提交時寫入磁盤

          • redo log持久化之后,意味著事務(wù)是可提交

          3. 分布式事務(wù)


          3.1 應(yīng)用場景

          當(dāng)我們的系統(tǒng)采用了微服務(wù)架構(gòu)后,一個電商系統(tǒng)往往被拆分成如下幾個子系統(tǒng):商品系統(tǒng)、訂單系統(tǒng)、支付系統(tǒng)、積分系統(tǒng)等。整個下單的過程如下:

          1) 用戶通過商品系統(tǒng)瀏覽商品,他看中了某一項(xiàng)商品,便點(diǎn)擊下單

          2) 此時訂單系統(tǒng)會生成一條訂單

          3) 訂單創(chuàng)建成功后,支付系統(tǒng)提供支付功能

          4) 當(dāng)支付完成后,由積分系統(tǒng)為該用戶增加積分

          上述2)、3)、4) 需要在一個事務(wù)中完成。對于傳統(tǒng)單體應(yīng)用而言,實(shí)現(xiàn)事務(wù)非常簡單,只需將這三個步驟放在一個方法A中,再用spring的@Transactional注解標(biāo)識該方法即可。Spring通過數(shù)據(jù)庫的事務(wù)支持,保證這些步驟要么全部執(zhí)行完成,要么全都不執(zhí)行。但在這個微服務(wù)架構(gòu)中,這三個步驟涉及三個系統(tǒng),涉及三個數(shù)據(jù)庫,此時我們必須在數(shù)據(jù)庫和應(yīng)用系統(tǒng)之間,通過某項(xiàng)黑科技,實(shí)現(xiàn)分布式事務(wù)的支持。

          3.2 CAP理論

          在一個分布式系統(tǒng)中,最多只能滿足C、A、P中的兩個需求:

          • C–Consistency: 一致性,同一數(shù)據(jù)的多個副本是否實(shí)時相同

          • A–Availability: 可用性,一定時間內(nèi),系統(tǒng)返回一個明確的結(jié)果,則稱為該系統(tǒng)可用

          • P–Partition tolerance: 分區(qū)容錯性,將同一服務(wù)分布在多個系統(tǒng)中,從而保證某一個系統(tǒng)宕機(jī),仍然有其他系統(tǒng)提供相同的服務(wù)。

          3.3 BASE理論

          BASE是三個單詞的縮寫:

          • Basically Available(基本可用)

          • Soft state(軟狀態(tài))

          • Eventually consistent(最終一致性)

          如下圖所示,訂單服務(wù)、庫存服務(wù)、用戶服務(wù)及他們對應(yīng)的數(shù)據(jù)庫就是分布式應(yīng)用中的三個部分。

          • CP方式:現(xiàn)在如果要滿足事務(wù)的強(qiáng)一致性,就必須在訂單服務(wù)數(shù)據(jù)庫鎖定的同時,對庫存服務(wù)、用戶服務(wù)數(shù)據(jù)資源同時鎖定。等待三個服務(wù)業(yè)務(wù)全部處理完成,才可以釋放資源。此時如果有其他請求想要操作被鎖定的資源就會被阻塞,這樣就是滿足了CP。(這就是強(qiáng)一致性,弱可用)

          • AP方式:三個服務(wù)的對應(yīng)數(shù)據(jù)庫各自獨(dú)立執(zhí)行自己的業(yè)務(wù),執(zhí)行本地事務(wù),不要求相互鎖定資源。但是這個中間狀態(tài)下,我們?nèi)ピL問數(shù)據(jù)庫,可能遇到數(shù)據(jù)不一致的情況,不過我們需要做一些后補(bǔ)措施,保證在經(jīng)過一段時間后,數(shù)據(jù)最終滿足一致性(這就是高可用,但弱一致: 最終一致性)。

          由上面的兩種思想,延伸出了很多的分布式事務(wù)解決方案:

          • XA

          • TCC

          • 可靠消息最終一致性

          • AT

          3.4 二階段提交

          1)?正常情況

          如上圖所示,正常情況下可分為兩階段:

          • 投票階段

          協(xié)調(diào)組詢問各個事務(wù)參與者,是否可以執(zhí)行事務(wù)。每個事務(wù)參與者執(zhí)行事務(wù),寫入redo和undo日志,然后反饋事務(wù)執(zhí)行成功的信息(agree)。

          • 提交階段

          協(xié)調(diào)組發(fā)現(xiàn)每個參與者都可以執(zhí)行事務(wù)(agree),于是向各個事務(wù)參與者發(fā)出commit指令,各個事務(wù)參與者提交事務(wù)。

          2)?異常情況

          如上圖所示,異常情況的處理方式為:

          • 投票階段:協(xié)調(diào)組詢問各個事務(wù)參與者,是否可以執(zhí)行事務(wù)。每個事務(wù)參與者執(zhí)行事務(wù),寫入redo和undo日志,然后反饋事務(wù)執(zhí)行結(jié)果。但只要有一個參與者返回的是Disagree,則說明執(zhí)行失敗。

          • 提交階段:協(xié)調(diào)組發(fā)現(xiàn)一個或多個參與者返回的是Disagree,認(rèn)為執(zhí)行失敗。于是向各個事務(wù)參與者發(fā)出abort指令,各個事務(wù)參與者回滾事務(wù)。

          3)?缺點(diǎn)

          2PC的缺點(diǎn)在于不能處理Fail-stop形式的節(jié)點(diǎn)failure。比如下圖這種情況:

          假設(shè)cordinator和voter3都在commit這個階段crash了,而voter1和voter2沒有收到commit消息。這時候voter1和voter2就陷入了一個困境。因?yàn)樗麄儾⒉荒芘袛喱F(xiàn)在是兩個場景中的哪一種:

          • 上輪全票通過,然后voter3第一個收到了commit消息并在commit操作之后crash了

          • 上輪voter3反對,所以干脆沒有通過;

          4)?阻塞問題

          在準(zhǔn)備階段、提交階段,每個事物參與者都會鎖定本地資源,并等待其它事務(wù)的執(zhí)行結(jié)果,阻塞時間較長,資源鎖定時間太久,因此執(zhí)行的效率就比較低了。

          3.5 TCC模式

          TCC模式可以解決2PC中的資源鎖定和阻塞問題,減少資源鎖定時間。它本質(zhì)是一種補(bǔ)償?shù)乃悸罚聞?wù)運(yùn)行過程包括三個方法:

          • Try: 資源的檢測和預(yù)留

          • Confirm: 執(zhí)行的業(yè)務(wù)操作提交。要求Try成功,Confirm一定要成功;

          • Cancel: 預(yù)留資源釋放

          執(zhí)行分兩個階段:

          • 準(zhǔn)備階段(try): 資源的檢測和預(yù)留

          • 執(zhí)行階段(confirm/cancel):根據(jù)上一步結(jié)果,判斷下面的執(zhí)行方法。如果上一步中所有事務(wù)參與者都成功,則這里執(zhí)行confirm;反之,執(zhí)行cancle

          粗看似乎與兩階段提交沒什么區(qū)別,但其實(shí)差別很大:

          • try、confirm、cancel都是獨(dú)立的事務(wù),不受其他參與者的影響,不會阻塞等待他人;

          • try、confirm、cancel由程序員在業(yè)務(wù)層編寫,鎖力度由代碼控制;

          以下單業(yè)務(wù)中的扣減余額為例來看下怎么編寫,假設(shè)賬戶A原來余額是100,需要余額扣減30元。如圖:

          1) 一階段(try)

          - 余額檢查,并凍結(jié)用戶部分金額,此階段執(zhí)行完畢,事務(wù)已經(jīng)提交。
           
          - 檢查用戶余額是否充足,如果充足,凍結(jié)部分余額
              
          - 在賬戶表中添加凍結(jié)金額字段,值為30,余額不變

          2) 二階段

          - 提交(Confirm):真正的扣款,把凍結(jié)金額從余額中扣除,凍結(jié)金額清空

          - 修改凍結(jié)金額為0,修改余額為100-30 = 70元

          - 補(bǔ)償(Cancel):釋放之前凍結(jié)的金額,并非回滾

          - 余額不變,修改賬戶凍結(jié)金額為0
          3.5.1 TCC優(yōu)缺點(diǎn)及使用場景

          1)?優(yōu)勢

          TCC執(zhí)行的每一個階段都會提交本地事務(wù)并釋放鎖,并不需要等待其它事務(wù)的執(zhí)行結(jié)果。而如果其它事務(wù)執(zhí)行失敗,最后不是回滾,而是執(zhí)行補(bǔ)償操作。這樣就避免了資源的長期鎖定和阻塞等待,執(zhí)行效率比較高,屬于性能比較好的分布式事務(wù)方式。

          2)?缺點(diǎn)

          • 代碼侵入:需要人為編寫代碼實(shí)現(xiàn)try、confirm、cancel,代碼侵入較多

          • 開發(fā)成本高:一個業(yè)務(wù)需要拆分成3個步驟,分別編寫業(yè)務(wù)實(shí)現(xiàn),業(yè)務(wù)編寫比較復(fù)雜

          • 安全性考慮:cancel動作如果執(zhí)行失敗,資源就無法釋放,需要引入重試機(jī)制,而重試可能導(dǎo)致重復(fù)執(zhí)行,還要考慮重試時的冪等問題

          3)?使用場景

          • 對事務(wù)有一定的一致性要求(最終一致)

          • 對性能要求較高

          • 開發(fā)人員具備較高的編碼能力和冪等處理經(jīng)驗(yàn)

          3.6 可靠消息服務(wù)

          一般分為事務(wù)的發(fā)起者A和事務(wù)的其它參與者B:

          • 事務(wù)發(fā)起者A執(zhí)行本地事務(wù)

          • 事務(wù)發(fā)起者A通過MQ將需要執(zhí)行的事務(wù)信息發(fā)送給事務(wù)參與者B

          • 事務(wù)參與者B接收到消息后執(zhí)行本地事務(wù)

          這個過程有點(diǎn)像你去學(xué)校食堂吃飯:

          • 拿著錢去收銀處,點(diǎn)一份紅燒牛肉面,付錢

          • 收銀處給你發(fā)一個小票,還有一個號牌,你別把票弄丟!

          • 你憑小票和號牌一定能領(lǐng)到一份紅燒牛肉面,不管需要多久

          幾個注意事項(xiàng):

          • 事務(wù)發(fā)起者A必須確保本地事務(wù)成功后,消息一定發(fā)送成功

          • MQ必須保證消息正確投遞和持久化保存

          • 事務(wù)參與者B必須確保消息最終一定能消費(fèi),如果失敗需要多次重試

          • 事務(wù)B執(zhí)行失敗,會重試,但不會導(dǎo)致事務(wù)A回滾

          那么問題來了,我們?nèi)绾伪WC消息發(fā)送一定成功?如何保證消費(fèi)者一定能收到消息?

          3.6.1 本地消息表

          參看如下簡化圖:

          事務(wù)發(fā)起者

          • 開啟本地事務(wù)

          • 執(zhí)行事務(wù)相關(guān)業(yè)務(wù)

          • 發(fā)送消息到MQ

          • 把消息持久化到數(shù)據(jù)庫,標(biāo)記為已發(fā)送

          • 提交本地事務(wù)

          事務(wù)接收者

          • 接收消息

          • 開啟本地事務(wù)

          • 處理事務(wù)相關(guān)業(yè)務(wù)

          • 修改數(shù)據(jù)庫消息狀態(tài)為已消費(fèi)

          • 提交本地事務(wù)

          額外的定時任務(wù)

          定時掃描表中超時未消費(fèi)的消息,重新發(fā)送

          優(yōu)點(diǎn)

          • 與tcc相比,實(shí)現(xiàn)方式較為簡單,開發(fā)成本低。

          缺點(diǎn)

          • 數(shù)據(jù)一致性完全依賴于消息服務(wù),因此消息服務(wù)必須是可靠的。

          • 需要處理被動業(yè)務(wù)方的冪等問題

          • 被動業(yè)務(wù)失敗不會導(dǎo)致主動業(yè)務(wù)的回滾,而是重試被動的業(yè)務(wù)

          • 事務(wù)業(yè)務(wù)與消息發(fā)送業(yè)務(wù)耦合、業(yè)務(wù)數(shù)據(jù)與消息表要在一起

          3.6.2 獨(dú)立消息服務(wù)

          為了解決上述問題,我們會引入一個獨(dú)立的消息服務(wù),來完成對消息的持久化、發(fā)送、確認(rèn)、失敗重試等一系列行為,大概的模型如下:

          一次消息發(fā)送時序圖:

          事務(wù)發(fā)起者A的基本執(zhí)行步驟

          • 開啟本地事務(wù)

          • 通知消息服務(wù),準(zhǔn)備發(fā)送消息(消息服務(wù)將消息持久化,標(biāo)記為準(zhǔn)備發(fā)送)

          • 執(zhí)行本地業(yè)務(wù):執(zhí)行失敗則終止,通知消息服務(wù),取消發(fā)送(消息服務(wù)修改訂單狀態(tài));執(zhí)行成功則繼續(xù),通知消息服務(wù),確認(rèn)發(fā)送(消息服務(wù)發(fā)送消息、修改訂單狀態(tài))

          • 提交本地事務(wù)

          消息服務(wù)本身提供下面的接口

          • 準(zhǔn)備發(fā)送:把消息持久化到數(shù)據(jù)庫,并標(biāo)記狀態(tài)為準(zhǔn)備發(fā)送

          • 取消發(fā)送:把數(shù)據(jù)庫消息狀態(tài)修改為取消

          • 確認(rèn)發(fā)送:把數(shù)據(jù)庫消息狀態(tài)修改為確認(rèn)發(fā)送。嘗試發(fā)送消息,成功后修改狀態(tài)為已發(fā)送

          • 確認(rèn)消費(fèi):消費(fèi)者已經(jīng)接收并處理消息,把數(shù)據(jù)庫消息狀態(tài)修改為已消費(fèi)

          • 定時任務(wù):定時掃描數(shù)據(jù)庫中狀態(tài)為確認(rèn)發(fā)送的消息,然后詢問對應(yīng)的事務(wù)發(fā)起者,事務(wù)業(yè)務(wù)執(zhí)行是否成功,結(jié)果:業(yè)務(wù)執(zhí)行成功,則嘗試發(fā)送消息,成功后修改狀態(tài)為已發(fā)送;業(yè)務(wù)執(zhí)行失敗,則把數(shù)據(jù)庫消息狀態(tài)修改為取消

          事務(wù)參與者B的基本步驟

          • 接收消息

          • 開啟本地事務(wù)

          • 執(zhí)行業(yè)務(wù)

          • 通知消息服務(wù),消息已經(jīng)接收和處理

          • 提交事務(wù)

          優(yōu)點(diǎn)

          • 解除了事務(wù)業(yè)務(wù)與消息相關(guān)業(yè)務(wù)的耦合

          缺點(diǎn)

          • 實(shí)現(xiàn)起來比較復(fù)雜

          3.6.3 RabbitMQ的消息確認(rèn)

          RabbitMQ確保消息不丟失的思路比較奇特,并沒有使用傳統(tǒng)的本地表,而是利用了消息的確認(rèn)機(jī)制:

          • 生產(chǎn)者確認(rèn)機(jī)制:確保消息從生產(chǎn)者到達(dá)MQ不會有問題

          • 消息生產(chǎn)者發(fā)送消息到RabbitMQ時,可以設(shè)置一個異步的監(jiān)聽器,監(jiān)聽來自MQ的ACK

          • MQ接收到消息后,會返回一個回執(zhí)給生產(chǎn)者:

          • 消息到達(dá)交換機(jī)后路由失敗,會返回失敗ACK

          • 消息路由成功,持久化失敗,會返回失敗ACK

          • 消息路由成功,持久化成功,會返回成功ACK

          • 生產(chǎn)者提前編寫好不同回執(zhí)的處理方式

          • 失敗回執(zhí):等待一定時間后重新發(fā)送

          • 成功回執(zhí):記錄日志等行為

          • 消費(fèi)者確認(rèn)機(jī)制:確保消息能夠被消費(fèi)者正確消費(fèi)

          • 消費(fèi)者需要在監(jiān)聽隊(duì)列的時候指定手動ACK模式

          • RabbitMQ把消息投遞給消費(fèi)者后,會等待消費(fèi)者ACK,接收到ACK后才刪除消息,如果沒有接收到ACK消息會一直保留在服務(wù)端,如果消費(fèi)者斷開連接或異常后,消息會投遞給其它消費(fèi)者。

          • 消費(fèi)者處理完消息,提交事務(wù)后,手動ACK。如果執(zhí)行過程中拋出異常,則不會ACK,業(yè)務(wù)處理失敗,等待下一條消息

          經(jīng)過上面的兩種確認(rèn)機(jī)制,可以確保從消息生產(chǎn)者到消費(fèi)者的消息安全,再結(jié)合生產(chǎn)者和消費(fèi)者兩端的本地事務(wù),即可保證一個分布式事務(wù)的最終一致性。

          3.7 AT模式

          基本原理:

          有沒有感覺跟TCC的執(zhí)行很像,都是分兩個階段:

          • 一階段:執(zhí)行本地事務(wù),并返回執(zhí)行結(jié)果

          • 二階段:根據(jù)一階段的結(jié)果,判斷二階段做法:提交或回滾

          但AT模式底層做的事情可完全不同,而且第二階段根本不需要我們編寫,全部有Seata自己實(shí)現(xiàn)了。也就是說:我們寫的代碼與本地事務(wù)時代碼一樣,無需手動處理分布式事務(wù)。

          3.7.1 詳細(xì)處理流程

          1)?一階段   

          在一階段,Seata 會攔截“業(yè)務(wù) SQL”,首先解析SQL語義,找到“業(yè)務(wù) SQL”要更新的業(yè)務(wù)數(shù)據(jù),在業(yè)務(wù)數(shù)據(jù)被更新前,將其保存成“before image”,然后執(zhí)行“業(yè)務(wù) SQL”更新業(yè)務(wù)數(shù)據(jù),在業(yè)務(wù)數(shù)據(jù)更新之后,再將其保存成“after image”,最后獲取全局行鎖,提交事務(wù)。以上操作全部在一個數(shù)據(jù)庫事務(wù)內(nèi)完成,這樣保證了一階段操作的原子性。

          這里的before imageafter image類似于數(shù)據(jù)庫的undo和redo日志,但其實(shí)是用數(shù)據(jù)庫模擬的

          2)?二階段

          二階段如果是提交的話,因?yàn)椤皹I(yè)務(wù) SQL”在一階段已經(jīng)提交至數(shù)據(jù)庫, 所以 Seata 框架只需將一階段保存的快照數(shù)據(jù)和行鎖刪掉,完成數(shù)據(jù)清理即可。

          二階段如果是回滾的話,Seata 就需要回滾一階段已經(jīng)執(zhí)行的“業(yè)務(wù) SQL”,還原業(yè)務(wù)數(shù)據(jù)。回滾方式便是用“before image”還原業(yè)務(wù)數(shù)據(jù);但在還原前要首先要校驗(yàn)臟寫,對比“數(shù)據(jù)庫當(dāng)前業(yè)務(wù)數(shù)據(jù)”和 “after image”,如果兩份數(shù)據(jù)完全一致就說明沒有臟寫,可以還原業(yè)務(wù)數(shù)據(jù),如果不一致就說明有臟寫,出現(xiàn)臟寫就需要轉(zhuǎn)人工處理。

          不過因?yàn)橛腥宙i機(jī)制,所以可以降低出現(xiàn)臟寫的概率。

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

          注:Seata的詳細(xì)流程不做贅述。

          [參看]:

          1. 分布式事務(wù)解決方案

          2. 分布式系統(tǒng)一致性解決方案

          https://ivanzz1001.github.io/records/post/distribute-systems/2018/05/30/distribute-transaction

          喜歡,在看


          瀏覽 74
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  久久白丝| 五月婷婷黄色 | 一级爱爱视频免费看 | 青娱乐人人干 | 蜜桃av无码一区三区。 |