Java中seata框架的XA模式詳解
XA模式
XA 模式屬于一種強(qiáng)一致性的事務(wù)模式。
前提
支持 XA 模式的數(shù)據(jù)庫(kù)。
Java 應(yīng)用通過(guò) JDBC 訪問(wèn)數(shù)據(jù)庫(kù)。
整體機(jī)制
在 Seata 定義的分布式事務(wù)框架內(nèi),利用事務(wù)資源(數(shù)據(jù)庫(kù)、消息服務(wù)等)對(duì) XA 協(xié)議提供可回滾、持久化的支持,使用 XA 協(xié)議的機(jī)制來(lái)管理分支事務(wù)。

執(zhí)行階段
執(zhí)行 XA Start、業(yè)務(wù) SQL、XA End =》注冊(cè)分支,XA Prepare => 報(bào)告分支事務(wù)的狀態(tài)。
完成階段
執(zhí)行 XA Commit / XA Rollback 操作進(jìn)行分支事務(wù)的提交或者回滾。
XA 模式需要 XAConnection,而獲取 XAConnection 的方式有兩種:
- 方式一、要求開(kāi)發(fā)者配置 XADataSource。給開(kāi)發(fā)者增加了認(rèn)知負(fù)擔(dān),需要為 XA 模式專門(mén)去學(xué)習(xí)和使用 XA 數(shù)據(jù)源,與透明化 XA 編程模型的設(shè)計(jì)目標(biāo)相悖。
- 方式二、根據(jù)開(kāi)發(fā)者的普通 DataSource 來(lái)創(chuàng)建。對(duì)開(kāi)發(fā)者比較友好,和 AT 模式一樣,開(kāi)發(fā)者完全不需要關(guān)心 XA 層面的任何問(wèn)題,保持本地編程模型即可。
優(yōu)先設(shè)計(jì)實(shí)現(xiàn)第二種方式,數(shù)據(jù)源代理根據(jù)普通數(shù)據(jù)源中獲取的普通 JDBC 連接創(chuàng)建出相應(yīng)的 XAConnection。
類(lèi)比 AT 模式的數(shù)據(jù)源代理機(jī)制,如下:

但是,第二種方法有局限:無(wú)法保證兼容的正確性。
實(shí)際上,這種方法是在做數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序要做的事情。不同的廠商、不同版本的數(shù)據(jù)庫(kù)驅(qū)動(dòng)實(shí)現(xiàn)機(jī)制是廠商私有的,我們只能保證在充分測(cè)試過(guò)的驅(qū)動(dòng)程序上是正確的,開(kāi)發(fā)者使用的驅(qū)動(dòng)程序版本差異很可能造成機(jī)制的失效。
綜合考慮,XA 模式的數(shù)據(jù)源代理設(shè)計(jì)需要同時(shí)支持第一種方式:基于 XA 數(shù)據(jù)源進(jìn)行代理。
類(lèi)比 AT 模式的數(shù)據(jù)源代理機(jī)制,如下:

使用方法
每個(gè)服務(wù)的 file.conf、registry.conf 配置文件的配置這里先不提供。
Business服務(wù)
|
|------> Stock服務(wù)
|
|------> Order服務(wù) -----> Account服務(wù)
Business服務(wù)
BusinessService
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount, boolean rollback) {
String xid = RootContext.getXID();
LOGGER.info("New Transaction Begins: " + xid);
String result = stockFeignClient.deduct(commodityCode, orderCount);
if (!SUCCESS.equals(result)) {
throw new RuntimeException("庫(kù)存服務(wù)調(diào)用失敗,事務(wù)回滾!");
}
result = orderFeignClient.create(userId, commodityCode, orderCount);
if (!SUCCESS.equals(result)) {
throw new RuntimeException("訂單服務(wù)調(diào)用失敗,事務(wù)回滾!");
}
if (rollback) {
throw new RuntimeException("Force rollback ... ");
}
}BusinessXADataSourceConfiguration
@Configuration
public class BusinessXADataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource dataSource() {
return new DruidDataSource();
}
}Stock服務(wù)
StockBusiness
public void deduct(String commodityCode, int count) {
String xid = RootContext.getXID();
LOGGER.info("deduct stock balance in transaction: " + xid);
jdbcTemplate.update("update seata_stock set count = count - ? where commodity_code = ?",
new Object[] {count, commodityCode});
}application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://10.211.55.6:3306/seata_stock?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=root
StockXADataSourceConfiguration
@Configuration
public class StockXADataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxyXA(druidDataSource);
}
@Bean("jdbcTemplate")
public JdbcTemplate jdbcTemplate(DataSource dataSourceProxy) {
return new JdbcTemplate(dataSourceProxy);
}
}Order服務(wù)
OrderService
public void create(String userId, String commodityCode, Integer count) {
String xid = RootContext.getXID();
LOGGER.info("create order in transaction: " + xid);
// 定單總價(jià) = 訂購(gòu)數(shù)量(count) * 商品單價(jià)(100)
int orderMoney = count * 100;
// 生成訂單
jdbcTemplate.update("insert seata_order(user_id,commodity_code,count,money) values(?,?,?,?)",
new Object[] {userId, commodityCode, count, orderMoney});
// 調(diào)用賬戶余額扣減
String result = accountFeignClient.reduce(userId, orderMoney);
if (!SUCCESS.equals(result)) {
throw new RuntimeException("Failed to call Account Service. ");
}
}application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://10.211.55.6:3306/seata_order?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=root
OrderXADataSourceConfiguration
@Configuration
public class OrderXADataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxyXA(druidDataSource);
}
@Bean("jdbcTemplate")
public JdbcTemplate jdbcTemplate(DataSource dataSourceProxy) {
return new JdbcTemplate(dataSourceProxy);
}
}Account服務(wù)
AccountService
@Transactional
public void reduce(String userId, int money) {
String xid = RootContext.getXID();
LOGGER.info("reduce account balance in transaction: " + xid);
jdbcTemplate.update("update seata_account set money = money - ? where user_id = ?", new Object[] {money, userId});
int balance = jdbcTemplate.queryForObject("select money from seata_account where user_id = ?",
new Object[] {userId}, Integer.class);
LOGGER.info("balance after transaction: " + balance);
if (balance < 0) {
throw new RuntimeException("Not Enough Money ...");
}
}application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://10.211.55.6:3306/seata_account?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true spring.datasource.username=root spring.datasource.password=root
AccountXADataSourceConfiguration
@Configuration
public class AccountXADataSourceConfiguration {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean("dataSourceProxy")
public DataSource dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxyXA(druidDataSource);
}
@Bean("jdbcTemplate")
public JdbcTemplate jdbcTemplate(DataSource dataSourceProxy) {
return new JdbcTemplate(dataSourceProxy);
}
@Bean
public PlatformTransactionManager txManager(DataSource dataSourceProxy) {
return new DataSourceTransactionManager(dataSourceProxy);
}
}啟動(dòng)類(lèi)需要標(biāo)注 @EnableTransactionManagement 注解。
到此這篇關(guān)于Java中seata框架的XA模式詳解的文章就介紹到這了,更多相關(guān)seata框架的XA模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中使用HashMap時(shí)指定初始化容量性能解析
這篇文章主要為大家介紹了Java中使用HashMap時(shí)指定初始化容量性能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
基于Hutool的圖片驗(yàn)證碼功能模塊實(shí)現(xiàn)
為了提高系統(tǒng)的安全性,防止接口被暴力刷新,驗(yàn)證碼是個(gè)好的手段,圖片驗(yàn)證碼沒(méi)有短信驗(yàn)證碼的費(fèi)用,其是個(gè)人開(kāi)發(fā)者學(xué)習(xí)的重點(diǎn),這篇文章主要介紹了基于Hutool的圖片驗(yàn)證碼功能模塊實(shí)現(xiàn),需要的朋友可以參考下2022-10-10
使用Java實(shí)現(xiàn)一個(gè)解析CURL腳本小工具
文章介紹了如何使用Java實(shí)現(xiàn)一個(gè)解析CURL腳本的工具,該工具可以將CURL腳本中的Header解析為KV Map結(jié)構(gòu),獲取URL路徑、請(qǐng)求類(lèi)型,解析URL參數(shù)列表和Body請(qǐng)求體,感興趣的小伙伴跟著小編一起來(lái)看看吧2025-02-02
解決mybatis一對(duì)多關(guān)聯(lián)查詢多條數(shù)據(jù)只顯示一條的問(wèn)題
這篇文章主要介紹了解決mybatis一對(duì)多關(guān)聯(lián)查詢多條數(shù)據(jù)只顯示一條的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
SpringBoot @Cacheable自定義KeyGenerator方式
這篇文章主要介紹了SpringBoot @Cacheable自定義KeyGenerator方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
使用MyEclipse 開(kāi)發(fā)struts2框架實(shí)現(xiàn)登錄功能(結(jié)構(gòu)教程)
這篇文章主要介紹了使用MyEclipse 開(kāi)發(fā)struts2框架實(shí)現(xiàn)登錄功能(結(jié)構(gòu)教程)的相關(guān)資料,需要的朋友可以參考下2016-03-03

