Spring Boot分布式事务选Seata还是自己造
微服务架构下,跨服务的数据一致性是后端开发无法绕开的问题。订单服务扣减库存、支付服务冻结资金、积分服务预增积分——这些操作要么全部成功,要么全部回滚。本地事务管不了跨库操作,分布式事务的选型直接决定了系统的可靠性和吞吐上限。本文聚焦Seata AT模式的实战配置,从零搭建到压测调优。
分布式事务方案对比:为什么AT模式适合多数业务
分布式事务的常见方案有四种:2PC、TCC、Saga、本地消息表。Seata AT模式本质是2PC的自动化版本——框架替你写补偿逻辑。
AT模式的工作机制:一阶段执行业务SQL并拦截,生成回滚日志(undo_log)存入数据库;二阶段提交时删除undo_log,回滚时根据undo_log反向补偿。对业务代码侵入最小,只需要加一个@GlobalTransactional注解。
不适用AT模式的场景:高并发短事务(锁冲突严重)、非关系型数据库操作、长事务。这些场景考虑TCC或Saga。
Seata Server部署与配置
Seata Server是事务协调器(TC),所有微服务的事务分支在此注册和协调。
# docker-compose部署Seata Server
version: '3'
services:
seata-server:
image: seataio/seata-server:2.2.0
container_name: seata-server
ports:
- "8091:8091"
- "7091:7091"
environment:
- SEATA_IP=10.0.1.100
volumes:
- ./seata-config:/seata-server/resources
depends_on:
- seata-store
seata-store:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: Seata@2026
MYSQL_DATABASE: seata
volumes:
- ./sql/seata-server.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3307:3306"
Seata Server配置文件(application.yml):
server:
port: 7091
seata:
config:
type: nacos
nacos:
server-addr: 10.0.1.50:8848
namespace: seata
group: SEATA_GROUP
registry:
type: nacos
nacos:
server-addr: 10.0.1.50: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-store:3306/seata?rewriteBatchedStatements=true
user: root
password: Seata@2026
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
微服务接入Seata AT模式
以订单服务为例,每个参与分布式事务的微服务需要完成三件事:引入依赖、配置代理数据源、添加undo_log表。
<!-- pom.xml 核心依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>seata-spring-cloud-starter</artifactId>
<version>2023.0.3.0</version>
</dependency>
每个业务数据库执行undo_log建表语句:
-- 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(11) NOT NULL COMMENT '日志状态',
`log_created` DATETIME(6) NOT NULL COMMENT '创建时间',
`log_modified` DATETIME(6) NOT NULL COMMENT '修改时间',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB COMMENT ='AT事务回滚日志表';
业务代码:@GlobalTransactional实战
订单创建涉及三个服务:订单服务创建订单、库存服务扣减库存、账户服务扣减余额。
// 订单服务 - 事务发起方
@Service
public class OrderService {
@DubboReference
private InventoryService inventoryService;
@DubboReference
private AccountService accountService;
@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)
public OrderDTO createOrder(CreateOrderRequest request) {
// 1. 创建订单(本地事务,自动生成undo_log)
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(request.getAmount());
order.setStatus(OrderStatus.INIT);
orderMapper.insert(order);
// 2. 扣减库存(远程调用,Seata自动注册分支事务)
inventoryService.deduct(request.getProductId(), request.getQuantity());
// 3. 扣减余额(远程调用)
accountService.debit(request.getUserId(), request.getAmount());
// 4. 更新订单状态
order.setStatus(OrderStatus.PAID);
orderMapper.updateById(order);
return OrderDTO.fromEntity(order);
}
}
// 库存服务 - 事务参与方
@Service
public class InventoryServiceImpl implements InventoryService {
@Autowired
private InventoryMapper inventoryMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public void deduct(Long productId, Integer quantity) {
Inventory inv = inventoryMapper.selectByProductId(productId);
if (inv == null) {
throw new BusinessException("商品不存在: " + productId);
}
if (inv.getStock() < quantity) {
throw new BusinessException("库存不足,当前: " + inv.getStock());
}
inv.setStock(inv.getStock() - quantity);
inventoryMapper.updateById(inv);
}
}
关键点:事务发起方加@GlobalTransactional,参与方只需要@Transactional。Seata通过拦截SQL自动生成undo_log,远程调用时xid通过Dubbo Filter透传。
XID透传:跨服务事务上下文传递
分布式事务的xid需要从发起方传递到每个参与方。Spring Cloud通过HTTP Header传递,Dubbo通过Attachment传递。
// Dubbo XID透传Filter(如果seata-dubbo集成未自动生效)
@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
public class SeataDubboFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
String xid = RootContext.getXID();
if (xid != null) {
RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
}
try {
Result result = invoker.invoke(invocation);
return result;
} finally {
String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);
if (rpcXid != null) {
RootContext.bind(rpcXid);
}
}
}
}
Spring Cloud用户配置seata-spring-cloud-starter后,xid通过SeataTransactionPropagationInterceptor自动在HTTP Header中传递,无需手动处理。
压测与调优:并发下的锁冲突处理
AT模式在高并发下最大的瓶颈是全局锁。当两个全局事务同时修改同一行数据,后获取锁的事务会等待甚至超时回滚。
# Seata客户端调优配置(application.yml)
seata:
client:
tm:
commit-retry-count: 3 # 提交失败重试次数
rollback-retry-count: 3 # 回滚失败重试次数
rm:
async-commit-buffer-limit: 10000 # 异步提交缓冲区
report-retry-count: 5 # 报告重试次数
table-meta-checker-enabled: true
report-success-enable: false
saga-branch-register-enable: false
saga-json-parser: jackson
lock:
retry-interval: 10 # 全局锁重试间隔(ms)
retry-times: 30 # 全局锁重试次数
retry-policy-branch-rollback-on-conflict: true
压测数据(订单创建场景,10并发,1000次请求):
- 单服务本地事务:QPS 850,0失败
- Seata AT模式(3服务):QPS 320,约5%锁冲突回滚
- Seata AT模式(优化后,lock retry=50):QPS 280,约2%锁冲突回滚
全局锁重试次数调高会降低锁冲突失败率,但会增加事务持有时间,QPS反而下降。实际调优需要在失败率和吞吐之间取平衡。对于极高并发场景,建议将热点数据操作拆分为TCC模式,AT模式保留给低冲突的普通业务。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/springboot-fen-bu-shi-shi-wu-shi-zhan-seataat-mo-shi-pei/