Seata 分布式事务
什么是分布式事务?
在微服务架构中,一个业务操作涉及多个服务,需要保证数据一致性。
订单服务 → 创建订单
↓
库存服务 → 扣减库存
↓
账户服务 → 扣减余额
任意一步失败,都需要全部回滚Seata 简介
Seata 是阿里巴巴开源的分布式事务解决方案,提供:
- AT 模式:无侵入,自动补偿
- TCC 模式:高性能,业务补偿
- SAGA 模式:长事务,状态机
AT 模式
原理
一阶段:
1. 解析 SQL,记录数据修改前的快照
2. 执行业务 SQL
3. 记录数据修改后的快照
4. 生成行锁
5. 注册分支事务到 TC
二阶段-提交:
1. 异步删除快照和行锁
二阶段-回滚:
1. 对比当前数据与修改后快照
2. 如果一致,用修改前快照回滚
3. 如果不一致,说明有脏写,需人工处理快速开始
1. 安装 Seata Server
# 下载
wget https://github.com/seata/seata/releases/download/v1.7.0/seata-server-1.7.0.zip
# 解压并启动
unzip seata-server-1.7.0.zip
cd seata-server-1.7.0
sh bin/seata-server.sh2. 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>3. 配置
seata:
enabled: true
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
registry:
type: nacos
nacos:
server-addr: localhost:8848
namespace: ""
group: SEATA_GROUP4. 使用
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StorageClient storageClient;
@Autowired
private AccountClient accountClient;
@GlobalTransactional // 开启全局事务
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());
orderMapper.insert(order);
// 2. 扣减库存(远程调用)
storageClient.decrease(orderDTO.getProductId(), orderDTO.getCount());
// 3. 扣减余额(远程调用)
accountClient.decrease(orderDTO.getUserId(), orderDTO.getMoney());
// 4. 更新订单状态
order.setStatus(1);
orderMapper.updateById(order);
}
}数据库表
-- 每个库都需要
CREATE TABLE `undo_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`branch_id` bigint NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int 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;TCC 模式
原理
Try:预留资源
Confirm:确认提交
Cancel:取消预留实现
public interface StorageTccService {
@TwoPhaseBusinessAction(name = "prepareDeduct",
commitMethod = "commit",
rollbackMethod = "rollback")
boolean prepareDeduct(@BusinessActionContextParameter(paramName = "productId") Long productId,
@BusinessActionContextParameter(paramName = "count") Integer count);
boolean commit(BusinessActionContext context);
boolean rollback(BusinessActionContext context);
}
@Service
public class StorageTccServiceImpl implements StorageTccService {
@Autowired
private StorageMapper storageMapper;
@Override
public boolean prepareDeduct(Long productId, Integer count) {
// 预留库存(冻结)
Storage storage = storageMapper.selectById(productId);
if (storage.getStock() < count) {
throw new RuntimeException("库存不足");
}
storage.setStock(storage.getStock() - count);
storage.setFrozenStock(storage.getFrozenStock() + count);
storageMapper.updateById(storage);
return true;
}
@Override
public boolean commit(BusinessActionContext context) {
// 确认扣减(清除冻结)
Long productId = context.getActionContext("productId", Long.class);
Integer count = context.getActionContext("count", Integer.class);
Storage storage = storageMapper.selectById(productId);
storage.setFrozenStock(storage.getFrozenStock() - count);
storageMapper.updateById(storage);
return true;
}
@Override
public boolean rollback(BusinessActionContext context) {
// 回滚(恢复库存)
Long productId = context.getActionContext("productId", Long.class);
Integer count = context.getActionContext("count", Integer.class);
Storage storage = storageMapper.selectById(productId);
storage.setStock(storage.getStock() + count);
storage.setFrozenStock(storage.getFrozenStock() - count);
storageMapper.updateById(storage);
return true;
}
}AT vs TCC
| 对比 | AT 模式 | TCC 模式 |
|---|---|---|
| 侵入性 | 无 | 高(需编写补偿代码) |
| 性能 | 较低(需要解析 SQL) | 高 |
| 一致性 | 最终一致 | 最终一致 |
| 适用场景 | 大多数业务 | 高性能要求 |
面试高频问题
Q1: 分布式事务有哪些方案?
| 方案 | 说明 |
|---|---|
| 2PC | 两阶段提交,强一致,性能低 |
| 3PC | 三阶段提交,改进 2PC |
| TCC | Try-Confirm-Cancel,高性能 |
| SAGA | 长事务,补偿机制 |
| 本地消息表 | 最终一致 |
| Seata AT | 无侵入,自动补偿 |
Q2: Seata AT 模式的原理?
- 一阶段:执行 SQL + 记录快照
- 二阶段提交:删除快照
- 二阶段回滚:用快照恢复数据
Q3: AT 模式如何防止脏写?
通过全局锁:
- 提交前检查全局锁
- 如果被其他事务锁定,等待或回滚
Q4: TCC 的空回滚和悬挂问题?
空回滚:Try 未执行,Cancel 却执行了 悬挂:Cancel 比 Try 先执行
解决:通过事务状态表记录状态
总结
Seata 核心要点:
1. 三种模式:AT(推荐)、TCC、SAGA
2. AT 模式:无侵入,自动补偿
3. TCC 模式:高性能,需编写补偿代码
4. 注解:@GlobalTransactional
5. 关键表:undo_log