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