日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

分布式事务科普(初识篇)

發布時間:2024/4/11 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 分布式事务科普(初识篇) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊上方“朱小廝的博客”,選擇“設為星標”

后臺回復"高效Java"領取《Effective Java第三版》

歡迎跳轉到本文的原文鏈接:https://honeypps.com/architect/introduction-of-distributed-transaction/

《分布式事務科普》是我在YQ期間整理的一篇科普型文章,內容共計兩萬五千字左右,應該算是涵蓋了這個領域的大多數知識點。篇幅較長,遂分為上下兩篇發出。上篇為《分布式事務科普——初識篇》:ACID、事務隔離級別、MySQL事務實現原理、CAP、BASE、2PC、3PC等。下篇為《分布式事務科普——終結篇》,詳細講解分布式事務的解決方案:XA、AT、TCC、Saga、本地消息表、消息事務、最大努力通知等(明日放出)。

分布式事務科普

隨著業務的快速發展、業務復雜度越來越高,傳統單體應用逐漸暴露出了一些問題,例如開發效率低、可維護性差、架構擴展性差、部署不靈活、健壯性差等等。而微服務架構是將單個服務拆分成一系列小服務,且這些小服務都擁有獨立的進程,彼此獨立,很好地解決了傳統單體應用的上述問題,但是在微服務架構下如何保證事務的一致性呢?本文首先從事務的概念出來,帶大家先回顧一下ACID、事務隔離級別、CAP、BASE、2PC、3PC等基本理論,然后再詳細講解分布式事務的解決方案:XA、AT、TCC、Saga、本地消息表、消息事務、最大努力通知等。

什么是事務

事務提供一種機制,可以將一個活動涉及的所有操作納入到一個不可分割的執行單元,組成事務的所有操作只有在所有操作均能正常執行的情況下方能提交,只要其中任一操作執行失敗,都將導致整個事務的回滾。簡單地說,事務提供一種“要么什么都不做,要么做全套(All or Nothing)”機制。

事務最經典也經常被拿出來說例子就是轉賬了。假如A要給B轉賬1000元,這個轉賬會涉及到兩個關鍵操作就是:將A的余額減少1000元,將B的余額增加1000元。萬一在這兩個操作之間突然出現錯誤比如銀行系統崩潰,導致A余額減少而B的余額沒有增加,這樣就不對了。事務就是保證這兩個關鍵操作要么都成功,要么都要失敗。

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱為ACID特性。任何事務機制在實現時,都應該考慮事務的ACID特性,包括:本地事務、分布式事務,即使不能都很好的滿足,也要考慮支持到什么程度。

ACID

ACID 理論是對事務特性的抽象和總結,方便我們實現事務。你可以理解成:如果實現了操作的 ACID 特性,那么就實現了事務。ACID的具體含義詳述如下。

原子性(Atomicity):原子性是指單個事務本身涉及到的數據庫操作,要么全部成功,要么全部失敗,不存在完成事務中一部分操作的可能。以上文說的轉賬為例,就是要么操作全部成功,A的錢少了,B的錢多了;要么就是全部失敗,AB保持和原來一直的數目。

一致性(Consistency):事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態,事務的中間狀態不能被觀察到的。還是以轉賬為例,原來AB賬戶的錢加一起是1000,相互轉賬完成時候彼此還是1000,所以一致性理解起來就是事務執行前后的數據狀態是穩定的,對于轉賬就是金額穩定不變,對于其他的事務操作就是事務執行完成之后,數據庫的狀態是正確的,沒有臟數據。

隔離性(isolation):一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾。隔離性側重于多個事務之間的特性,也就是說多個事務之間是沒有相互影響的比如A給B轉賬和B給C轉賬這兩個事務是沒有影響的(這里B給C轉賬如果和A給B轉賬的事務同時進行的時候,B的金額正確性問題保證就要看隔離級別了)。

持久性(durability):持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。

事務的隔離級別

在多個事務并發操作數據庫(多線程、網絡并發等)的時候,如果沒有有效的避免機制,就會出現臟讀、不可重復讀和幻讀這3種問題。

臟讀(Dirty Read)

A事務讀取B事務尚未提交的數據,此時如果B事務由于某些原因執行了回滾操作,那么A事務讀取到的數據就是臟數據。

參考下圖,事務A讀取到了事務B未提交的記錄。

不可重復讀(Nonrepeatable Read)

