Java中seata框架的XA模式詳解
XA模式
XA 模式屬于一種強一致性的事務模式。
前提
支持 XA 模式的數(shù)據(jù)庫。
Java 應用通過 JDBC 訪問數(shù)據(jù)庫。
整體機制
在 Seata 定義的分布式事務框架內(nèi),利用事務資源(數(shù)據(jù)庫、消息服務等)對 XA 協(xié)議提供可回滾、持久化的支持,使用 XA 協(xié)議的機制來管理分支事務。
執(zhí)行階段
執(zhí)行 XA Start、業(yè)務 SQL、XA End =》注冊分支,XA Prepare => 報告分支事務的狀態(tài)。
完成階段
執(zhí)行 XA Commit / XA Rollback 操作進行分支事務的提交或者回滾。
XA 模式需要 XAConnection,而獲取 XAConnection 的方式有兩種:
- 方式一、要求開發(fā)者配置 XADataSource。給開發(fā)者增加了認知負擔,需要為 XA 模式專門去學習和使用 XA 數(shù)據(jù)源,與透明化 XA 編程模型的設計目標相悖。
- 方式二、根據(jù)開發(fā)者的普通 DataSource 來創(chuàng)建。對開發(fā)者比較友好,和 AT 模式一樣,開發(fā)者完全不需要關心 XA 層面的任何問題,保持本地編程模型即可。
優(yōu)先設計實現(xiàn)第二種方式,數(shù)據(jù)源代理根據(jù)普通數(shù)據(jù)源中獲取的普通 JDBC 連接創(chuàng)建出相應的 XAConnection。
類比 AT 模式的數(shù)據(jù)源代理機制,如下:
但是,第二種方法有局限:無法保證兼容的正確性。
實際上,這種方法是在做數(shù)據(jù)庫驅(qū)動程序要做的事情。不同的廠商、不同版本的數(shù)據(jù)庫驅(qū)動實現(xiàn)機制是廠商私有的,我們只能保證在充分測試過的驅(qū)動程序上是正確的,開發(fā)者使用的驅(qū)動程序版本差異很可能造成機制的失效。
綜合考慮,XA 模式的數(shù)據(jù)源代理設計需要同時支持第一種方式:基于 XA 數(shù)據(jù)源進行代理。
類比 AT 模式的數(shù)據(jù)源代理機制,如下:
使用方法
每個服務的 file.conf、registry.conf 配置文件的配置這里先不提供。
Business服務
|
|------> Stock服務
|
|------> Order服務 -----> Account服務
Business服務
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("庫存服務調(diào)用失敗,事務回滾!"); } result = orderFeignClient.create(userId, commodityCode, orderCount); if (!SUCCESS.equals(result)) { throw new RuntimeException("訂單服務調(diào)用失敗,事務回滾!"); } if (rollback) { throw new RuntimeException("Force rollback ... "); } }
BusinessXADataSourceConfiguration
@Configuration public class BusinessXADataSourceConfiguration { @Bean @ConfigurationProperties(prefix = "spring.datasource") public DruidDataSource dataSource() { return new DruidDataSource(); } }
Stock服務
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服務
OrderService
public void create(String userId, String commodityCode, Integer count) { String xid = RootContext.getXID(); LOGGER.info("create order in transaction: " + xid); // 定單總價 = 訂購數(shù)量(count) * 商品單價(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服務
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); } }
啟動類需要標注 @EnableTransactionManagement 注解。
到此這篇關于Java中seata框架的XA模式詳解的文章就介紹到這了,更多相關seata框架的XA模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Struts2開發(fā)環(huán)境搭建 附簡單登錄功能實例
這篇文章主要介紹了Struts2開發(fā)環(huán)境搭建,為大家分享一個簡單登錄功能實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11