分布式事务是微服务架构绕不过去的坎
单体应用里数据库事务靠ACID保证一致性,拆成微服务后,一个业务操作横跨多个数据库,本地事务管不了跨库一致性。订单服务扣库存、支付服务扣余额、积分服务加积分,三步要么全成功要么全回滚。Seata的AT模式是侵入性最小的分布式事务方案——不需要改业务SQL,只需加注解。
Seata AT模式的工作原理
AT模式的核心是两阶段提交,但对业务代码完全透明:
一阶段:拦截业务SQL,在执行前保存数据快照(before image),执行后保存变更后的快照(after image),生成回滚日志(undo_log)。业务SQL和undo_log在同一个本地事务中提交,保证原子性。
二阶段提交:TC(事务协调器)通知各分支提交,因为一阶段已经提交了,只需要异步清理undo_log即可,性能极高。
二阶段回滚:TC通知各分支回滚,读取undo_log中的before image,生成反向补偿SQL执行回滚。如果发现after image与当前数据不一致(被脏写),需要人工介入。
Seata Server部署与Spring Boot集成
Seata Server部署
# docker-compose.yml
version: '3'
services:
seata-server:
image: seataio/seata-server:2.2.0
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_IP=0.0.0.0
volumes:
- ./seata-config:/seata-server/resources
depends_on:
- seata-store
seata-store:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: seata
volumes:
- ./sql:/docker-entrypoint-initdb.d
application.yml配置(每个微服务)
# 各微服务的application.yml
seata:
enabled: true
application-id: order-service
tx-service-group: my-tx-group
service:
vgroup-mapping:
my-tx-group: default
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
group: SEATA_GROUP
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: seata
group: SEATA_GROUP
每个微服务数据库建undo_log表
-- 每个参与分布式事务的数据库都要建这张表
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` BIGINT 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;
业务代码:一个注解搞定分布式事务
集成完成后,业务代码只需在入口方法上加@GlobalTransactional注解:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryClient inventoryClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public Order createOrder(OrderDTO dto) {
// 1. 创建订单(本地事务)
Order order = new Order();
order.setUserId(dto.getUserId());
order.setProductId(dto.getProductId());
order.setAmount(dto.getAmount());
order.setStatus("CREATED");
orderMapper.insert(order);
// 2. 扣减库存(远程调用)
inventoryClient.deduct(dto.getProductId(), dto.getQuantity());
// 3. 扣减账户余额(远程调用)
accountClient.debit(dto.getUserId(), dto.getAmount());
order.setStatus("PAID");
orderMapper.updateById(order);
return order;
}
}
远程服务只需要引入seata依赖和配置undo_log,不需要加@GlobalTransactional。Seata通过数据源代理自动拦截SQL,记录undo_log。
高并发场景下的Seata调优
AT模式在一阶段就提交了本地事务,性能瓶颈主要在undo_log的写入和全局锁的争用上。
全局锁优化
默认情况下,AT模式会在全局事务提交前持有全局锁,防止脏写。高并发写入场景下全局锁成为瓶颈。优化方案:
# Nacos配置中心 - seata配置
# 全局锁超时时间(毫秒),默认30秒太长
client.lock.lock-retry-interval = 10
client.lock.lock-retry-times = 30
# 对于读多写少场景,可以关闭全局锁(需自行保证不脏写)
# 注意:关闭全局锁后需要业务层保证幂等性
service.disable-global-transaction = false
undo_log表优化
-- undo_log表加上索引,加速清理
ALTER TABLE `undo_log` ADD INDEX `idx_log_created` (`log_created`);
-- 定期清理已提交的undo_log
DELETE FROM `undo_log` WHERE `log_status` = 1
AND `log_modified` < DATE_SUB(NOW(), INTERVAL 7 DAY);
Seata Server JVM调优
# seata-server启动参数
JAVA_OPT="-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError
-Dio.netty.leakDetection.level=disabled"
G1收集器适合Seata的事务日志写入模式,200ms的GC停顿在大多数业务场景下可接受。Netty的内存泄漏检测关闭后吞吐提升约15%。
异常场景处理与最佳实践
场景1:全局事务超时
默认超时60秒。如果业务中有耗时较长的远程调用,需要调大超时时间:
@GlobalTransactional(timeoutMills = 300000) // 5分钟超时
public Order createOrder(OrderDTO dto) {
// ...
}
场景2:脏写检测触发
回滚时发现after image与当前数据不一致,说明在分布式事务期间有其他事务修改了数据。Seata默认会记录异常日志并人工介入。处理方式:
# 在Nacos配置中开启脏写自动处理
# 注意:仅在业务能容忍覆盖回滚时开启
client.undo.log-serialization = jackson
client.undo.only-care-update-columns = true
场景3:幂等性保证
分布式事务可能重试,远程调用接口必须幂等:
@PostMapping("/deduct")
public Result deduct(@RequestParam String productId,
@RequestParam Integer quantity,
@RequestParam String txId) {
// 用txId做幂等校验
DeductRecord exist = deductRecordMapper.selectByTxId(txId);
if (exist != null) {
return Result.success("already deducted");
}
// 正常执行扣减逻辑
inventoryService.deduct(productId, quantity);
// 记录事务ID
deductRecordMapper.insert(txId, productId, quantity);
return Result.success();
}
场景4:避免大事务
分布式事务越短越好。不要在@GlobalTransactional方法中做RPC调用之外的耗时操作(如大文件上传、复杂计算)。把不必要的操作移到事务外,只在事务中保留必须保证一致性的核心操作。
Seata AT模式在微服务架构中的定位是"低成本的一致性保障"。牺牲了一点点性能(undo_log写入和全局锁),换来了业务代码几乎零侵入。对于大多数电商、支付、SaaS场景,AT模式的性价比远高于TCC和Saga模式。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-ji-cheng-seata-fen-bu-shi-shi-wu-shi-zhan-at-mo/