一個事務內前后多次讀取,數據內容不一致。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩次讀數據之間,由于第二個事務的修改,那么第一個事務兩次讀到的的數據可能是不一樣的。這樣在一個事務內兩次讀到的數據是不一樣的,因此稱為是不可重復讀。

參考下圖,事務A讀取到的name可能為“張三”,也可能為“李四”。

幻讀(Phantom Read)

一個事務內前后多次讀取,數據總量不一致。參考下圖,事務A在執行讀取操作,需要兩次統計數據的總量,前一次查詢數據總量后,此時事務B執行了新增數據的操作并提交后,這個時候事務A讀取的數據總量和之前統計的不一樣,就像產生了幻覺一樣,平白無故的多了幾條數據,成為幻讀。

不可重復讀和幻讀有些相似,兩者的區別在于:不可重復讀的重點在于修改,同樣的條件, 你讀取過的數據,再次讀取出來發現值不一樣了;而幻讀的重點在于新增或者刪除(參考MySQL官網https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html對幻讀的定義,記錄的減少也應該算是幻讀),同樣的條件, 第 1 次和第 2 次讀出來的記錄數不一樣。

隔離級別

事務的隔離性是指多個并發的事務同時訪問一個數據庫時,一個事務不應該被另一個事務所干擾,每個并發的事務間要相互進行隔離。SQL 標準定義了以下四種隔離級別:

  • 讀未提交(Read Uncommitted):一個事務可以讀取到另一個事務未提交的修改。這種隔離級別是最弱的,可能會產生臟讀,幻讀,不可重復讀的問題問題。

  • 讀已提交(Read Committed):一個事務只能讀取另一個事務已經提交的修改。其避免了臟讀,仍然存在不可以重復讀和幻讀的問題。SQL Server和Oracle的默認隔離級別就是這個。

  • 可重復讀(Repeated Read):同一個事務中多次讀取相同的數據返回的結果是一樣的。其避免了臟讀和不可重復讀問題,但是幻讀依然存在。MySQL中的默認隔離級別就是這個,不過MySQL通過多版本并發控制(MVCC)、Next-key Lock等技術解決了幻讀問題。

  • 串行化(Serializable):這是數據庫最高的隔離級別,這種級別下,事務“串行化順序執行”,也就是一個一個排隊執行。在這種級別下,臟讀、不可重復讀、幻讀都可以被避免,但是執行效率奇差,性能開銷也最大。

事務的隔離級別和臟讀、不可重復讀、幻讀的關系總結如下表所示:

隔離級別臟讀不可重復讀幻讀
未提交讀可能可能可能
已提交讀不可能可能可能
可重復讀不可能不可能可能
可串行化不可能不可能不可能

MySQL事務實現原理

這里所說的MySQL事務是指使用InnoDB引擎時的事務。MySQL在5.5版本之前默認的數據庫引擎時MyISAM,雖然性能極佳,而且提供了大量的特性,包括全文索引、壓縮、空間函數等,但MyISAM不支持事務和行級鎖,而且最大的缺陷就是崩潰后無法安全恢復。5.5版本之后,MySQL引入了InnoDB(事務性數據庫引擎),MySQL 5.5版本后默認的存儲引擎為InnoDB。

redo log和undo log來保證事務的原子性、一致性和持久性,同時采用預寫日志(WAL)方式將隨機寫入變成順序追加寫入,提升事務性能。而隔離性是通過鎖技術來保證的。

這里我們不放先來了解一下redo log和undo log。redo log是重做日志,提供前滾操作,undo log是回滾日志,提供回滾操作。undo log不是redo log的逆向過程,其實它們都算是用來恢復的日志:

  • redo log通常是物理日志,記錄的是數據頁的物理修改,而不是某一行或某幾行修改成怎樣怎樣,它用來恢復提交后的物理數據頁(恢復數據頁,且只能恢復到最后一次提交的位置)。

  • undo用來回滾行記錄到某個版本。undo log一般是邏輯日志,根據每行記錄進行記錄。

redo log

redo log 又稱為重做日志,它包含兩部分:一是內存中的日志緩沖(redo log buffer),該部分日志是易失性的;二是磁盤上的重做日志文件(redo log file),該部分日志是持久的。

當需要修改事務中的數據時,先將對應的redo log寫入到redo log buffer中,然后才在內存中執行相關的數據修改操作。InnoDB通過“force log at commit”機制實現事務的持久性,即在事務提交的時候,必須先將該事務的所有redo log都寫入到磁盤上的redo log file中,然后待事務的commit操作完成才算整個事務操作完成。

