再談分布式事務,你了解多少?
前言
三年前,我寫了第一篇和分布式事務相關的文章再有人問你分布式事務,把這篇扔給他,后面陸續也寫了一些和分布式事務相關的文章:
- 如何能在實戰中完成分布式事務
- 深度剖析一站式分布式事務方案Seata-Server
- 深度剖析一站式分布式事務方案Seata-Cient
- 解密分布式事務框架-Fescar
時隔三年,回看之前的文章,之前的確也有很多漏掉的一些知識,所以今天在這里再次和大家總結下分布式事務相關的東西。
事務
首先還是先說一下事務的定義吧,事務的英語是transaction,我們查找詞典可以發現這個單詞的中文解釋是交易,買賣等含義,所以我們可以知道事務一定和交易密不可分他們才能共享一個英文單詞,而交易的定義是什么呢?有句俗話說得好,一手交錢,一手交貨,那這個就是交易的規則,而這個同時也是事務的定義。那么事務的官方定義是什么呢?
事務是一系列操作的集合,這些操作要么都做,要么都不做,是一個不可分割的工作單位,是數據庫環境中的最小工作單元。
可以發現這個基本和我們一手交錢,一手交貨很像,的確在現實的開發環境中,在交易的業務中對事務的保證特別看重,而一些社交類的業務,和資金關系不大的,比如點贊數,評論數,是不會對事務特別看重,這些應該關注的是性能。
事務的類型
之前的文章都沒有介紹過事務的類型相關,直到之前看了一篇文章,才知道事務是分了5種類型的,這里也介紹給大家:
扁平事務
我們日常使用的基本都是扁平事務,以begin開始,然后以commit 或者 rollback結束.
begin;
do xxxx;
commit/rollback;
帶保存點的扁平事務
增加了SavePoint機制,內存保存,如果數據庫宕機,savepoint將會丟失。
begin
insert into xxx
savepoint a
insert into yyy
rollback to a
這里的rollback to a只會回滾到保存點a這里,不會整個事務都回滾
鏈式事務
當我們提交事務后,相當于執行了 COMMIT AND CHAIN,也就是開啟一個鏈式事務,即當我們提交事務之后會開啟一個相同隔離級別的事務。如果回滾只會回滾當前節點。
通過下面sql語句可以再mysql里面查到當前是否開啟鏈式模式,以及如何開啟
select @@completion_type
set @@completion_type = 1 // 0無鏈式,
嵌套事務
- 嵌套事務可以是一棵樹,其中的葉節點可以使扁平事務,也可以是嵌套事務,但是都叫做子事務
- 某個節點回滾只影響當前節點下面所有的事務
- 子節點的提交是會根據父節點提交才會最后提交,也就說,所有事務的保存只能再最頂層提交,才會生效。
- mysql不支持嵌套事務,Oracle支持
分布式事務
通常是一個在分布式環境下運行的扁平事務,需要根據數據所在位置訪問網絡中的不同節點,一般來說對于分布式事務,其同樣需要滿足單機 ACID 特性,要么都發生,要么都失效。但是實際實現的情況,可能遠比理想的更為復雜,所以通常會降低要求。
單機事務
上面講了五種類型的事務,前4種其實都可以歸結為單機事務,而單機事務也是后端程序員中經常接觸到的,所以先簡單講講單機事務的核心關鍵點:
ACID
ACID是單機事務的四大特性,由這四個特性可以定義到底什么條件下才能算作事務。
- A:原子性——事務內的操作要么全部提交,要么全部回滾。
- C:一致性——一個事務執行之前和執行之后數據庫都必須處于一致性狀態(數據庫的數據要么處于事務前的狀態,要么處于事務后的狀態)。
- I:隔離性—— 在并發環境中,當不同的事務同時操縱相同的數據時,每個事務都有各自的完整數據空間。
- D:持久性——事務成功結束,它對數據庫所做的更新就必須永久保存下來。即使發生系統崩潰,重新啟動數據庫系統后,數據庫還能恢復到事務成功結束時的狀態
實現關鍵
這里只講一下mysql的一些實現關鍵:
整體來說事務的ACID是通過InnoDB日志和鎖來保證:。
- 事務的隔離性是通過數據庫鎖的機制實現的
- 持久性通過redolog(重做日志)來實現。
- 原子性通過Undolog來實現。
- 一致性依靠三面上個特性來實現
redolog,undolog,binlog通常會容易搞混淆,這里簡單說一下
undolog:用于回滾和mvcc,可以理解他用于記錄之前版本的數據。
redolog:數據庫為了加快刷盤速度,采用了WAL的方式,也就是先順序預寫日志,然后再異步去刷盤,而redolog就是順序寫日志的產物。
binlog:上面都是innodb引擎的日志,binlog是mysql-server的日志,用于主從同步等作用。
代碼使用
java程序員的話如果使用的是spring,那么使用本地事務會有兩種手段:
聲明式事務
其實就是直接加個注解,spring自己會做一個切面代理,然后使用事務,這種方法使用得應該也是最多的,因為他是最簡單的,但是說如果只想控制這個方法部分代碼進入事務,或者調用同一個類的方法使用事務,那就不太能使用這種方法。
編程式事務
編程式事務,實現起來稍顯麻煩,需要自己手寫很多代碼,但是能解決上面說的那個問題。這種方式更加靈活多變。
分布式事務
為什么需要分布式事務
我們會發現分布式事務在最近幾年里提到的聲音越來越多,這是究竟為什么呢?在《架構即未來》這本書中提到了AKF模型是軟件架構擴展的基礎模型
如上圖,如果我們要對一個軟件進行擴展,那么需要以AKF模型為基礎,從三個維度進行擴展。
- X軸:x軸的意思就是將以前的單機,變成集群,從臺機器,擴容成N臺。
- Y軸:以前的單體服務將所有業務代碼都寫在一個服務里面,而Y軸做的就是將這些業務模塊分開,拆分成微服務。
- Z軸:很多業務瓶頸在數據庫,通常我們可以做數據庫分庫分表,單元化等等
在Y軸中我們之前的單體服務被拆分成了多個服務,那就有可能以前一個事務的內容,可能出現在了多個服務中,比如一個訂單扣除積分和優惠券,之前是一個服務,如果拆分出來了有積分服務和優惠券服務,那么我們之前的本地事務就會失效,就需要引入分布式服務來保證一個訂單中的全部扣減都是成功的。
在Z軸中,如果我們做了分庫分表,也會破壞本地事務,如果大家都是一個庫自然還能使用分庫分表,但如果操作了不同庫那么就無法保證事務,那么也需要引入分布式事務。
而AKF又是現代軟件擴展的基礎模型,三者有其二可能都需要引入分布式事務,所以,如果要對軟件進行擴展,那么分布式事務必不可少。
分布式的理論知識
CAP和BASE的理論知識應該很多人都知道,但是我這里還是需要介紹一下,因為分布式事務離不開這兩個東西。
CAP
CAP定理,又被叫作布魯爾定理。CAP是分布式系統的入門理論。
- C (一致性):對某個指定的客戶端來說,讀操作能返回最新的寫操作。對于數據分布在不同節點上的數據上來說,如果在某個節點更新了數據,那么在其他節點如果都能讀取到這個最新的數據,那么就稱為強一致,如果有某個節點沒有讀取到,那就是分布式不一致。
- A (可用性):非故障的節點在合理的時間內返回合理的響應(不是錯誤和超時的響應)。可用性的兩個關鍵一個是合理的時間,一個是合理的響應。合理的時間指的是請求不能無限被阻塞,應該在合理的時間給出返回。合理的響應指的是系統應該明確返回結果并且結果是正確的,這里的正確指的是比如應該返回50,而不是返回40。
- P (分區容錯性):當出現網絡分區后,系統能夠繼續工作。打個比方,這里個集群有多臺機器,有臺機器網絡出現了問題,但是這個集群仍然可以正常工作。
BASE
BASE 是 Basically Available(基本可用)、Soft state(軟狀態)和 Eventually consistent (最終一致性)三個短語的縮寫。是對CAP中AP的一個擴展。
1.基本可用:分布式系統在出現故障時,允許損失部分可用功能,保證核心功能可用。
2.軟狀態:允許系統中存在中間狀態,這個狀態不影響系統可用性,這里指的是CAP中的不一致。
3.最終一致:最終一致是指經過一段時間后,所有節點數據都將會達到一致。
BASE解決了CAP中理論沒有網絡延遲,在BASE中用軟狀態和最終一致,保證了延遲后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的強一致性模型,而是通過犧牲強一致性來獲得可用性,并允許數據在一段時間內是不一致的,但最終達到一致狀態。
分布式事務解決方案
剛性事務
剛性事務追求的是強一致性事務。
DTP/XA
DTP的XA規范(全稱為Distributed Transaction Processing The XA Specification)的制定者是X/Open,即現在的Open Group。
熟悉數據庫自帶的分布式事務支持的同學,其實就知道這個就是XA,
這里的AP你可以理解成是我們自己的業務服務,RM則是我們使用的數據庫通常都是使用mysql而mysql也自帶支持XA,TM可以是一個單獨的服務,也可以是我們自己的業務服務承擔,他的作用是做事務管理。如果是用mysql的話,使用XA有如下代碼:
XA BEGIN '123';
insert into xxx;
XA END '123'; // 鏈接斷開會失去這次事務
XA PREPARE '123'; // 二階段準備會持久化
XA COMMIT '123'; //prepare全部成功/或者有一個失敗就回滾
當然XA其實有很多缺點:
1.數據鎖定:數據在整個事務處理過程結束前,都被鎖定,讀寫都按隔離級別的定義約束起來。
2.協議阻塞:XA prepare 后,分支事務進入阻塞階段,收到 XA commit 或 XA rollback 前必須阻塞等待。
3.性能差:性能的損耗主要來自兩個方面:一方面,事務協調過程,增加單個事務的 RT;另一方面,并發事務數據的鎖沖突,降低吞吐。
柔性事務
因為剛性事務的實現成本較大,對于現在互聯網的業務來說很多不愿意承受這么大的成本性能損失,愿意犧牲一定的一致性來保證性能,所以我們這里叫做柔性事務。
消息最終一致
適用于很多異步任務,在我們的場景中,比如異步審核筆記,異步發放積分(適用于加法的業務)。而消息最終一致也有兩種實現方法。
消息表
qmq實現事務消息的方法是利用的消息表。首先我們需要有一張這樣的消息表,用來和我們業務在同一個事務里面一起保存。
CREATE TABLE `msg_queue` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
`topic` varchar(64) NOT NULL,
`nameServer` varchar(64) NOT NULL,
`status` smallint(6) NOT NULL DEFAULT '0' COMMENT '消息狀態',
`error` int unsigned NOT NULL DEFAULT '0' COMMENT '錯誤次數',
`create_time` datetime NOT NULL COMMENT '創建時間',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='記錄業務系統消息';
從上面圖上可以看出,我們會有單獨的task在不斷掃描我們的消息表,如果消息表沒有被刪除掉,代表之前沒有發送成功,那么我們需要做發送,這里需要說的是,我們一定要保證這個消息是冪等的,因為這里的發送完之后刪除消息,并不能保證數據是一致的,有可能一個消息會發送多次,最后才能被刪掉。
使用的代碼如下:
@Transactional
public void pay(Order order){
saveOrder(order);
messageProducer.sendMessage(buildMessage(order)); //要寫在最后
}
rocketmq事務消息
rockemq的事務消息如圖上所示分為四個階段:
- 第一階段:先發送一個prepare消息,會獲取到一個prepare的消息id
- 第二階段:執行本地事務,如果執行成功,則發送commit/失敗則rollback。
- 第三階段:rocketmq-server根據消息結果,如果成功就投遞給consumer,不成功則把消息刪除掉。
- 第四階段: 如果二階段沒有上報消息結果,那就需要進行回查。
最大努力通知
適合于開放平臺,外部的第三方系統想要保證最終一致。比如微信,支付寶的開放平臺。下面我貼一個微信支付平臺的執行流程:
從上面可以看到,最大努力通知需要保證兩點:
- 有限次數的重試,一般重試策略采用指數退避
- 需要提供查詢接口,來防止通知失敗。
TCC
做支付的同學比較常用的,雖然是柔性事物,但是目標是有剛性的效果。隔離性比較強。
舉個例子:有積分服務,券服務,余額服務,如果用戶一次訂單想同時扣減這三個怎么能保證。用其他的模式可以嗎?
消息最終一致和最大努力通知,都不太適合,無法保證隔離型,用戶重復使用資產。XA性能差。
所以這里我們選擇使用了TCC,分三個方法:
- try: 鎖定,通常用一個字段或者記錄。(演化成saga直接try commit合并)
- commit: 提交資源
- cancel:釋放資源
SAGA
Saga是30年前一篇數據庫倫理提到的一個概念。其核心思想是將長事務拆分為多個本地短事務,由Saga事務協調器協調,如果正常結束那就正常完成,如果某個步驟失敗,則根據相反順序一次調用補償操作。
- 適用于無法提供TCC接口(遺留系統,外部系統),一般來說提供提交 和 回滾接口即可 ,這里的可以看做業務上的接口,生成訂單 對應 的刪除訂單就是回滾,不需要單獨命名回滾接口。
- 不看隔離性,一階段就生效
- 想異步執行
- 想支持正向重試(tcc,try 為什么不能正向重試,資源一直被業務隔離,需要釋放隔離性)
SEATA
當然上面介紹了很多種分布式事務,有同學會想,說了這么多但是我該怎么實現呢?那我在這里推薦使用seata,seata 是一款開源的分布式事務解決方案,致力于在微服務架構下提供高性能和簡單易用的分布式事務服務。為用戶提供了 AT、TCC、SAGA 和 XA 事務模式。
有興趣的可以訪問seata官網:https://seata.io/zh-cn 。我這里就不具體介紹了,或者看我之前的文章也有很多介紹。
最后
時隔這么久,再次寫了一下關于分布式事務相關的,這次算對之前的是補充,當然也算是對分布式事務的總結。希望大家能在自己的業務中能找到合適自己的分布式事務的方法。