微服务架构下分布式事务的必然性
单体应用里一个数据库连接就能搞定事务,微服务拆分后一个业务操作跨3个服务、3个数据库,本地事务管不了跨库的一致性问题。订单服务扣库存、支付服务扣余额、积分服务加积分——三个操作必须全部成功或全部回滚,中间任何一步失败,数据就不一致了。
微服务架构中分布式事务的常见方案有:2PC(两阶段提交)、TCC(Try-Confirm-Cancel)、Saga(长事务编排)、本地消息表+最终一致性。Seata的AT模式本质上是一种增强型2PC,对业务代码侵入最小——只需要加一个@GlobalTransactional注解。
Seata AT模式的核心机制:一阶段业务+回滚日志
AT模式的执行流程比传统2PC简单得多:
一阶段:业务SQL执行 + 生成回滚日志(undo_log)。业务SQL执行后,Seata拦截器自动记录修改前后的数据快照到undo_log表。
二阶段提交:异步清理undo_log,几乎零开销。
二阶段回滚:读取undo_log反向补偿,恢复到修改前状态。
这个机制的关键优势是:一阶段完成后本地事务已提交,不持有数据库锁,其他业务可以正常读写。回滚通过undo_log反向补偿实现,而不是通过长时间持有锁来保证隔离性。
Seata Server部署与配置
Seata Server(TC,事务协调器)是独立部署的中间件,管理全局事务的分支注册和二阶段决策。
# docker-compose部署Seata Server
version: '3'
services:
seata-server:
image: seataio/seata-server:2.2.0
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_IP=seata-server
volumes:
- ./seata-config:/seata-server/resources
depends_on:
- seata-db
Seata Server配置application.yml:
server:
port: 7091
seata:
registry:
type: nacos
nacos:
server-addr: nacos:8848
namespace: seata
group: SEATA_GROUP
application: seata-server
store:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://seata-db:3306/seata?useSSL=false
user: seata
password: seata_pwd
min-conn: 5
max-conn: 30
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
Spring Boot微服务集成Seata AT模式
每个参与分布式事务的微服务需要:
1. 引入Seata依赖
2. 配置数据源代理
3. 每个业务数据库创建undo_log表
4. 在业务入口方法加@GlobalTransactional注解
Maven依赖:
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- 如果使用Nacos注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.0</version>
</dependency>
数据源代理配置——Seata需要代理数据源来拦截SQL生成undo_log:
@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource dataSource) {
return new DataSourceProxy(dataSource);
}
}
每个业务数据库的undo_log建表语句:
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` BIGINT NOT NULL COMMENT '分支事务ID',
`xid` VARCHAR(128) NOT NULL COMMENT '全局事务ID',
`context` VARCHAR(128) NOT NULL COMMENT '上下文',
`rollback_info` LONGBLOB NOT NULL COMMENT '回滚数据',
`log_status` INT NOT NULL COMMENT '状态',
`log_created` DATETIME NOT NULL COMMENT '创建时间',
`log_modified` DATETIME NOT NULL COMMENT '修改时间',
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
业务代码实现:订单-库存-积分三服务事务
订单服务作为事务发起方:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient;
@Autowired
private PointsClient pointsClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public OrderDTO createOrder(OrderRequest request) {
// 1. 创建订单(本地事务)
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(request.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 扣减库存(远程调用)
stockClient.deduct(request.getProductId(), request.getQuantity());
// 3. 增加积分(远程调用)
pointsClient.addPoints(request.getUserId(), request.getAmount().intValue());
return OrderDTO.fromEntity(order);
}
}
库存服务和积分服务不需要加@GlobalTransactional,只需要被Seata代理数据源即可。Seata通过全局事务ID(XID)的传播来自动关联分支事务。XID通过RPC框架(Feign/Dubbo)的拦截器自动传递,不需要手工编码。
Feign拦截器配置XID传播:
@Configuration
public class SeataFeignConfig {
@Bean
public RequestInterceptor seataFeignInterceptor() {
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
String xid = RootContext.getXID();
if (StringUtils.isNotBlank(xid)) {
template.header(RootContext.KEY_XID, xid);
}
}
};
}
}
分布式事务线上运维与故障处理
Seata AT模式线上最常见的故障是undo_log未清理导致表膨胀和全局事务悬挂。
全局事务悬挂:一阶段已经执行但TC未收到分支注册,导致undo_log残留。Seata Server有定时任务清理超时的全局事务(默认60秒),但业务侧也需要定期清理undo_log:
-- 定期清理超过24小时的undo_log
DELETE FROM undo_log
WHERE log_created < DATE_SUB(NOW(), INTERVAL 24 HOUR)
AND log_status = 1; -- 1表示已提交
回滚失败处理:二阶段回滚读undo_log发现数据已被修改(脏写),Seata默认策略是打印日志并重试。如果持续失败需要人工介入,在branch_table中查看悬挂的事务分支,手动确认后强制回滚或提交。
Seata控制台的事务列表可以查看所有未完成的全局事务,是线上排查的第一入口。核心指标监控:正在运行的全局事务数量、回滚率、平均事务耗时。回滚率持续上升通常意味着下游服务不稳定,需要从根因解决而不是依赖事务补偿。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-zai-springboot/