在每次將redo log buffer中的內容寫入redo log file時,都需要調用一次fsync操作,以此確保redo log成功寫入到磁盤上(參考下圖,內容的流向為:用戶態的內存->操作系統的頁緩存->物理磁盤)。因此,磁盤的性能在一定程度上也決定了事務提交的性能。這里還可以通過innodb_flush_log_at_trx_commit來控制redo log刷磁盤的策略,這里就不做贅述了。

undo log

undo log有2個功能:實現回滾和多版本并發控制(MVCC, Multi-Version Concurrency Control)。

在數據修改的時候,不僅記錄了redo log,還記錄了相對應的undo log,如果因為某些原因導致事務失敗或回滾了,可以借助該undo log進行回滾。

undo log和redo log記錄物理日志不一樣,它是邏輯日志。可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄。

當執行rollback時,就可以從undo log中的邏輯記錄讀取到相應的內容并進行回滾。有時候應用到行版本控制的時候,也是通過undo log來實現的:當讀取的某一行被其他事務鎖定時,它可以從undo log中分析出該行記錄以前的數據是什么,從而提供該行版本信息,讓用戶實現非鎖定一致性讀取。

MVCC

說到undo log,就不得不順帶提一下MVCC了,因為MVCC的實現依賴了undo log。當然,MVCC的實現還依賴了隱藏字段(DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID)、Read View等。

MVCC的全稱是多版本并發控制,它使得在使用READ COMMITTD、REPEATABLE READ這兩種隔離級別的事務下執行一致性讀操作有了保證。換言之,就是為了查詢一些正在被另一個事務更新的行,并且可以看到它們被更新之前的值。這是一個可以用來增強并發性的強大技術,因為這樣的一來的話查詢就不用等待另一個事務釋放鎖,使不同事務的讀-寫、寫-讀操作并發執行,從而提升系統性能。

這里的讀指的是“快照讀”。普通的SELECT操作就是快照讀,有的地方也稱之為“一致性讀”或者“一致性無鎖讀”。它不會對表中的任何記錄做加鎖動作,即不加鎖的非阻塞讀。快照讀的前提是隔離級別不是串行化級別,串行化級別下的快照讀會退化成當前讀。之所以出現快照讀的情況,是基于提高并發性能的考慮,這里可以認為MVCC是行鎖的一個變種,但它在很多情況下,避免了加鎖操作,降低了開銷。當然,既然是基于多版本,即快照讀可能讀到的并不一定是數據的最新版本,而有可能是之前的歷史版本。

對應的還有“當前讀”。類似UPDATE、DELETE、INSERT、SELECT...LOCK IN SHARE MODE、SELECT...FOR UPDATE這些操作就是當前讀。為什么叫當前讀?就是它讀取的是記錄的最新版本,讀取時還要保證其他并發事務不能修改當前記錄,會對讀取的記錄進行加鎖。

鎖技術

并發事務的讀-讀情況并不會引起什么問題(讀取操作本身不會對記錄有任何影響,并不會引起什么問題,所以允許這種情況的發生),不過對于寫-寫、讀-寫或寫-讀這些情況可能會引起一些問題,需要使用MVCC或者加鎖的方式來解決它們。

在使用加鎖的方式解決問題時,既要允許讀-讀情況不受影響,又要使寫-寫、讀-寫或寫-讀情況中的操作相互阻塞。這里引入了兩種行級鎖:

  • 共享鎖:英文名為Shared Locks,簡稱S鎖。允許事務讀一行數據。

  • 排它鎖:也常稱獨占鎖,英文名為Exclusive Locks,簡稱X鎖。允許事務刪除或更新一行數據。

假如事務A首先獲取了一條記錄的S鎖之后,事務B接著也要訪問這條記錄:1) 如果事務B想要再獲取一個記錄的S鎖,那么事務B也會獲得該鎖,也就意味著事務A和B在該記錄上同時持有S鎖;2) 如果事務B想要再獲取一個記錄的X鎖,那么此操作會被阻塞,直到事務A提交之后將S鎖釋放掉。

如果事務A首先獲取了一條記錄的X鎖之后,那么不管事務B接著想獲取該記錄的S鎖還是X鎖都會被阻塞,直到事務A提交。

除了 S鎖 和 S 鎖兼容,其他都不兼容。

