分布式事务的核心挑战
微服务架构下,一个业务操作可能跨多个服务的数据源,本地事务无法保证跨服务数据一致性。订单服务扣减库存、支付服务扣款、积分服务增减积分——任一步骤失败,如何回滚已执行的操作?这是分布式事务要解决的核心问题。这篇文章对比主流方案的适用场景,并给出Seata AT模式在Spring Boot中的完整部署实践。
三种主流方案对比
2PC(两阶段提交):强一致性协议,协调者统一控制所有参与者的事务提交或回滚。XA协议是2PC的标准实现,Java生态中由数据库驱动和应用服务器支持。优点是严格保证一致性,缺点是同步阻塞严重,单点协调者故障会导致所有参与者锁死。适合对一致性要求极高、并发量不高的场景。
TCC(Try-Confirm-Cancel):业务层面的2PC。Try阶段预留资源(冻结库存),Confirm阶段确认扣减,Cancel阶段释放预留。每个服务需实现三个接口,业务侵入性大,但性能优于2PC——锁持有时间短,仅在Try阶段冻结。适合资金、库存等核心业务场景。
Saga模式:长事务编排,每个步骤执行本地事务并记录补偿操作,失败时按反向顺序执行补偿。正向和反向都是本地事务,无全局锁。适合业务流程长、步骤多的场景,代价是只有最终一致性。
Seata AT模式实战部署
Seata的AT模式是最容易接入的分布式事务方案——对业务代码零侵入,只需加注解。原理是通过拦截SQL自动生成回滚日志(undo_log),全局回滚时根据日志反向补偿。
Step 1: 部署Seata Server
# docker部署seata-server
docker run -d --name seata-server -p 8091:8091 -e SEATA_IP=192.168.1.100 -v /data/seata/config:/seata-server/config seataio/seata-server:2.0.0
Seata Server配置文件application.yml使用Nacos作为注册中心:
seata:
registry:
type: nacos
nacos:
server-addr: 192.168.1.100:8848
namespace: seata
group: DEFAULT_GROUP
store:
mode: db
db:
datasource: druid
url: jdbc:mysql://192.168.1.100:3306/seata?useSSL=false
username: seata
password: seata_pwd
Step 2: 微服务接入Seata
每个参与分布式事务的微服务需要添加依赖和配置。Maven依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2023.0.1.0</version>
</dependency>
每个服务的数据源需要被Seata代理,使用DataSourceProxy包装原始数据源。Spring Boot自动配置版本引入starter后无需手动配置。
每个服务数据库创建undo_log表:
CREATE TABLE IF NOT EXISTS `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Step 3: 业务代码使用
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockClient stockClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional(timeoutMills = 60000, name = "create-order")
public void createOrder(OrderDTO orderDTO) {
// 1. 创建订单(本地事务,自动记录undo_log)
Order order = new Order();
order.setUserId(orderDTO.getUserId());
order.setProductId(orderDTO.getProductId());
order.setAmount(orderDTO.getAmount());
orderMapper.insert(order);
// 2. 扣减库存(远程调用,Seata拦截并注册分支事务)
stockClient.deduct(orderDTO.getProductId(), orderDTO.getQuantity());
// 3. 扣减账户余额(远程调用)
accountClient.debit(orderDTO.getUserId(), orderDTO.getAmount());
}
}
@GlobalTransactional注解标记全局事务入口。Seata拦截订单服务的本地SQL生成undo_log,拦截远程调用注册分支事务。任一步骤异常,Seata Server协调所有分支回滚,根据undo_log反向补偿。
生产环境注意事项
1. 全局锁与写隔离:AT模式在全局事务提交前获取全局锁,防止脏写。如果全局锁获取超时(默认30秒),事务回滚。高并发场景下需关注全局锁冲突率,必要时切换为写隔离级别RC。
2. undo_log清理:已提交事务的undo_log需要定期清理,Seata Server默认每小时清理一次。大流量场景下undo_log表增长快,建议设置定时任务或缩短清理间隔。
3. Seata Server高可用:生产环境至少部署3个Seata Server节点,通过Nacos注册中心实现服务发现。Store模式使用数据库而非文件,确保事务日志持久化。
4. 超时设置:timeoutMills不宜过短(默认60秒),跨服务调用链路长时容易超时回滚;也不宜过长,避免全局锁占用时间过长影响并发。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-wei-fu-wu-fen-bu-shi-shi-wu-fang-an-dui-bi-yu/