1、事務簡介
- 事務(
Transaction
)是訪問并可能更新數據庫中各種數據項的一個程序執行單元(unit)。 - 在關系數據庫中,一個事務由一組SQL語句組成。事務應該具有ACID四個特性:
原子性
、一致性
、隔離性
、持久性
。 - 任何事務機制在實現時,都應該考慮事務的ACID特性,包括:本地事務、分布式事務。
1.1 本地事務
@Transational
- 大多數場景下,我們的應用都只需要操作單一的數據庫,這種情況下的事務稱之為本地事務(
Local Transaction
)。本地事務的ACID特性是數據庫直接提供支持。 - 本地事務應用架構如下所示:
- 大多數場景下,我們的應用都只需要操作單一的數據庫,這種情況下的事務稱之為本地事務(
- 在JDBC編程中,我們通過java.sql.Connection對象來開啟、關閉或者提交事務。
- 代碼如下所示:
Connection conn = ... //獲取數據庫連接 conn.setAutoCommit(false); //開啟事務 try{//...執行增刪改查sqlconn.commit(); //提交事務 }catch (Exception e) {conn.rollback();//事務回滾 }finally{conn.close();//關閉鏈接
- 代碼如下所示:
1.2 分布式事務典型場景
- 當下互聯網發展如火如荼,絕大部分公司都進行了數據庫拆分和服務化(SOA)。在這種情況下,完成某一個業務功能可能需要橫跨多個服務,操作多個數據庫。
- 這就涉及到了分布式事務,需要操作的資源位于多臺資源服務器上,而應用需要保證對于多個資源服務器數據的操作,要么全部成功,要么全部失敗。本質上來說,分布式事務就是為了保證不同資源服務器的數據一致性。
1.2.1 跨庫事務
- 跨庫事務指的是,應用的某個功能需要操作多個庫,不同的庫中存儲這不同的業務數據。
- 下圖演示了一個服務同時操作兩個庫的情況:
1.2.2 分庫分表
- 通常一個庫數據量比較大或者預期未來的數據量比較大,都會進行水平拆分,也就是分庫分表。
- 如下圖,將數據庫B拆分成了2個庫:
- 對于分庫分表的情況,一般開發人員都會使用一些數據庫中間件來降低sql操作的復雜性
- 例如,對于sql:
insert into user(id,name) values (1,"張三"),(2,"李四")
。這條sql是操作單庫的語法,單庫情況下,可以保證事務的一致性。 - 但是由于現在進行了分庫分表,開發人員希望將1號記錄插入分庫1,2號記錄插入分庫2。所以數據庫中間件要將其改寫為2條sql,分別插入兩個不同的分庫,此時要保證兩個庫要不都成功,要不都失敗,因此基本上所有的數據庫中間件都面臨著分布式事務的問題。
1.2.3 服務化
- 微服務架構是目前比較比較火的概念。例如:某個應用同時操作了9個數據庫,這樣的應用業務邏輯必然非常復雜,對于開發人員是極大的挑戰,應該拆分成不同的獨立服務,以簡化業務邏輯。拆分后,獨立服務之間通過RPC框架來進行遠程調用,實現彼此的通信。
- 下圖演示了3個服務之間彼此調用的架構:
- Service A完成某個功能需要直接操作數據庫,同時又需要調用Service B和Service C,而Service B又同時操作了2個數據庫,Service C也操作了一個庫。需要保證這些跨服務的對多個數據庫的操作要不都成功,要不都失敗,實際上這可能是最典型的分布式事務場景。
2、分布式事務理論基礎
- 解決分布式事務,也有相應的規范和協議。分布式事務相關的協議有
2PC
、3PC
。 - 由于
3PC
(三階段提交協議)非常難實現,目前市面主流的分布式事務解決方案都是2PC
協議。 - 有些文章在分析2PC時,幾乎都會用TCC兩階段的例子,第一階段try,第二階段完成confirm或cancel。其實2PC并不是專為實現TCC設計的,2PC具有普適性——協議一樣的存在,目前絕大多數分布式解決方案都是以
2PC
(兩階段提交協議)為基礎的。 - TCC(
Try-Confirm-Cancel
) 實際上是服務化的兩階段提交協議。
2.1 2PC兩階段提交協議
- 2PC(
Two-Prepare-Commit
):分為Prepare(預處理)和Commit(提交)兩個階段。- Prepare:提交事務請求
- 首先:協調者向所有參與者發送事務請求,詢問是否可以執行事務操作,然后等待各個參與者的響應;
- 然后:各個參與者接收到協調者事務請求后,執行事務操作(例如:更新一個關系型數據庫表中的記錄),并將 Undo(更新前的狀態) 和 Redo(更新后的狀態) 信息記錄事務日志中。
- 最后:如果參與者成功執行了事務并寫入 Undo 和 Redo 信息,則向協調者返回 YES 響應,否則返回 NO 響應。當然,參與者也可能宕機,從而不會返回響應。
- Commit:執行事務提交/回滾
- 正常提交事務:
- 首先:協調者向所有參與者發送 Commit 請求。
- 然后:參與者收到 Commit 請求后,執行事務提交,提交完成后釋放事務執行期占用的所有資源。
- 其次:參與者執行事務提交后向協調者發送 Ack 反饋響應。
- 最后:協調者接收到所有參與者的 Ack 響應后,完成事務提交。
- 回滾事務:
- 在執行 Prepare 步驟過程中,如果某些參與者執行事務失敗、宕機或與協調者之間的網絡中斷,那么協調者就無法收到所有參與者的 YES 響應,或者某個參與者返回了 No 響應,此時,協調者就會進入回退流程,對各個參與者的事務進行回滾操作。
- 首先:協調者向所有參與者發送 Rollback 請求。
- 然后:參與者收到 Rollback 后,使用 Prepare 階段的 Undo 日志執行事務回滾,完成后釋放事務執行期占用的所有資源。
- 其次: 參與者執行事務回滾后向協調者發送 Ack 響應。
- 最后:接收到所有參與者的 Ack 響應后,完成事務回滾。
- 在執行 Prepare 步驟過程中,如果某些參與者執行事務失敗、宕機或與協調者之間的網絡中斷,那么協調者就無法收到所有參與者的 YES 響應,或者某個參與者返回了 No 響應,此時,協調者就會進入回退流程,對各個參與者的事務進行回滾操作。
- Prepare:提交事務請求
2.2 2PC存在的問題
同步阻塞
:參與者在等待協調者的指令時,其實是在等待其他參與者的響應,在此過程中,參與者是無法進行其他操作的,也就是阻塞了其運行。 倘若參與者與協調者之間網絡異常導致參與者一直收不到協調者信息,那么會導致參與者一直阻塞下去。單點障礙
:在 2PC 中,一切請求都來自協調者,所以協調者的地位是至關重要的,如果協調者宕機,那么就會使參與者一直阻塞并一直占用事務資源。數據不一致
:Commit 事務過程中,Commit 請求 或 Rollback 請求可能會因為協調者宕機或協調者與參與者網絡問題丟失,那么就導致了部分參與者沒有收到 Commit/Rollback 請求,而其他參與者則正常收到執行了 Commit/Rollback 操作,沒有收到請求的參與者則繼續阻塞。這時,參與者之間的數據就不再一致了。環境可靠性依賴
:協調者 Prepare 請求發出后,等待響應,然而如果有參與者宕機或與協調者之間的網絡中斷,都會導致協調者無法收到所有參與者的響應,那么在 2PC 中,協調者會等待一定時間,然后超時后,會觸發事務中斷,在這個過程中,協調者和所有其他參與者都是出于阻塞的。這種機制對網絡問題常見的現實環境來說太苛刻了。
3、分布式事務實現的4種模式
3.1 AT模式(auto transcation)
- AT 模式是一種無侵入的分布式事務解決方案,阿里的Seata框架,實現了該模式。
- 在 AT 模式下,用戶只需關注自己的“業務 SQL”,用戶的 “業務 SQL” 作為一階段,Seata 框架會自動生成事務的二階段進行提交和回滾操作。
- AT 模式如何做到對業務的無侵入 :
- AT 模式的一階段、二階段提交和回滾均由 Seata 框架自動生成,用戶只需編寫“業務 SQL”,便能輕松接入分布式事務,AT 模式是一種對業務無任何侵入的分布式事務解決方案。
- 一階段:
- 在一階段,Seata 會攔截“業務 SQL”,首先解析 SQL 語義,找到“業務 SQL”要更新的業務數據,在業務數據被更新前,將其保存成
before image
,然后執行“業務 SQL”更新業務數據,在業務數據更新之后,再將其保存成after image
,最后生成行鎖。 - 以上操作全部都在一個數據庫事務內完成,這樣保證了一階段操作的原子性。
- 在一階段,Seata 會攔截“業務 SQL”,首先解析 SQL 語義,找到“業務 SQL”要更新的業務數據,在業務數據被更新前,將其保存成
- 二階段提交:
- 二階段如果是提交的話,因為“業務 SQL”在一階段已經提交至數據庫, 所以 Seata 框架只需將一階段保存的快照數據和行鎖刪掉,完成數據清理即可。
- 二階段如果是提交的話,因為“業務 SQL”在一階段已經提交至數據庫, 所以 Seata 框架只需將一階段保存的快照數據和行鎖刪掉,完成數據清理即可。
- 二階段回滾:
- 二階段如果是回滾的話,Seata 就需要回滾一階段已經執行的“業務 SQL”,還原業務數據。回滾方式便是用“before image”還原業務數據;但在還原前要首先要校驗臟寫,對比“數據庫當前業務數據”和 “after image”,如果兩份數據完全一致就說明沒有臟寫,可以還原業務數據,如果不一致就說明有臟寫,出現臟寫就需要轉人工處理。
- 二階段如果是回滾的話,Seata 就需要回滾一階段已經執行的“業務 SQL”,還原業務數據。回滾方式便是用“before image”還原業務數據;但在還原前要首先要校驗臟寫,對比“數據庫當前業務數據”和 “after image”,如果兩份數據完全一致就說明沒有臟寫,可以還原業務數據,如果不一致就說明有臟寫,出現臟寫就需要轉人工處理。
3.2 TCC 模式
- 侵入性比較強, 并且需要自己實現相關事務控制邏輯;
- 在整個過程基本沒有鎖,性能更強;
- TCC 模式需要用戶根據自己的業務場景實現
Try
、Confirm
和Cancel
三個操作;- 事務發起方在一階段執行 Try 方法,
- 在二階段提交執行 Confirm 方法,二階段回滾執行 Cancel 方法。
- TCC 三個方法描述:
- Try:資源的檢測和預留;
- Confirm:執行的業務操作提交,要求 Try 成功 Confirm 一定要能成功;
- Cancel:預留資源的釋放;
- TCC的實踐經驗:
- 螞蟻金服TCC實踐,總結以下注意事項:
- 業務模型分2階段設計
- 并發控制
- 允許空回滾
- 防懸掛控制
- 冪等控制
- 1、TCC 設計 – 業務模型分 2 階段設計:
- 用戶接入 TCC 模式,最重要的事情就是考慮如何將業務模型拆成 2 階段,實現成 TCC 的 3 個方法,并且保證 Try 成功 Confirm 一定能成功。相對于 AT 模式,TCC 模式對業務代碼有一定的侵入性,但是 TCC 模式無 AT 模式的全局行鎖,TCC 性能會比 AT 模式高很多。
- 以“扣錢”場景為例,在接入 TCC 前,對 A 賬戶扣錢,只需一條更新賬戶余額的 SQL 便能完成;但是在接入 TCC 之后,用戶就需要考慮如何將原來一步就能完成的扣錢操作,拆成兩階段,實現成三個方法,并且保證一階段 Try 成功的話 二階段 Confirm 一定能成功。
- 如上圖所示,Try 方法作為一階段的準備方法,需要做資源的檢查和預留。在扣錢場景下,Try 要做的事情是就是檢查賬戶余額是否充足,預留轉賬資金,預留的方式就是凍結 A 賬戶的 轉賬資金。Try 方法執行之后,賬號 A 余額雖然還是 100,但是其中 30 元已經被凍結了,不能被其他事務使用。
- 二階段 Confirm 方法執行真正的扣錢操作。Confirm 會使用 Try 階段凍結的資金,執行賬號扣款。Confirm 方法執行之后,賬號 A 在一階段中凍結的 30 元已經被扣除,賬號 A 余額變成 70 元 。
- 如果二階段是回滾的話,就需要在 Cancel 方法內釋放一階段 Try 凍結的 30 元,使賬號 A 的回到初始狀態,100 元全部可用。
- 2、TCC 設計 – 允許空回滾:
- Cancel 接口設計時需要允許空回滾。在 Try 接口因為丟包時沒有收到,事務管理器會觸發回滾,這時會觸發 Cancel 接口,這時 Cancel 執行時發現沒有對應的事務 xid 或主鍵時,需要返回回滾成功。讓事務服務管理器認為已回滾,否則會不斷重試,而 Cancel 又沒有對應的業務數據可以進行回滾。
- 3、TCC 設計 – 防懸掛控制:
- 懸掛的意思是:Cancel 比 Try 接口先執行,出現的原因是 Try 由于網絡擁堵而超時,事務管理器生成回滾,觸發 Cancel 接口,而最終又收到了 Try 接口調用,但是 Cancel 比 Try 先到。按照前面允許空回滾的邏輯,回滾會返回成功,事務管理器認為事務已回滾成功,則此時的 Try 接口不應該執行,否則會產生數據不一致,所以我們在 Cancel 空回滾返回成功之前先記錄該條事務 xid 或業務主鍵,標識這條記錄已經回滾過,Try 接口先檢查這條事務xid或業務主鍵如果已經標記為回滾成功過,則不執行 Try 的業務操作。
- 4、TCC 設計 – 冪等控制:
- 冪等性的意思是:對同一個系統,使用同樣的條件,一次請求和重復的多次請求對系統資源的影響是一致的。因為網絡抖動或擁堵可能會超時,事務管理器會對資源進行重試操作,所以很可能一個業務操作會被重復調用,為了不因為重復調用而多次占用資源,需要對服務設計時進行冪等控制,通常我們可以用事務 xid 或業務主鍵判重來控制。
- 螞蟻金服TCC實踐,總結以下注意事項:
3.3 Saga模式
- Saga 理論出自 Hector & Kenneth 1987發表的論文 Sagas。
- Saga模式的實現,是長事務解決方案。
- Saga 是一種補償協議,在 Saga 模式下,分布式事務內有多個參與者,每一個參與者都是一個沖正補償服務,需要用戶根據業務場景實現其正向操作和逆向回滾操作。
- 分布式事務執行過程中,依次執行各參與者的正向操作,如果所有正向操作均執行成功,那么分布式事務提交。如果任何一個正向操作執行失敗,那么分布式事務會退回去執行前面各參與者的逆向回滾操作,回滾已提交的參與者,使分布式事務回到初始狀態。
- Saga 正向服務與補償服務也需要業務開發者實現。因此是業務入侵的。
- Saga 模式下分布式事務通常是由事件驅動的,各個參與者之間是異步執行的,Saga 模式是一種長事務解決方案。
- Saga 模式使用場景:
- Saga 模式適用于業務流程長且需要保證事務最終一致性的業務系統,Saga 模式一階段就會提交本地事務,無鎖、長流程情況下可以保證性能。
- 事務參與者可能是其它公司的服務或者是遺留系統的服務,無法進行改造和提供 TCC 要求的接口,可以使用 Saga 模式。
- Saga模式的優勢是:
- 一階段提交本地數據庫事務,無鎖,高性能;
- 參與者可以采用事務驅動異步執行,高吞吐;
- 補償服務即正向服務的“反向”,易于理解,易于實現;
3.4 XA模式
- XA是X/Open DTP組織(X/Open DTP group)定義的兩階段提交協議,XA被許多數據庫(如Oracle、DB2、SQL Server、MySQL)和中間件等工具(如CICS 和 Tuxedo)本地支持 。
- X/Open DTP模型(1994)包括應用程序(AP)、事務管理器(TM)、資源管理器(RM)。
- XA接口函數由數據庫廠商提供。XA規范的基礎是兩階段提交協議2PC。
- JTA(Java Transaction API) 是Java實現的XA規范的增強版 接口。
- 在XA模式下,需要有一個[全局]協調器,每一個數據庫事務完成后,進行第一階段預提交,并通知協調器,把結果給協調器。協調器等所有分支事務操作完成、都預提交后,進行第二步;
- 第二步:協調器通知每個數據庫進行逐個commit/rollback。
- 其中,這個全局協調器就是XA模型中的TM角色,每個分支事務各自的數據庫就是RM。
MySQL 提供的XA實現(https://dev.mysql.com/doc/refman/5.7/en/xa.html ) - XA模式下的 開源框架有atomikos,其開發公司也有商業版本。
- XA模式缺點:事務粒度大。高并發下,系統可用性低。因此很少使用。
3.5 (AT、TCC、Saga、XA)模式分析
- 四種分布式事務模式,分別在不同的時間被提出,每種模式都有它的適用場景。
- AT 模式是無侵入的分布式事務解決方案,適用于不希望對業務進行改造的場景,幾乎0學習成本。
- TCC 模式是高性能分布式事務解決方案,適用于核心系統等對性能有很高要求的場景。
- Saga 模式是長事務解決方案,適用于業務流程長且需要保證事務最終一致性的業務系統,Saga 模式一階段就會提交本地事務,無鎖,長流程情況下可以保證性能,多用于渠道層、集成層業務系統。事務參與者可能是其它公司的服務或者是遺留系統的服務,無法進行改造和提供 TCC 要求的接口,也可以使用 Saga 模式。
- XA模式是分布式強一致性的解決方案,但性能低而使用較少。
- 分布式事務本身就是一個技術難題,業務中具體使用哪種方案還是需要不同的業務特點自行選擇,但是我們也會發現,分布式事務會大大的提高流程的復雜度,會帶來很多額外的開銷工作,「代碼量上去了,業務復雜了,性能下跌了」。所以,當我們真實開發的過程中,能不使用分布式事務就不使用。