分布式事務的問題,在微服務架構中一直是難題脸候。單體應用實現(xiàn)本地事務即可穷娱,到了分布式環(huán)境,情況就變得復雜运沦。一個請求可能涉及多個服務泵额,上下游存在依賴關系,其中的一環(huán)失敗携添,需要將整個事務回滾嫁盲。筆者在去年上半年開源過一款微服務的分布式事務組件:lottor,基于可靠消息的柔性分布式事務實現(xiàn)方案烈掠。引入的 Lottor 客戶端使用比較復雜羞秤,具有業(yè)務侵入性。推廣使用的效果并不是很好向叉。阿里在今年年初開源了 Seata(原名 fescar)锥腻,引起了強烈的反響嗦董。筆者最近也在考慮改進 Lottor母谎,借學習實踐 Seata 的機會,和大家分享一下京革。
幾款開源的分布式事務組件
既有的分布式事務解決方案按照對業(yè)務侵入性分為兩類奇唤,即:對業(yè)務無侵入的和對業(yè)務有侵入的幸斥。
業(yè)務無侵入的方案
既有的主流分布式事務解決方案中,對業(yè)務無侵入的只有基于 XA 的方案(注:問題中提到的 JTA 是 XA 方案的 Java 版本)咬扇,但應用 XA 方案存在 3 個方面的問題:
要求數(shù)據(jù)庫提供對 XA 的支持甲葬。如果遇到不支持 XA(或支持得不好,比如 MySQL 5.7 以前的版本)的數(shù)據(jù)庫懈贺,則不能使用经窖。
受協(xié)議本身的約束,事務資源(數(shù)據(jù)記錄梭灿、數(shù)據(jù)庫連接)的鎖定周期長画侣。長周期的資源鎖定從業(yè)務層面來看,往往是不必要的堡妒,而因為事務資源的管理器是數(shù)據(jù)庫本身配乱,應用層無法插手。這樣形成的局面就是皮迟,基于 XA 的應用往往性能會比較差搬泥,而且很難優(yōu)化。
已經(jīng)落地的基于 XA 的分布式解決方案伏尼,都依托于重量級的應用服務器(Tuxedo/WebLogic/WebSphere 等)忿檩,這是不適用于微服務架構的。
侵入業(yè)務的方案
實際上烦粒,最初分布式事務只有 XA 這個唯一方案休溶。XA 是完備的,但在實踐過程中扰她,由于種種原因(包含但不限于上面提到的3 點)往往不得不放棄兽掰,轉而從業(yè)務層面著手來解決分布式事務問題。比如:
- 基于可靠消息的最終一致性方案
- TCC
- Saga
都屬于這一類徒役。這些方案的具體機制在這里不做展開孽尽,網(wǎng)上這方面的論述文章非常多∮俏穑總之杉女,這些方案都要求在應用的業(yè)務層面把分布式事務技術約束考慮到設計中,通常每一個服務都需要設計實現(xiàn)正向和反向的冪等接口鸳吸。這樣的設計約束熏挎,往往會導致很高的研發(fā)和維護成本。
不可否認晌砾,侵入業(yè)務的分布式事務方案都經(jīng)過大量實踐驗證坎拐,能有效解決問題,在各行種業(yè)的業(yè)務應用系統(tǒng)中起著重要作用。但回到原點來思考哼勇,這些方案的采用實際上都是迫于無奈都伪。
Seata 介紹
Seata 是一款開源的分布式事務解決方案,提供高性能和簡單易用的分布式事務服務积担。
包括了集團的 TXC(云版本叫 GTS)和螞蟻金服的 TCC 兩種模式陨晶,目前 Github 上的 star 數(shù)已經(jīng)超過一萬,算是目前唯一有大廠背書的分布式事務解決方案帝璧。
TXC 在 Seata 中又叫 AT 模式先誉,意為補償方法是框架自動生成的,對用戶完全屏蔽的烁,用戶可以向使用本地事務那樣使用分布式事務谆膳,缺點是僅支持關系型數(shù)據(jù)庫(目前支持 MySQL),引入 Seata AT 的服務需要本地建表存儲 rollback_info撮躁,隔離級別默認 RU 適用場景有限漱病。
TCC 不算是新概念,很早就有了把曼,用戶通過定義 try/confirm/cancel 三個方法在應用層面模擬兩階段提交杨帽,區(qū)別在于 TCC 中 try 方法也需要操作數(shù)據(jù)庫進行資源鎖定,后續(xù)兩個補償方法由框架自動調用嗤军,分別進行資源提交和回滾注盈,這點同單純的存儲層 2PC 不太一樣。螞蟻金服向 Seata 貢獻了自己的 TCC 實現(xiàn)叙赚,據(jù)說已經(jīng)演化了十多年老客,大量應用在在金融、交易震叮、倉儲等領域胧砰。
本文目前重點關注 Seata 中的 AT 模式。
Seata 組成
Seata 設計上將整體分成三個大模塊苇瓣,即 TM尉间、RM、TC击罪,具體解釋如下:
TM(Transaction Manager):全局事務管理器哲嘲,控制全局事務邊界,負責全局事務開啟媳禁、全局提交眠副、全局回滾。
RM(Resource Manager):資源管理器竣稽,控制分支事務囱怕,負責分支注冊槽唾、狀態(tài)匯報,并接收事務協(xié)調器的指令光涂,驅動分支(本地)事務的提交和回滾。
TC(Transaction Coordinator):事務協(xié)調器拧烦,維護全局事務的運行狀態(tài)忘闻,負責協(xié)調并驅動全局事務的提交或回滾。
Seata AT 模式是基于兩階段提交模式設計的恋博,以高效且對業(yè)務零侵入的方式齐佳,解決微服務場景下面臨的分布式事務問題。
AT 模式的架構與實現(xiàn)原理介紹
分布式事務是一個全局事務债沮,由多個分支事務組成炼吴,Seata AT 模式具體包括如下兩個階段:
- 階段1:分支(本地)事務執(zhí)行。將一個本地事務做為一個分布式事務分支疫衩,所以若干個分布在不同微服務中的本地事務共同組成了一個全局事務硅蹦,結構如下。
- 階段2:分支事務提交或回滾闷煤。階段2完成的是全局事物的最終提交或回滾童芹,當全局事務中所有分支事務全部完成并且都執(zhí)行成功,這時TM會發(fā)起全局事務提交鲤拿,TC收到全全局事務提交消息后假褪,會通知各分支事務進行提交;同理近顷,當全局事務中所有分支事務全部完成并且某個分支事務失敗了生音,TM會通知TC協(xié)調全局事務回滾,進而TC通知各分支事務進行回滾窒升。
在業(yè)務應用啟動過程中缀遍,由于引入了 Seata 客戶端,RmRpcClient會隨應用一起啟動饱须,該RmRpcClient采用Netty實現(xiàn)瑟由,可以接收TC消息和向TC發(fā)送消息,因此RmRpcClient是與TC收發(fā)消息的關鍵模塊冤寿。
Seata 實現(xiàn)分布式事務的一般過程如下:
- TM 通知 TC 開始一個新的全局事務歹苦。TC 生成了一個代表全局事務的 XID。
- XID 通過微服務的調用鏈傳播下去督怜。
- RM將本地事務注冊為XID到TC的相應全局事務的分支殴瘦。
- TM 通知 TC 提交或者回滾 XID 對應的全局事務
- TC 驅動 XID 的對應全局事務下的所有分支事務以完成分支提交或回滾。
Seata AT 模式使用入門
示例使用 seata-samples 中的 Seata-JPA 項目号杠。
涉及到的幾個服務如下所示:
客戶端發(fā)起業(yè)務服務蚪腋,Business 調用 Storage 鎖定庫存丰歌、Order 生成訂單。Order 調用 Account 扣減余額屉凯。各個服務對應的角色已經(jīng)在上圖標注立帖。
啟動 TC 服務
首先,我們啟動 Seata-Server悠砚,即 TC晓勇。下載 Seata-Server 的發(fā)行包。執(zhí)行如下命令即可啟動服務器灌旧。
sh distribution/bin/seata-server.sh -p 8091 -h 127.0.0.1 -m file
新建數(shù)據(jù)庫表
執(zhí)行幾個服務涉及到的數(shù)據(jù)庫表:
每個服務都有對應的業(yè)務表和 undo_log 表绑咱。
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
在事務鏈涉及的服務的數(shù)據(jù)庫中新建 undo_log 表用來存儲 UndoLog 信息,用于二階段回滾操作枢泰,表中包含 xid描融、branchId、rollback_info 等關鍵字段信息衡蚂。
增加全局事務注解
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
storageFeignClient.deduct(commodityCode, orderCount);
orderFeignClient.create(userId, commodityCode, orderCount);
}
依賴 Seata 的客戶端 SDK窿克,然后在整個分布式事務發(fā)起方的業(yè)務方法上增加 @GlobalTransactional
注解。
配置代理數(shù)據(jù)源
Seata 中主要針對 java.sql 包下的 DataSource毛甲、Connection让歼、Statement、PreparedStatement 四個接口進行了再包裝丽啡,包裝類分別為 DataSourceProxy谋右、ConnectionProxy、StatementProxy补箍、PreparedStatementProxy改执,很好一一對印,其功能是在 SQL 語句執(zhí)行前后坑雅、事務 commit 或者 rollbakc 前后進行一些與 Seata 分布式事務相關的操作辈挂,例如分支注冊、狀態(tài)回報裹粤、全局鎖查詢终蒂、快照存儲、反向 SQL 生成等遥诉。
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean("dataSource")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
需要將 DataSourceProxy 設置為主數(shù)據(jù)源拇泣,否則事務無法回滾。
驗證
業(yè)務服務中提供了兩個接口矮锈,一個正常提交霉翔,另一個回滾,執(zhí)行之后查看結果苞笨。
GET http://127.0.0.1:8084/purchase/commit
Accept: application/json
###
GET http://127.0.0.1:8084/purchase/rollback
Accept: application/json
大家可以自行驗證一下結果债朵。
小結
本文簡單介紹了阿里開源的分布式事務組件 Seata 的相關概念子眶,重點介紹了 Seata 的 AT 模式,MT 模式在后面將會介紹序芦。本文比較簡單臭杰,是一個入門的實踐。Seata 是一個優(yōu)秀的分布式事務框架谚中,筆者會在后面的文章中結合源碼介紹 Seata 的實現(xiàn)原理渴杆。