Java中seata框架的XA模式详解
作者:等後那场雪
XA模式
XA 模式属于一种强一致性的事务模式。
前提
支持 XA 模式的数据库。
Java 应用通过 JDBC 访问数据库。
整体机制
在 Seata 定义的分布式事务框架内,利用事务资源(数据库、消息服务等)对 XA 协议提供可回滚、持久化的支持,使用 XA 协议的机制来管理分支事务。
执行阶段
执行 XA Start、业务 SQL、XA End =》注册分支,XA Prepare => 报告分支事务的状态。
完成阶段
执行 XA Commit / XA Rollback 操作进行分支事务的提交或者回滚。
XA 模式需要 XAConnection,而获取 XAConnection 的方式有两种:
- 方式一、要求开发者配置 XADataSource。给开发者增加了认知负担,需要为 XA 模式专门去学习和使用 XA 数据源,与透明化 XA 编程模型的设计目标相悖。
- 方式二、根据开发者的普通 DataSource 来创建。对开发者比较友好,和 AT 模式一样,开发者完全不需要关心 XA 层面的任何问题,保持本地编程模型即可。
优先设计实现第二种方式,数据源代理根据普通数据源中获取的普通 JDBC 连接创建出相应的 XAConnection。
类比 AT 模式的数据源代理机制,如下:
但是,第二种方法有局限:无法保证兼容的正确性。
实际上,这种方法是在做数据库驱动程序要做的事情。不同的厂商、不同版本的数据库驱动实现机制是厂商私有的,我们只能保证在充分测试过的驱动程序上是正确的,开发者使用的驱动程序版本差异很可能造成机制的失效。
综合考虑,XA 模式的数据源代理设计需要同时支持第一种方式:基于 XA 数据源进行代理。
类比 AT 模式的数据源代理机制,如下:
使用方法
每个服务的 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("库存服务调用失败,事务回滚!"); } result = orderFeignClient.create(userId, commodityCode, orderCount); if (!SUCCESS.equals(result)) { throw new RuntimeException("订单服务调用失败,事务回滚!"); } 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); // 定单总价 = 订购数量(count) * 商品单价(100) int orderMoney = count * 100; // 生成订单 jdbcTemplate.update("insert seata_order(user_id,commodity_code,count,money) values(?,?,?,?)", new Object[] {userId, commodityCode, count, orderMoney}); // 调用账户余额扣减 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模式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!