微服务架构下,跨服务的数据一致性问题不可避免。Seata作为阿里巴巴开源的分布式事务解决方案,AT(Automatic Transaction)模式通过自动生成回滚SQL实现两阶段提交,对业务代码侵入性极低。本文以Spring Boot集成Seata 2.2.0为例,覆盖从环境搭建到生产排障的完整流程。
Seata AT模式工作原理:全局事务与分支事务的协作机制
AT模式分为两阶段:第一阶段,各分支事务在本地提交,Seata自动记录回滚日志到undo_log表;第二阶段,全局事务协调器(TC)根据各分支事务状态决定全局提交或回滚。提交时异步删除undo_log,回滚时根据undo_log反向生成补偿SQL执行。
核心角色定义:TC(Transaction Coordinator)是事务协调器,独立部署的Seata Server,维护全局事务和分支事务的状态机;TM(Transaction Manager)是事务管理器,定义全局事务的范围,发起全局提交或回滚;RM(Resource Manager)是资源管理器,管理分支事务上的本地资源,注册分支事务并上报状态。
Seata Server部署与存储模式选型
Seata Server支持file、db、redis三种存储模式。生产环境推荐db模式,保证事务状态持久化和多节点一致性:
CREATE DATABASE seata DEFAULT CHARACTER SET utf8mb4;
USE seata;
CREATE TABLE `global_table` (
`xid` varchar(128) NOT NULL,
`transaction_id` bigint(20) DEFAULT NULL,
`status` tinyint(4) NOT NULL,
`application_id` varchar(32) DEFAULT NULL,
`transaction_service_group` varchar(32) DEFAULT NULL,
`transaction_name` varchar(128) DEFAULT NULL,
`timeout` int(11) DEFAULT NULL,
`begin_time` bigint(20) DEFAULT NULL,
`application_data` varchar(2000) DEFAULT NULL,
`gmt_create` datetime DEFAULT NULL,
`gmt_modified` datetime DEFAULT NULL,
PRIMARY KEY (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
每个业务数据库需要创建undo_log表:
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL,
`xid` varchar(128) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime(6) NOT NULL,
`log_modified` datetime(6) NOT NULL,
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Spring Boot集成Seata AT模式:业务代码改造
业务代码使用@GlobalTransactional注解开启全局事务:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StorageFeignClient storageFeignClient;
@Autowired
private AccountFeignClient accountFeignClient;
@GlobalTransactional(name = "create-order", timeoutMills = 60000, rollbackFor = Exception.class)
public void createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地事务 - 订单服务)
Order order = new Order();
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setCount(orderDTO.getCount());
order.setMoney(orderDTO.getMoney());
order.setStatus(0);
orderMapper.insert(order);
// 2. 扣减库存(远程调用 - 库存服务)
storageFeignClient.decrease(orderDTO.getProductId(), orderDTO.getCount());
// 3. 扣减账户余额(远程调用 - 账户服务)
accountFeignClient.decrease(orderDTO.getUserId(), orderDTO.getMoney());
// 4. 修改订单状态(本地事务)
orderMapper.updateStatus(order.getId(), 1);
}
}
应用配置中确保数据源代理生效:
seata:
enabled: true
application-id: order-service
tx-service-group: my_tx_group
data-source-proxy-mode: AT
client:
rm:
report-success-enable: true
async-commit-buffer-limit: 10000
lock:
retry-interval: 10
retry-times: 30
tm:
commit-retry-count: 5
rollback-retry-count: 5
default-global-transaction-timeout: 60000
服务治理与高可用:Seata Server集群部署
生产环境Seata Server应部署至少3个节点,通过Nacos注册中心实现集群发现。客户端通过tx-service-group映射到集群名,Seata自动从Nacos获取集群节点列表并负载均衡。当某节点宕机时,客户端自动切换到其他节点,正在进行的全局事务由TC通过db存储恢复处理。
常见排障方案:脏写检测、超时回滚与悬挂事务
问题1:BranchTransactionRollbackFailed_Retriable(脏写检测异常)
原因:分支事务在第一阶段提交后,同一行数据被其他事务修改,导致回滚时afterImage与当前数据不一致。Seata检测到脏写后会不断重试回滚。处理方法:查看branch_table中卡住的分支事务,确认冲突来源。如果是正常业务并发操作,需要通过LockRetryController调整重试参数,或在业务层引入分布式锁避免并发。
问题2:GlobalTransactionTimeout(全局事务超时)
原因:timeoutMills设置过短,远程调用链路过长导致超时。超时后TM发起回滚,但此时分支事务可能仍在执行,产生悬挂事务。处理:调大timeoutMills,合理值为最慢调用链路耗时的2倍。检查retry-dead-threshold(默认130秒),超过此阈值后Seata停止重试并标记事务为RollbackFailed_Unretryable,需要人工介入修复。
问题3:悬挂事务清理
-- 排查悬挂事务的SQL
SELECT xid, status, gmt_create, gmt_modified
FROM global_table
WHERE status IN (4, 5, 6, 7, 8, 9, 10, 11, 12)
AND gmt_modified < DATE_SUB(NOW(), INTERVAL 30 MINUTE);
-- status含义:4=RollbackRetrying, 5=RollbackFailed
-- 11=RollbackFailedTimeout, 12=RollbackFailedRetryable
强制清理前需确认各分支事务的undo_log已手动处理,否则可能导致数据不一致。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/seata-fen-bu-shi-shi-wu-shi-zhan-springboot-ji-cheng-at-mo/