InnoDB存儲引擎還支持多粒度鎖定,這種鎖定允許事務在行級上的鎖和表級上的鎖同時存在。為此,InnoDB存儲引擎引入了意向鎖(表級別鎖):

  • 意向共享鎖(IS 鎖):事務想要獲取一張表的幾行數據的共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的 IS 鎖。

  • 意向排他鎖(IX 鎖):事務想要獲取一張表中幾行數據的排它鎖,事務在給一個數據行加排它鎖前必須先取得該表的 IX 鎖。

當我們在對使用InnoDB存儲引擎的表的某些記錄加S鎖之前,那就需要先在表級別加一個IS鎖,當我們在對使用InnoDB存儲引擎的表的某些記錄加X鎖之前,那就需要先在表級別加一個IX鎖。IS鎖和IX鎖的使命只是為了后續在加表級別的S鎖和X鎖時判斷表中是否有已經被加鎖的記錄,以避免用遍歷的方式來查看表中有沒有上鎖的記錄。

下表展示了X、IX、S、IS鎖的兼容性:

兼容性XIXSIS
X不兼容不兼容不兼容不兼容
IX不兼容兼容不兼容兼容
S不兼容不兼容兼容兼容
IS不兼容兼容兼容兼容

這里還要了解一下的是,在InnoDB中有 3 種行鎖的算法:

  • Record Locks(記錄鎖):單個行記錄上的鎖。

  • Gap Locks(間隙鎖):在記錄之間加鎖,或者在第一個記錄之前加鎖,亦或者在最后一個記錄之后加鎖,即鎖定一個范圍,而非記錄本身。

  • Next-Key Locks:結合 Gap Locks 和 Record Locks,鎖定一個范圍,并且鎖定記錄本身。主要解決的是 REPEATABLE READ 隔離級別下的幻讀問題。

對于Next-Key Locks,如果我們鎖定了一個行,且查詢的索引含有唯一屬性時(即有唯一索引),那么這個時候InnoDB會將Next-Key Locks優化成Record Locks,也就是鎖定當前行,而不是鎖定當前行加一個范圍;如果我們使用的不是唯一索引鎖定一行數據,那么此時InnoDB就會按照本來的規則鎖定一個范圍和記錄。還有需要注意的點是,當唯一索引由多個列組成時,如果查詢僅是查找其中的一個列,這時候是不會降級的。InnoDB存儲引擎還會對輔助索引的下一個鍵值區間加Gap Locks(這么做也是為了防止幻讀)。

總結

MySQL實現事務ACID特性的方式總結如下:

  • 原子性:使用 undo log來實現,如果事務執行過程中出錯或者用戶執行了rollback,系統通過undo log日志返回事務開始的狀態。

  • 持久性:使用 redo log來實現,只要redo log日志持久化了,當系統崩潰,即可通過redo log把數據恢復。

  • 隔離性:通過鎖以及MVCC來實現。

  • 一致性:通過回滾、恢復以及并發情況下的隔離性,從而實現一致性。

理論基石

對于事務,想必大家也或多或少地聽到過類似本地事務、數據庫事務、傳統事務、剛性事務、柔性事務、分布式事務等多種稱呼(還有如單機事務、全局事務等稱呼),那么這些多種類的事務分別指的是什么呢?

本地事務(Local Transaction),通常也被稱之為數據庫事務、傳統事務(相對于分布式事務而言)。它僅限于對單一數據庫資源的訪問控制,如下圖(左)所示。

不過,現在隨著系統架構的服務化,事務的概念也延伸到了服務中,倘若將一個單一的服務操作作為一個事務,那么整個服務操作只能涉及一個單一的數據庫資源。由此,本地事務的定義可以擴展為基于單個服務單一數據庫資源訪問的事務,如上圖(右)所示。

本地事務通常由資源管理器進行管理,如下圖所示。

本地事務的優點就是支持嚴格的ACID特性,高效,可靠,狀態可以只在資源管理器中維護,而且應用編程模型簡單。但是本地事務不具備分布式事務的處理能力,隔離的最小單位受限于資源管理器。

與傳統的本地事務所對應的是分布式事務,它指事務的參與者、支持事務的服務器、資源服務器以及事務管理器分別位于不同的分布式系統的不同節點之上。簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分布在不同的服務器上,且屬于不同的應用,分布式事務需要保證這些小操作要么全部成功,要么全部失敗。

剛性事務是指完全遵循ACID規范的事務。最常見的剛性事務就是數據庫事務(本地事務),比如MySQL事務就是一種典型的剛性事務。

在電商領域等互聯網場景下,傳統的事務在數據庫性能和處理能力上都暴露出了瓶頸。在分布式領域基于CAP理論以及BASE理論,有人就提出了柔性事務的概念。柔性事務為了滿足可用性、性能與降級服務的需要,降低了一致性(Consistency)與隔離性(Isolation)的要求。

CAP

CAP是指的是在一個分布式系統中、Consistency(一致性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可得兼。

  • 一致性(C):每次讀取要么獲得最近寫入的數據,要么獲得一個錯誤。

  • 可用性(A):每次請求都能獲得一個(非錯誤)響應,但不保證返回的是最新寫入的數據。

  • 分區容忍性(P):盡管任意數量的消息被節點間的網絡丟失(或延遲),系統仍繼續運行。

ACID理論和CAP理論都有一個C,也都叫一致性, 所以很多人都會把這兩個概念當做是一個概念。不過,這兩個C是有區別的:

  • ACID的C指的是事務中的一致性,在一系列對數據修改的操作中,保證數據的正確性。即數據在事務期間的多個操作中,數據不會憑空的消失或增加,數據的每一個增刪改操作都是有因果關系的。比如用戶A向用戶B轉了200塊錢,不會出現用戶A扣了款,而用戶B沒有收到的情況。

  • 在分布式環境中,多服務之間的復制是異步,需要一定耗時,不會瞬間完成。在某個服務節點的數據修改之后,到同步到其它服務節點之間存在一定的時間間隔,如果在這個間隔內有并發讀請求過來,而這些請求又負載均衡到多個節點,可能會出現從多個節點數據不一致的情況,因為請求有可能會落到還沒完成數據同步的節點上。CAP中的C就是為了做到在分布式環境中讀取的數據是一致的。

總的來說,ACID的C著重強調單數據庫事務操作時,要保證數據的完整和正確性,而CAP理論中的C強調的是對一個數據多個備份的讀寫一致性。

有關CAP的更多解讀,可以看看這篇《越說越迷糊的CAP》。記得關注公眾號:朱小廝的博客。

BASE

在 CAP 理論中,三者不可同時滿足,而服務化中,更多的是提升 A 以及 P,在這個過程中不可避免的會降低對 C 的要求,因此,BASE 理論隨之而來。

BASE理論來源于 ebay 在 2008 年 ACM 中發表的論文(下載地址:https://dl.acm.org/doi/10.1145/1394127.1394128),BASE 理論的基本原則有三個:Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent(最終一致性),主要目的是為了提升分布式系統的可伸縮性,論文同樣闡述了如何對業務進行調整以及折中的手段,BASE 理論的提出為分布式事務的發展指出了一個方向。

BASE理論是對CAP中一致性和可用性權衡的結果,其來源于對大規模互聯網系統分布式實踐的總結,是基于CAP定理逐步演化而來的。BASE理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,采用適當的方式來使系統達到最終一致性。BASE 理論本質上是對 CAP 理論的延伸,是對 CAP 中 AP 方案的一個補充。

基本可用

基本可用是指分布式系統在出現不可預知故障的時候,允許損失部分可用性。不過,這絕不等價于系統不可用。比如:

  • 響應時間上的損失。正常情況下,一個在線搜索引擎需要在0.5秒之內返回給用戶相應的查詢結果,但由于出現故障,查詢結果的響應時間增加了1~2秒。

  • 系統功能上的損失:正常情況下,在一個電子商務網站上進行購物的時候,消費者幾乎能夠順利完成每一筆訂單,但是在一些節日大促購物高峰的時候,由于消費者的購物行為激增,為了保護購物系統的穩定性,部分消費者可能會被引導到一個降級頁面。

  • 軟狀態

    軟狀態,也被稱之為柔性狀態,是指允許系統中的數據存在中間狀態,并認為該中間狀態的存在不會影響系統的整體可用性,即允許系統在不同節點的數據副本之間進行數據同步的過程存在延時。

    最終一致性

    最終一致性強調的是所有的數據副本,在經過一段時間的同步之后,最終都能夠達到一個一致的狀態。因此,最終一致性的本質是需要系統保證最終數據能夠達到一致,而不需要實時保證系統數據的強一致性。

    分布式事務的由來

    當下互聯網發展如火如荼,絕大部分公司都進行了數據庫拆分和服務化(SOA)。在這種情況下,完成某一個業務功能可能需要橫跨多個服務,操作多個數據庫。這就涉及到了分布式事務,用需要操作的資源位于多個資源服務器上,而應用需要保證對于多個資源服務器的數據的操作,要么全部成功,要么全部失敗。本質上來說,分布式事務就是為了保證不同資源服務器的數據一致性。

    最早的分布式事務應用架構很簡單,不涉及服務間的訪問調用,僅僅是服務內操作涉及到對多個數據庫資源的訪問,如下圖所示。

    當一個服務操作訪問不同的數據庫資源,又希望對它們的訪問具有事務特性時,就需要采用分布式事務來協調所有的事務參與者。

    典型的應用場景如分表分庫中的事務。通常一個庫數據量比較大或者預期未來的數據量比較大,都會進行水平拆分,也就是分庫分表。對于分庫分表的情況,一般開發人員都會使用一些數據庫中間件來降低SQL操作的復雜性。如:INSERT INTO user(id, name) VALUES (1,"張三"),(2,"李四");這條SQL是操作單庫的語法,單庫情況下,可以保證事務的一致性。但是由于現在進行了分庫分表,開發人員希望將1號記錄插入分庫1,2號記錄插入分庫2。所以數據庫中間件要將其改寫為2條SQL,分別插入兩個不同的分庫,此時要保證兩個庫要不都成功,要不都失敗,因此基本上所有的數據庫中間件都面臨著分布式事務的問題。

    對于上面介紹的分布式事務應用架構,盡管一個服務操作會訪問多個數據庫資源,但是畢竟整個事務還是控制在單一服務的內部。如果一個服務操作需要調用另外一個服務,這時的事務就需要跨越多個服務了。在這種情況下,起始于某個服務的事務在調用另外一個服務的時候,需要以某種機制流轉到另外一個服務,從而使被調用的服務訪問的資源也自動加入到該事務當中來。下圖反映了這樣一個跨越多個服務的分布式事務。

    舉個簡單的例子,一個公司之內,用戶的資產可能分為好多個部分,比如余額,積分,優惠券等等,如下圖所示。

    在公司內部有可能積分功能由一個微服務團隊維護,優惠券又是另外的團隊維護,這樣的話就無法保證積分扣減了之后,優惠券能否扣減成功。所以這里也需要使用分布式事務來控制。

    如果將上面這兩種場景(一個服務可以調用多個數據庫資源,也可以調用其他服務)結合在一起,對此進行延伸,整個分布式事務的參與者將會組成如下圖所示的樹形拓撲結構。在一個跨服務的分布式事務中,事務的發起者和提交均系同一個,它可以是整個調用的客戶端,也可以是客戶端最先調用的那個服務。

    上述討論的分布式事務場景中,無一例外的都直接或者間接的操作了多個數據庫。如何保證事務的ACID特性,對于分布式事務實現方案而言,是非常大的挑戰。同時,分布式事務實現方案還必須要考慮性能的問題,如果為了嚴格保證ACID特性,導致性能嚴重下降,那么對于一些要求快速響應的業務,是無法接受的。

    分布式事務一致性協議

    如果一個操作涉及多個分布式節點,為了保證事務的ACID特性,需要引入一個“協調者”組件來統一調度所有分布式節點的執行邏輯,這些被調度的分布式節點被稱為“參與者”。協調者負責調度參與者的行為,并最終決定這些參與者是否真正地提交事務。

    分布式事務通常采用二階段提交協議(2PC),它是幾乎所有分布式事務算法的基礎,后續的分布式事務算法幾乎都由此改進而來。

    2PC

    二階段提交(Two-phase Commit,簡稱2PC),是指為了使基于分布式系統架構下的所有節點在進行事務提交時保持一致性而設計的一種算法(Algorithm)。通常2PC也被稱為是一種協議(Protocol)。

    在此協議中,一個事務管理器(Transaction Manager,簡稱 TM,也被稱之為“協調者”)協調 1 個或多個資源管理器(Resource Manager,簡稱 RM,也被稱之為“參與者”)的活動,所有資源管理器(參與者)向事務管理器(協調者)匯報自身活動狀態,由事務管理器(協調者)根據各資源管理器(協調者)匯報的狀態(完成準備或準備失敗)來決定各資源管理器(協調者)是“提交”事務還是進行“回滾”操作。

    因此,二階段提交的算法思路可以概括為:參與者將操作成敗通知協調者,再由協調者根據所有參與者的反饋情報決定各參與者是否要提交操作還是中止操作。

    所謂的兩個階段是指:第一階段:準備階段(投票階段)和第二階段:提交階段(執行階段)。

    第一階段

    協調者通知各個參與者準備提交它們的事務分支。如果參與者判斷自己進行的工作可以被提交,那就對工作內容進行持久化,再給協調者肯定答復;要是發生了其他情況,那給協調者的都是否定答復。在發送了否定答復并回滾了已經的工作后,參與者就可以丟棄這個事務分支信息。

    以MySQL數據庫為例,在第一階段,事務管理器(協調者)向所有涉及到的數據庫服務器(參與者)發出Prepare "準備提交"請求,數據庫(參與者)收到請求后執行數據修改和日志記錄等處理,處理完成后只是把事務的狀態改成"可以提交",然后把結果返回給事務管理器(協調者)。

    第二階段

    協調者根據第一階段中各個參與者 Prepare的結果,決定是提交還是回滾事務。如果所有的參與者都Prepare成功,那么協調者通知所有的參與者進行提交;如果有參與者Prepare失敗的話,則協調者通知所有參與者回滾自己的事務分支。

    還是以MySQL數據庫為例,如果第一階段中所有數據庫(參與者)都Prepare成功,那么事務管理器(協調者)向數據庫服務器(參與者)發出"確認提交"請求,數據庫服務器(參與者)把事務的"可以提交"狀態改為"提交完成"狀態,然后返回應答。如果在第一階段內有任何一個數據庫(參與者)的操作發生了錯誤,或者事務管理器(協調者)收不到某個數據庫(參與者)的回應,則認為事務失敗,回撤所有數據庫(參與者)的事務。數據庫服務器(參與者)收不到第二階段的確認提交請求,也會把"可以提交"的事務回撤。

    2PC提交的優點是盡量保證了數據的強一致,但不是 100% 一致。但是2PC也有明顯的缺點:

    • 單點故障:由于協調者的重要性,一旦協調者發生故障,參與者會一直阻塞,尤其是在第二階段,協調者發生故障,那么所有的參與者都處于鎖定事務資源的狀態中,而無法繼續完成事務操作。

    • 同步阻塞:由于所有節點在執行操作時都是同步阻塞的,當參與者占有公共資源時,其他第三方節點訪問公共資源不得不處于阻塞狀態。

    • 數據不一致:在第二階段中,當協調者向參與者發送提交事務請求之后,發生了局部網絡異常或者在發送提交事務請求過程中協調者發生了故障,這會導致只有一部分參與者接收到了提交事務請求。而在這部分參與者接到提交事務請求之后就會執行提交事務操作。但是其他部分未接收到提交事務請求的參與者則無法提交事務。從而導致分布式系統中的數據不一致。

    二階段提交的問題:如果協調者在第二階段發送提交請求之后掛掉,而唯一接受到這條消息的參與者執行之后也掛掉了,即使協調者通過選舉協議產生了新的協調者并通知其他參與者進行提交或回滾操作的話,都可能會與這個已經執行的參與者執行的操作不一樣。

    3PC

    三階段提交(Three-phase Commit,簡稱3PC),是為解決2PC中的缺點而設計的。參考維基百科:https://en.wikipedia.org/wiki/Three-phasecommitprotocol。與兩階段提交不同的是,三階段提交是“非阻塞”協議。

    對應于2PC,3PC有兩個改動點:

  • 引入超時機制。同時在協調者和參與者中都引入超時機制。

  • 在兩階段提交的第一階段與第二階段之間插入了一個準備階段,使得原先在兩階段提交中,參與者在投票之后,由于協調者發生崩潰或錯誤而導致參與者處于無法知曉是否提交或者中止的“不確定狀態”所產生的可能相當長的延時的問題得以解決。

  • 第一階段CanCommit

    3PC的CanCommit階段其實和2PC的準備階段很像。協調者向參與者發送Commit請求,參與者如果可以提交就返回Yes響應,否則返回No響應。

  • 事務詢問:協調者向參與者發送CanCommit請求。詢問是否可以執行事務提交操作。然后開始等待參與者的響應。

  • 響應反饋:參與者接到CanCommit請求之后,正常情況下,如果其自身認為可以順利執行事務,則返回Yes響應,并進入預備狀態。否則反饋No。

  • 第二階段PreCommit

    協調者根據參與者的反應情況來決定是否可以進行事務的PreCommit操作。根據響應情況,有以下兩種可能。

    • 假如協調者從所有的參與者獲得的反饋都是Yes響應,那么就會執行事務的預執行。

      a) 發送預提交請求:協調者向參與者發送PreCommit請求,并進入Prepared階段。

      b) 事務預提交:參與者接收到PreCommit請求后,會執行事務操作,并將undo和redo信息記錄到事務日志中。

      c) 響應反饋:如果參與者成功的執行了事務操作,則返回ACK響應,同時開始等待最終指令。

    • 假如有任何一個參與者向協調者發送了No響應,或者等待超時之后,協調者都沒有接到參與者的響應,那么就執行事務的中斷。

      a) 發送中斷請求:協調者向所有參與者發送Abort請求。

      b) 中斷事務:參與者收到來自協調者的Abort請求之后(或超時之后,仍未收到協調者的請求),執行事務的中斷。

    第三階段DoCommit

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

    • Case 1:執行提交。

      a) 發送提交請求:協調者接收到參與者發送的ACK響應,那么他將從預提交狀態進入到提交狀態。并向所有參與者發送DoCommit請求。

      b) 事務提交:參與者接收到DoCommit請求之后,執行正式的事務提交。并在完成事務提交之后釋放所有事務資源。

      c) 響應反饋:事務提交完之后,向協調者發送ACK響應。

      d) 完成事務:協調者接收到所有參與者的ACK響應之后,完成事務。

    • Case 2:中斷事務。協調者沒有接收到參與者發送的ACK響應(可能是接受者發送的不是ACK響應,也可能響應超時),那么就會執行中斷事務。

      a) 發送中斷請求:協調者向所有參與者發送Abort請求。

      b) 事務回滾:參與者接收到Abort請求之后,利用其在階段二記錄的undo信息來執行事務的回滾操作,并在完成回滾之后釋放所有的事務資源。

      c) 反饋結果:參與者完成事務回滾之后,向協調者發送ACK消息。

      d) 中斷事務:協調者接收到參與者反饋的ACK消息之后,執行事務的中斷。

    在三階段提交中,如果在第三階段協調者發送提交請求之后掛掉,并且唯一的接受的參與者執行提交操作之后也掛掉了,這時協調者通過選舉協議產生了新的協調者。在二階段提交時存在的問題就是新的協調者不確定已經執行過事務的參與者是執行的提交事務還是中斷事務。但是在三階段提交時,肯定得到了第二階段的再次確認,那么第二階段必然是已經正確的執行了事務操作,只等待提交事務了。所以新的協調者可以從第二階段中分析出應該執行的操作,進行提交或者中斷事務操作,這樣即使掛掉的參與者恢復過來,數據也是一致的。

    所以,三階段提交解決了二階段提交中存在的由于協調者和參與者同時掛掉可能導致的數據一致性問題和單點故障問題,并減少阻塞。因為一旦參與者無法及時收到來自協調者的信息之后,他會默認執行提交事務,而不會一直持有事務資源并處于阻塞狀態。

    不過3PC也存在自身的問題:在提交階段如果發送的是中斷事務請求,但是由于網絡問題,導致部分參與者沒有接到請求。那么參與者會在等待超時之后執行提交事務操作,這樣這些由于網絡問題導致提交事務的參與者的數據就與接受到中斷事務請求的參與者存在數據不一致的問題。所以無論是 2PC 還是 3PC 都不能保證分布式系統中的數據 100% 一致。

    分布式事務解決方案

    預告目錄如下(明日放):

    想知道更多?描下面的二維碼關注我

    歡迎跳轉到本文的原文鏈接:https://honeypps.com/architect/introduction-of-distributed-transaction/

    后臺回復”加群“獲取公眾號專屬群聊入口

    ?

    【精彩推薦】

    • Paxos、Raft不是一致性算法嘛?

    • 越說越迷糊的CAP

    • 面試官居然問我Raft為什么會叫做Raft!

    • 面試官給我挖坑:URI中的//有什么用

    • 網關Zuul科普

    • 網關Spring Cloud?Gateway科普

    >>>?字節跳動社招內推入口?<<<

    >>> 字節跳動校招內推入口 <<<

    朕已閱?

    超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

    總結

    以上是生活随笔為你收集整理的分布式事务科普(初识篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。