一受神、什么是事務(wù)
事務(wù)是將一次執(zhí)行過程中所涉及的所有操作納入到一個不可分割的執(zhí)行單元,組成事務(wù)的所有操作只有在所有操作均能正常執(zhí)行的情況下才能提交格侯,只要其中任一操作執(zhí)行失敗弛针,都將導(dǎo)致整個事務(wù)的回滾履磨。一句話來說耕皮,就是保證多個操作要么都做仑濒,要么都不做。同時一旦事務(wù)提交朝墩,則其所做的修改會永久保存到數(shù)據(jù)庫醉拓。
二、事務(wù)的四個特性(ACID)
- A:原子性(Atomicity)
一個事務(wù)(transaction)中的所有操作收苏,要么全部完成亿卤,要么全部不完成,不會結(jié)束在中間某個環(huán)節(jié)倒戏。事務(wù)在執(zhí)行過程中發(fā)生錯誤怠噪,會被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個事務(wù)從來沒有執(zhí)行過一樣杜跷。 - C:一致性(Consistency)
事務(wù)的一致性指的是在一個事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫都必須處于一致性狀態(tài)。如果事務(wù)成功地完成矫夷,那么系統(tǒng)中所有變化將正確地應(yīng)用葛闷,系統(tǒng)處于有效狀態(tài)。如果在事務(wù)中出現(xiàn)錯誤双藕,那么系統(tǒng)中的所有變化將自動地回滾淑趾,系統(tǒng)返回到原始狀態(tài)。 - I:隔離性(Isolation)
指的是在并發(fā)環(huán)境中忧陪,當(dāng)不同的事務(wù)同時操縱相同的數(shù)據(jù)時扣泊,每個事務(wù)都有各自的完整數(shù)據(jù)空間近范。由并發(fā)事務(wù)所做的修改必須與任何其他并發(fā)事務(wù)所做的修改隔離。事務(wù)查看數(shù)據(jù)更新時延蟹,數(shù)據(jù)所處的狀態(tài)要么是另一事務(wù)修改它之前的狀態(tài)评矩,要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會查看到中間狀態(tài)的數(shù)據(jù)阱飘。 - D:持久性(Durability)
指的是只要事務(wù)成功結(jié)束斥杜,它對數(shù)據(jù)庫所做的更新就必須永久保存下來。即使發(fā)生系統(tǒng)崩潰沥匈,重新啟動數(shù)據(jù)庫系統(tǒng)后蔗喂,數(shù)據(jù)庫還能恢復(fù)到事務(wù)成功結(jié)束時的狀態(tài)。
三高帖、InnoDB 事務(wù)實(shí)現(xiàn)
基于衡量事務(wù)的四個特性缰儿,InnoDB 實(shí)現(xiàn)事務(wù)實(shí)際上就是 4 個特性的實(shí)現(xiàn)。
-
原子性
- 在 MySQL 中有很多類型的日志散址,二進(jìn)制日志乖阵、查詢?nèi)罩尽㈠e誤日志爪飘、慢查詢?nèi)罩镜鹊纫迤稹3诉@些日志,還提供了兩種事務(wù)日志师崎,redo log 用來保證持久性默终, undo log 是原子性和隔離性實(shí)現(xiàn)的基礎(chǔ)。
- 數(shù)據(jù)庫每執(zhí)行一條更新數(shù)據(jù)的 sql 就會生成一條 undo log犁罩,比如 insert 一條數(shù)據(jù)齐蔽,就會生出一條 delete 的 undo log。如果事務(wù)執(zhí)行失敗或者調(diào)用 rollback 就可以根據(jù) undo log 做數(shù)據(jù)回滾床估。
-
隔離性
- 隔離性是指含滴,事務(wù)內(nèi)部的操作與其他事務(wù)是隔離的,并發(fā)執(zhí)行的各個事務(wù)之間不能互相干擾丐巫。嚴(yán)格的隔離性谈况,對應(yīng)了事務(wù)隔離級別中的Serializable (可串行化),但實(shí)際應(yīng)用中出于性能方面的考慮很少會使用可串行化递胧。
- InnoDB 采用可重復(fù)讀隔離級別碑韵,使用 MVCC 和行鎖、間隙鎖實(shí)現(xiàn)隔離性缎脾。
-
持久性
- InnoDB作為MySQL的存儲引擎祝闻,數(shù)據(jù)是存放在磁盤中的,但如果每次讀寫數(shù)據(jù)都需要磁盤IO遗菠,效率會很低联喘。為此华蜒,InnoDB提供了緩存(Buffer Pool),Buffer Pool中包含了磁盤中部分?jǐn)?shù)據(jù)頁的映射豁遭,作為訪問數(shù)據(jù)庫的緩沖:當(dāng)從數(shù)據(jù)庫讀取數(shù)據(jù)時叭喜,會首先從Buffer Pool中讀取,如果Buffer Pool中沒有堤框,則從磁盤讀取后放入Buffer Pool域滥;當(dāng)向數(shù)據(jù)庫寫入數(shù)據(jù)時,會首先寫入Buffer Pool蜈抓,Buffer Pool中修改的數(shù)據(jù)會定期刷新到磁盤中(這一過程稱為刷臟)启绰。
- Buffer Pool的使用大大提高了讀寫數(shù)據(jù)的效率,但是也帶了新的問題:如果MySQL宕機(jī)沟使,而此時Buffer Pool中修改的數(shù)據(jù)還沒有刷新到磁盤委可,就會導(dǎo)致數(shù)據(jù)的丟失,事務(wù)的持久性無法保證腊嗡。
- 于是着倾,redo log被引入來解決這個問題:當(dāng)數(shù)據(jù)修改時,除了修改Buffer Pool中的數(shù)據(jù)燕少,還會在redo log記錄這次操作卡者;當(dāng)事務(wù)提交時,會調(diào)用fsync接口對redo log進(jìn)行刷盤客们。如果MySQL宕機(jī)崇决,重啟時可以讀取redo log中的數(shù)據(jù),對數(shù)據(jù)庫進(jìn)行恢復(fù)底挫。redo log采用的是WAL(Write-ahead logging恒傻,預(yù)寫式日志),所有修改先寫入日志建邓,再更新到Buffer Pool盈厘,保證了數(shù)據(jù)不會因MySQL宕機(jī)而丟失,從而滿足了持久性要求官边。
既然redo log也需要在事務(wù)提交時將日志寫入磁盤沸手,為什么它比直接將Buffer Pool中修改的數(shù)據(jù)寫入磁盤(即刷臟)要快呢?主要有以下兩方面的原因:
(1)刷臟是隨機(jī)IO注簿,因?yàn)槊看涡薷牡臄?shù)據(jù)位置隨機(jī)罐氨,但寫redo log是追加操作,屬于順序IO滩援。
(2)刷臟是以數(shù)據(jù)頁(Page)為單位的,MySQL默認(rèn)頁大小是16KB塔嬉,一個Page上一個小修改都要整頁寫入玩徊;而redo log中只包含真正需要寫入的部分租悄,無效IO大大減少。 -
一致性
- 一致性是指事務(wù)執(zhí)行結(jié)束后恩袱,數(shù)據(jù)庫的完整性約束沒有被破壞泣棋,事務(wù)執(zhí)行的前后都是合法的數(shù)據(jù)狀態(tài)。
- 一致性不僅由數(shù)據(jù)庫本身來保證畔塔,同時業(yè)務(wù)系統(tǒng)也保證數(shù)據(jù)的一致性潭辈。
四、分布式事務(wù)的由來
現(xiàn)代軟件架構(gòu)隨著業(yè)務(wù)領(lǐng)域劃分為多個微服務(wù)澈吨,共同組成了復(fù)雜的軟件系統(tǒng)把敢。而從數(shù)據(jù)庫層面來看,隨著數(shù)據(jù)量的爆發(fā)谅辣,不得不采用分庫分表的方式修赞,降低數(shù)據(jù)庫的壓力。這樣桑阶,就造成多個服務(wù)依賴不同的數(shù)據(jù)庫柏副,那么在同時操作的時候,如何保證事務(wù)蚣录?這就是分布式事務(wù)割择。
簡而言之,分布式事務(wù)就是一個大的事務(wù)由不同的子事務(wù)組成萎河,這些小的事務(wù)操作分布在不同的服務(wù)器節(jié)點(diǎn)上面荔泳,屬于不同的微服務(wù),分布式事務(wù)需要保證同一事務(wù)下的子事務(wù)要么全部成功公壤,要么全部失敗换可,即保證數(shù)據(jù)的最終一致性。
五厦幅、分布式事務(wù)解決方案
在這篇不想用太大的篇幅說一些概念上的東西沾鳄,但是要說 RocketMQ 的分布式事務(wù)實(shí)現(xiàn),所以在這里順便提一下當(dāng)前分布式事務(wù)的集中解決方案:
-
兩階段提交(2PC)
兩階段提交(2PC) 是 Oracle Tuxedo 系統(tǒng)提出的 XA 分布式事務(wù)協(xié)議的其中一種實(shí)現(xiàn)方式确憨,參考 《分布式事務(wù)之兩階段提交(2PC)》 译荞。
-
Try-Confirm-Cancle (TCC)
TCC 是基于嘗試、確認(rèn)休弃、取消來實(shí)現(xiàn)分布式事務(wù)的吞歼,想了解更多,參考 《分布式事務(wù)之補(bǔ)償事務(wù)( TCC )》 塔猾。
-
本地消息表
本地消息表 方案最初是ebay提出的篙骡,核心是將需要分布式處理的任務(wù)通過消息日志的方式來異步執(zhí)行。消息日志可以存儲到本地文本、數(shù)據(jù)庫或消息隊(duì)列糯俗,再通過業(yè)務(wù)規(guī)則自動或人工發(fā)起重試尿褪。人工重試更多的是應(yīng)用于支付場景,通過對賬系統(tǒng)對事后問題的處理得湘。
除了上述外杖玲,還有一些解決方案,比如阿里 SEATA 淘正,SAGA方案和最大努力通知...感興趣同學(xué)們可以自行了解,當(dāng)然還有我們這篇要說的 MQ 事務(wù)摆马。
六、MQ 事務(wù)
RocketMQ 是阿里開源的一款高性能鸿吆、高吞吐量的分布式消息中間件囤采,基于消息異步方式提供了對分布式事務(wù)的支持,實(shí)現(xiàn)事務(wù)最終一致性伞剑。
下面是 RocketMQ 事務(wù)消息的基本流程交互圖:
如圖其中分為兩個流程:正常事務(wù)消息的發(fā)送及提交斑唬、事務(wù)消息的補(bǔ)償流程。
1.事務(wù)消息發(fā)送及提交:
(1) 發(fā)送 half 消息黎泣。
(2) 服務(wù)端響應(yīng)消息寫入結(jié)果恕刘。
(3) 根據(jù)發(fā)送結(jié)果執(zhí)行本地事務(wù)(如果寫入失敗,此時half消息對業(yè)務(wù)不可見抒倚,本地邏輯不執(zhí)行)褐着。
(4) 根據(jù)本地事務(wù)狀態(tài)執(zhí)行 Commit 或者 Rollback( Commit 操作生成消息索引,消息對消費(fèi)者可見)
流程圖如下:
2.補(bǔ)償流程:
(1) 對沒有 Commit/Rollback 的事務(wù)消息( pending 狀態(tài)的消息)托呕,從服務(wù)端發(fā)起一次“回查”
(2) Producer收到回查消息含蓉,檢查回查消息對應(yīng)的本地事務(wù)的狀態(tài)
(3) 根據(jù)本地事務(wù)狀態(tài),重新Commit或者Rollback
其中项郊,補(bǔ)償階段使用定時器回查方式用于解決消息 Commit 或者 Rollback 發(fā)生超時或者失敗的情況馅扣。
七、RocektMQ 事務(wù)消息的使用
如上着降,小伙伴們應(yīng)該對 RocketMQ 的事務(wù)消息有了一定的了解差油,下面看下如何在開發(fā)場景下如何使用。
發(fā)送事務(wù)消息時和普通的消息區(qū)別是任洞,自己要新建一個 TransactionMQProducer
和對應(yīng)的一個 TransactionListener
的實(shí)現(xiàn)蓄喇。
- TransactionMQProducer
具體的配置有 group、 nameServer 地址交掏、執(zhí)行本地事務(wù)的線程池和事務(wù)監(jiān)聽器的實(shí)現(xiàn)妆偏。
this.producer = new TransactionMQProducer(config.getGroup());
this.producer.setNamesrvAddr(config.getNameServer());
this.producer.setExecutorService(config.getExecutorService());
this.producer.setTransactionListener(config.getTransactionListener());
- TransactionListener
實(shí)現(xiàn)TransactionListener
接口的兩個方法:-
executeLocalTransaction(Message message, Object o)
用于執(zhí)行本地事務(wù)的方法。 -
checkLocalTransaction(MessageExt messageExt)
RocketMQ 回查本地事務(wù)狀態(tài)調(diào)用的方法盅弛。
-
代碼詳見 ?? : https://github.com/wangning1018/rocketmq-transaction-message-demo
歡迎訪問個人博客 獲取更多知識分享钱骂。