分佈式事務的問題,在微服務架構中一直是難題。單體應用實現本地事務便可,到了分佈式環境,狀況就變得複雜。一個請求可能涉及多個服務,上下游存在依賴關係,其中的一環失敗,須要將整個事務回滾。筆者在去年上半年開源過一款微服務的分佈式事務組件:lottor,基於可靠消息的柔性分佈式事務實現方案。引入的 Lottor 客戶端使用比較複雜,具備業務侵入性。推廣使用的效果並非很好。阿里在今年年初開源了 Seata(原名 fescar),引發了強烈的反響。筆者最近也在考慮改進 Lottor,借學習實踐 Seata 的機會,和你們分享一下。java
既有的分佈式事務解決方案按照對業務侵入性分爲兩類,即:對業務無侵入的和對業務有侵入的。git
既有的主流分佈式事務解決方案中,對業務無侵入的只有基於 XA 的方案(注:問題中提到的 JTA 是 XA 方案的 Java 版本),但應用 XA 方案存在 3 個方面的問題:github
要求數據庫提供對 XA 的支持。若是遇到不支持 XA(或支持得很差,好比 MySQL 5.7 之前的版本)的數據庫,則不能使用。spring
受協議自己的約束,事務資源(數據記錄、數據庫鏈接)的鎖定週期長。長週期的資源鎖定從業務層面來看,每每是沒必要要的,而由於事務資源的管理器是數據庫自己,應用層沒法插手。這樣造成的局面就是,基於 XA 的應用每每性能會比較差,並且很難優化。sql
已經落地的基於 XA 的分佈式解決方案,都依託於重量級的應用服務器(Tuxedo/WebLogic/WebSphere 等),這是不適用於微服務架構的。數據庫
實際上,最初分佈式事務只有 XA 這個惟一方案。XA 是完備的,但在實踐過程當中,因爲種種緣由(包含但不限於上面提到的3 點)每每不得不放棄,轉而從業務層面着手來解決分佈式事務問題。好比:json
都屬於這一類。這些方案的具體機制在這裏不作展開,網上這方面的論述文章很是多。總之,這些方案都要求在應用的業務層面把分佈式事務技術約束考慮到設計中,一般每個服務都須要設計實現正向和反向的冪等接口。這樣的設計約束,每每會致使很高的研發和維護成本。bash
不能否認,侵入業務的分佈式事務方案都通過大量實踐驗證,能有效解決問題,在各行種業的業務應用系統中起着重要做用。但回到原點來思考,這些方案的採用實際上都是迫於無奈。服務器
Seata 是一款開源的分佈式事務解決方案,提供高性能和簡單易用的分佈式事務服務。微信
包括了集團的 TXC(雲版本叫 GTS)和螞蟻金服的 TCC 兩種模式,目前 Github 上的 star 數已經超過一萬,算是目前惟一有大廠背書的分佈式事務解決方案。 TXC 在 Seata 中又叫 AT 模式,意爲補償方法是框架自動生成的,對用戶徹底屏蔽,用戶能夠向使用本地事務那樣使用分佈式事務,缺點是僅支持關係型數據庫(目前支持 MySQL),引入 Seata AT 的服務須要本地建表存儲 rollback_info,隔離級別默認 RU 適用場景有限。
TCC 不算是新概念,很早就有了,用戶經過定義 try/confirm/cancel 三個方法在應用層面模擬兩階段提交,區別在於 TCC 中 try 方法也須要操做數據庫進行資源鎖定,後續兩個補償方法由框架自動調用,分別進行資源提交和回滾,這點同單純的存儲層 2PC 不太同樣。螞蟻金服向 Seata 貢獻了本身的 TCC 實現,聽說已經演化了十多年,大量應用在在金融、交易、倉儲等領域。
本文目前重點關注 Seata 中的 AT 模式。
Seata 設計上將總體分紅三個大模塊,即 TM、RM、TC,具體解釋以下:
TM(Transaction Manager):全局事務管理器,控制全局事務邊界,負責全局事務開啓、全局提交、全局回滾。
RM(Resource Manager):資源管理器,控制分支事務,負責分支註冊、狀態彙報,並接收事務協調器的指令,驅動分支(本地)事務的提交和回滾。
TC(Transaction Coordinator):事務協調器,維護全局事務的運行狀態,負責協調並驅動全局事務的提交或回滾。
Seata AT 模式是基於兩階段提交模式設計的,以高效且對業務零侵入的方式,解決微服務場景下面臨的分佈式事務問題。
分佈式事務是一個全局事務,由多個分支事務組成,Seata AT 模式具體包括以下兩個階段:
在業務應用啓動過程當中,因爲引入了 Seata 客戶端,RmRpcClient會隨應用一塊兒啓動,該RmRpcClient採用Netty實現,能夠接收TC消息和向TC發送消息,所以RmRpcClient是與TC收發消息的關鍵模塊。
Seata 實現分佈式事務的通常過程以下:
示例使用 seata-samples 中的 Seata-JPA 項目。
涉及到的幾個服務以下所示:
客戶端發起業務服務,Business 調用 Storage 鎖定庫存、Order 生成訂單。Order 調用 Account 扣減餘額。各個服務對應的角色已經在上圖標註。
首先,咱們啓動 Seata-Server,即 TC。下載 Seata-Server 的發行包。執行以下命令便可啓動服務器。
sh distribution/bin/seata-server.sh -p 8091 -h 127.0.0.1 -m file
複製代碼
執行幾個服務涉及到的數據庫表:
每一個服務都有對應的業務表和 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;
複製代碼
在事務鏈涉及的服務的數據庫中新建 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,而後在整個分佈式事務發起方的業務方法上增長 @GlobalTransactional
註解。
Seata 中主要針對 java.sql 包下的 DataSource、Connection、Statement、PreparedStatement 四個接口進行了再包裝,包裝類分別爲 DataSourceProxy、ConnectionProxy、StatementProxy、PreparedStatementProxy,很好一一對印,其功能是在 SQL 語句執行先後、事務 commit 或者 rollbakc 先後進行一些與 Seata 分佈式事務相關的操做,例如分支註冊、狀態回報、全局鎖查詢、快照存儲、反向 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 設置爲主數據源,不然事務沒法回滾。
業務服務中提供了兩個接口,一個正常提交,另外一個回滾,執行以後查看結果。
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 是一個優秀的分佈式事務框架,筆者會在後面的文章中結合源碼介紹 Seata 的實現原理。