Seata中的XA模式
约 3018 字大约 10 分钟
2025-09-05
XA模式的实现
XA模式的规范是Open Grouop组织定义的分布式事务处理(DTP,Distribute Transaction Processing)标准,XA模式规范描述了全局TM与局部RM之间的接口,几乎所有主流关系型数据库都对XA模式的规范提供了支持。
XA模式的简介
在XA模式下,每个参与事务的数据库都会开启一个XA事务分支,Seata的TC(事务协调器)负责协调这个分支事务的提交和回滚。
应用层通过Seata的JDBC数据源代理,将普通的数据源操作为代理支持XA的数据源操作。最终由TC发起两阶段提交:
- Prepare阶段:各个RM执行SQL并将操作结果预提交,等待TC指令;
- Commit阶段:根据全局事务的成功与否,TC通知所有的RM提交或回滚;
XA模式的具体实现
我们模拟电商场景,一共有三个服务:订单服务、库存服务和账户服务。其中订单服务作为全局事务的发起者,创建订单 –> 扣减库存 –> 扣减余额。
创建三个微服务
分别创建三个服务:seata-xa-order、seata-xa-storage、seata-xa-account,它们分别对应的数据库为order、storage和account。
1️⃣三个微服务的配置文件大体类似(着重展示seate的配置内容,其他的基础内容不展示):
seata:
enable: true,
application-id: ${spring.application.name}
tx-service-group: xa_order_group
data-source-proxy-mode: XA # 数据源使用XA模式
service:
vgroup-mapping:
xa_order_group: default
group-list:
default: 192.168.128.2:8091
registry:
type: file # 表示采用file作为注册中心
config:
type: file # 表示采用file作为配置中心2️⃣核心配置项
在Seata中默认都是走AT模式的,我们要演示XA模式的结果就需要手动显式指定数据代理的类型为XA模式。
data-source-proxy-mode: XA # 数据源使用XA模式这里额外将一下下面这三个配置的含义:
seata:
tx-service-group: xa_order_group
service:
vgroup-mapping:
xa_order_group: default
group-list:
default: 192.168.128.2:8091其中:
seata.service.vgroup-mapping.xa_order_group=default—— 该配置项的意思是xa_order_group事务组对应的集群名称为default;seata.service.group-list.default=192.168.128.2:9081—— 该配置项的意思是default集群对应的seata服务的地址是192.168.128.2:8091;seata.tx-service-group=xa_order_group—— 该配置项的意思是当前应用对应的事务组时xa_order_group;
关于他们的关系可以参考前文中的事务分组
3️⃣配置复制到其它的服务中,这里每个服务的关于Seata的配置都是一样的。
数据库初始化脚本
seata-xa-order服务的数据库初始化脚本:
CREATE TABLE `tb_order` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`product_id` int NOT NULL COMMENT '商品ID',
`count` smallint NOT NULL COMMENT '数量',
`price` bigint unsigned NOT NULL DEFAULT '0' COMMENT '单价',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;seata-xa-account服务的数据库初始化脚本:
CREATE TABLE tb_account (
id INT auto_increment PRIMARY key comment '账户ID',
balance BIGINT NOT NULL DEFAULT 0 COMMENT '账户余额',
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
insert into tb_account(balance) values(200);
insert into tb_account(balance) values(400);seata-xa-storage服务的数据库初始化脚本
create table tb_storage(
id int auto_increment primary key comment '库存记录ID',
product_id int not null comment '商品ID',
store int unsigned not null default 0 comment '库存数量',
create_time timestamp not null default current_timestamp comment '更新时间',
update_time timestamp not null default current_timestamp on update current_timestamp comment '更新时间'
)engine=InnoDB default charset=utf8mb4;
insert into tb_storage(product_id,store) values(2,200);
insert into tb_storage(product_id,store) values(1,100);编写分布式事务
分布式事务的发起服务为order服务,所以需要再order服务中通过@GlobalTransaction注解开启一个全局事务。
@Transactional
@GlobalTransactional
@Override
public void saveOrder(Integer productId, Integer count, Long price, Integer accountId) {
OrderEntity entity = new OrderEntity(productId, count, price);
// 本地事务开启
this.save(entity);
// rpc调用库存服务扣减库存
storageClient.deductAccount(productId, count);
// rpc调用账户服务来扣减余额
accountClient.deductAccount(accountId, price * count);
} @Transactional
@Override
public void deduct(Integer productId, Integer count) {
StorageEntity storageEntity = this.lambdaQuery().eq(StorageEntity::getProductId, productId).one();
this.lambdaUpdate().eq(StorageEntity::getProductId, productId)
.set(StorageEntity::getStore, storageEntity.getStore() - count).update();
} @Override
@Transactional
public void deductAccount(Integer accountId, Long balance) {
AccountEntity accountEntity = this.lambdaQuery().eq(AccountEntity::getId, accountId).one();
this.lambdaUpdate().eq(AccountEntity::getId, accountId)
.set(AccountEntity::getBalance, accountEntity.getBalance() - balance).update();
}此时我们通过接口触发这个方法后,如果seata-xa-storage和seata-xa-account服务中的事务操作没有出现异常,则可以看到最终订单保存成功 –> 库存扣减成功 –> 余额扣减成功。
如果们在seata-xa-storage服务中模拟一个异常:
@Transactional
@Override
public void deduct(Integer productId, Integer count) {
StorageEntity storageEntity = this.lambdaQuery().eq(StorageEntity::getProductId, productId).one();
if (Math.random() > 0) {
throw new RuntimeException("模拟异常");
}
this.lambdaUpdate().eq(StorageEntity::getProductId, productId)
.set(StorageEntity::getStore, storageEntity.getStore() - count).update();
}此时再次触发这个方法的时候 ,我们可以很会看到订单数据没有保存 –> 库存扣减失败 –> 余额扣减失败。
其实在XA模式下仔细的观察日志文件可以看到很清晰的交互逻辑
[xa-order] [nio-8542-exec-3] i.seata.tm.api.DefaultGlobalTransaction : Begin new global transaction [172.18.0.2:8091:18703754580742173]
[xa-order] [nio-8542-exec-3] io.seata.rm.AbstractResourceManager : branch register success, xid:172.18.0.2:8091:18703754580742173, branchId:18703754580742174, lockKeys:null
[xa-order] [nio-8542-exec-3] i.seata.tm.api.DefaultGlobalTransaction : transaction 172.18.0.2:8091:18703754580742173 will be commit
[xa-order] [h_RMROLE_1_2_48] i.s.c.r.p.c.RmBranchCommitProcessor : rm client handle branch commit process:BranchCommitRequest{xid='172.18.0.2:8091:18703754580742173', branchId=18703754580742174, branchType=XA, resourceId='jdbc:mysql://localhost:3306/order', applicationData='null'}
[xa-order] [h_RMROLE_1_2_48] io.seata.rm.AbstractRMHandler : Branch committing: 172.18.0.2:8091:18703754580742173 18703754580742174 jdbc:mysql://localhost:3306/order null
[xa-order] [h_RMROLE_1_2_48] i.s.rm.datasource.xa.ResourceManagerXA : 172.18.0.2:8091:18703754580742173-18703754580742174 was committed.
[xa-order] [h_RMROLE_1_2_48] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed
[xa-order] [nio-8542-exec-3] i.seata.tm.api.DefaultGlobalTransaction : [172.18.0.2:8091:18703754580742173] commit status: Committed[xa-storage] [nio-8989-exec-3] io.seata.rm.AbstractResourceManager : branch register success, xid:172.18.0.2:8091:18703754580742173, branchId:18703754580742175, lockKeys:null
[xa-storage] [h_RMROLE_1_2_48] i.s.c.r.p.c.RmBranchCommitProcessor : rm client handle branch commit process:BranchCommitRequest{xid='172.18.0.2:8091:18703754580742173', branchId=18703754580742175, branchType=XA, resourceId='jdbc:mysql://localhost:3306/mall', applicationData='null'}
[xa-storage] [h_RMROLE_1_2_48] io.seata.rm.AbstractRMHandler : Branch committing: 172.18.0.2:8091:18703754580742173 18703754580742175 jdbc:mysql://localhost:3306/mall null
[xa-storage] [h_RMROLE_1_2_48] i.s.rm.datasource.xa.ResourceManagerXA : 172.18.0.2:8091:18703754580742173-18703754580742175 was committed.
[xa-storage] [h_RMROLE_1_2_48] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed[xa-account] [nio-8942-exec-1] io.seata.rm.AbstractResourceManager : branch register success, xid:172.18.0.2:8091:18703754580742173, branchId:18703754580742176, lockKeys:null
[xa-account] [h_RMROLE_1_2_48] i.s.c.r.p.c.RmBranchCommitProcessor : rm client handle branch commit process:BranchCommitRequest{xid='172.18.0.2:8091:18703754580742173', branchId=18703754580742176, branchType=XA, resourceId='jdbc:mysql://localhost:3306/account', applicationData='null'}
[xa-account] [h_RMROLE_1_2_48] io.seata.rm.AbstractRMHandler : Branch committing: 172.18.0.2:8091:18703754580742173 18703754580742176 jdbc:mysql://localhost:3306/account null
[xa-account] [h_RMROLE_1_2_48] i.s.rm.datasource.xa.ResourceManagerXA : 172.18.0.2:8091:18703754580742173-18703754580742176 was committed.
[xa-account] [h_RMROLE_1_2_48] io.seata.rm.AbstractRMHandler : Branch commit result: PhaseTwo_Committed通过日志文件也能很方便的看出来每个服务与TC之间的交互逻辑。
XA模式的特点
因为XA模式是基于2PC协议实现的,所以它具备以下的特点:
- 优点
- 强一致性:基于数据库原生XA协议和两阶段提交协议,是标准的分布式解决方案,能够严格保证事务的ACID特性。适用于对数据一致性要求非常高的场景(如金融交易、账户资金变动等);
- 无侵入向(相对于TCC和SAGA):XA模式的实现非常简单,它只需要通过配置Seata数据源的代理模式为XA,然后通过
@GlobalTransaction注解开启一个全局事务即可; - 自动管理分支事务:Seata会自动拦截SQL,通过XA协议与数据库交互,开发者无需手动控制每个分支事务的提交与回滚;
- 缺点
- 性能较低:XA模式需要两阶段提交,设计到多次网络交互,并且事务过程中资源(如数据库连接)会被长时间锁定,直到第二阶段完成;
- 阻塞与锁竞争:在Prepare阶段,数据库会对相关资源加锁并保持,直到Commit/Rollback,这可能导致锁持有时间较长,容易引发死锁或并发性能下降,尤其在分布式环境下更为明显;
- 依赖数据的XA能力:只有支持XA协议的数据库才能使用Seata的XA模式,某些数据库可能不支持XA,因为XA模式不具备普适性;
- 事务协调器压力大:由于XA模式需要精准控制每个分支的两阶段,TC需要维护更多的状态信息,对TC的稳定性与可用性要求更高;
AT模式的具体实现
AT模式的实现前提是数据库要支持ACID的事务特性,它也是基于2PC协议的基础逻辑,但是它与2PC协议也有不同。
- 一阶段:业务数据和undo日志记录在同一个本地事务中提交,释放本地锁和连接资源;
- 二阶段:提交异步化,非常快速的完成;如果是回滚,则直接通过一阶段的undo日志进行反向补偿。
AT模式的具体实现逻辑为: