一、分片场景下的分布式事务,真的是"地狱级"难题
还记得第一次在分片数据库上实现分布式事务时,我天真地以为用个简单的两阶段提交就搞定了。结果上线后,事务要么提交失败,要么回滚不完整、
今天我们就来聊聊,在分片场景下,TCC 和 Seata 到底该怎么选?分布式事务又该怎么实现?这些坑,我踩过,你也别踩了!
二、分片场景下的分布式事务,为什么这么难?
1. 分片场景的特殊性
在分片数据库中,数据被分散到多个节点上,传统的 ACID 事务很难保证。比如:
-- 用户表在分片1UPDATE users SET balance = balance - 100 WHERE user_id = 123;
-- 订单表在分片2 INSERT INTO orders (user_id, amount) VALUES (123, 100);
复制代码
这两个操作跨分片,传统事务无法保证原子性。
2. 常见的"翻车现场"
场景 1:部分提交,部分回滚
// 用户扣款成功,订单创建失败try { // 分片1:扣款成功 userService.deductBalance(userId, amount); // 分片2:订单创建失败 orderService.createOrder(order); // 结果:钱扣了,订单没创建,数据不一致!} catch (Exception e) { // 回滚逻辑复杂,容易遗漏}
复制代码
场景 2:网络分区导致的不一致
分片 1 事务提交成功
分片 2 网络超时,事务状态未知
系统重启后,数据状态混乱
三、TCC 模式:手动补偿的"硬核"方案
1. TCC 是什么?
TCC(Try-Confirm-Cancel)是一种手动补偿的分布式事务模式:
Try 阶段:资源预留,检查并预留资源
Confirm 阶段:确认执行,真正执行业务逻辑
Cancel 阶段:取消执行,释放预留的资源
2. TCC 在分片场景下的实现
用户服务 TCC 实现:
@Servicepublic class UserTccService { // Try阶段:冻结用户余额 @Transactional public boolean tryDeductBalance(Long userId, BigDecimal amount) { User user = userMapper.selectById(userId); if (user.getBalance().compareTo(amount) < 0) { throw new RuntimeException("余额不足"); } // 冻结余额,而不是直接扣款 user.setFrozenAmount(user.getFrozenAmount().add(amount)); userMapper.updateById(user); return true; } // Confirm阶段:确认扣款 @Transactional public boolean confirmDeductBalance(Long userId, BigDecimal amount) { User user = userMapper.selectById(userId); // 真正扣款 user.setBalance(user.getBalance().subtract(amount)); user.setFrozenAmount(user.getFrozenAmount().subtract(amount)); userMapper.updateById(user); return true; } // Cancel阶段:取消扣款 @Transactional public boolean cancelDeductBalance(Long userId, BigDecimal amount) { User user = userMapper.selectById(userId); // 解冻余额 user.setFrozenAmount(user.getFrozenAmount().subtract(amount)); userMapper.updateById(user); return true; }}
复制代码
订单服务 TCC 实现:
@Servicepublic class OrderTccService { // Try阶段:预创建订单 @Transactional public boolean tryCreateOrder(Order order) { // 检查库存等前置条件 if (!checkInventory(order.getProductId(), order.getQuantity())) { throw new RuntimeException("库存不足"); } // 预创建订单,状态为"待确认" order.setStatus("PENDING"); orderMapper.insert(order); return true; } // Confirm阶段:确认订单 @Transactional public boolean confirmCreateOrder(Long orderId) { Order order = orderMapper.selectById(orderId); order.setStatus("CONFIRMED"); orderMapper.updateById(order); return true; } // Cancel阶段:取消订单 @Transactional public boolean cancelCreateOrder(Long orderId) { Order order = orderMapper.selectById(orderId); order.setStatus("CANCELLED"); orderMapper.updateById(order); return true; }}
复制代码
3. TCC 的优缺点
优点:
性能好,不需要全局锁
可以精确控制事务边界
支持长事务
缺点:
开发成本高,需要手动实现三个接口
业务侵入性强
补偿逻辑复杂,容易出错
四、Seata 模式:自动补偿的"懒人"方案
1. Seata 是什么?
Seata 是阿里开源的分布式事务框架,提供了 AT、TCC、SAGA、XA 四种模式,在分片场景下主要使用 AT 模式。
2. Seata AT 模式在分片场景下的实现
配置 Seata:
# application.ymlseata: enabled: true application-id: ${spring.application.name} tx-service-group: my_test_tx_group enable-auto-data-source-proxy: true data-source-proxy-mode: AT service: vgroup-mapping: my_test_tx_group: default grouplist: default: 127.0.0.1:8091
复制代码
业务代码实现:
@Servicepublic class OrderService { @GlobalTransactional public void createOrder(OrderRequest request) { // 分片1:扣减用户余额 userService.deductBalance(request.getUserId(), request.getAmount()); // 分片2:创建订单 orderService.createOrder(request.getOrder()); // 分片3:更新库存 inventoryService.updateStock(request.getProductId(), request.getQuantity()); }}
复制代码
Seata 自动补偿机制:
// Seata会自动生成补偿SQL// 原始SQL:UPDATE users SET balance = balance - 100 WHERE id = 123// 补偿SQL:UPDATE users SET balance = balance + 100 WHERE id = 123
复制代码
3. Seata 的优缺点
优点:
开发简单,只需要加注解
自动生成补偿逻辑
支持多种事务模式
缺点:
性能相对较低,需要全局锁
对数据库有侵入性(需要 undo_log 表)
不适合长事务
五、分片场景下的选择策略
1. 什么时候选 TCC?
适用场景:
对性能要求极高的场景
业务逻辑复杂,需要精确控制
长事务场景
对数据库侵入性要求高的场景
示例:
// 电商下单场景,涉及多个分片@GlobalTransactionalpublic void placeOrder(OrderRequest request) { // 用户服务(分片1) userTccService.tryDeductBalance(request.getUserId(), request.getAmount()); // 订单服务(分片2) orderTccService.tryCreateOrder(request.getOrder()); // 库存服务(分片3) inventoryTccService.tryDeductStock(request.getProductId(), request.getQuantity()); // 如果都成功,进入Confirm阶段 // 如果有失败,进入Cancel阶段}
复制代码
2. 什么时候选 Seata?
适用场景:
快速开发,对性能要求不高的场景
业务逻辑相对简单
短事务场景
团队对分布式事务经验不足
示例:
// 简单的转账场景@GlobalTransactionalpublic void transfer(Long fromUserId, Long toUserId, BigDecimal amount) { // 分片1:扣减转出用户余额 userService.deductBalance(fromUserId, amount); // 分片2:增加转入用户余额 userService.addBalance(toUserId, amount);}
复制代码
六、实战案例:电商订单系统的分布式事务设计
需求分析:
订单系统采用分片架构
涉及用户余额、订单创建、库存扣减
要求高可用、高性能
需要支持事务回滚
方案设计:
方案 1:TCC 模式(推荐)
@Servicepublic class OrderTccService { @GlobalTransactional public void placeOrder(OrderRequest request) { // Try阶段:资源预留 userTccService.tryDeductBalance(request.getUserId(), request.getAmount()); orderTccService.tryCreateOrder(request.getOrder()); inventoryTccService.tryDeductStock(request.getProductId(), request.getQuantity()); // 如果Try阶段都成功,自动进入Confirm阶段 // 如果有失败,自动进入Cancel阶段 }}
复制代码
优点:
性能好,支持高并发
可以精确控制事务边界
支持复杂的业务逻辑
缺点:
方案 2:Seata AT 模式
@Servicepublic class OrderService { @GlobalTransactional public void placeOrder(OrderRequest request) { // 直接执行业务逻辑,Seata自动处理事务 userService.deductBalance(request.getUserId(), request.getAmount()); orderService.createOrder(request.getOrder()); inventoryService.deductStock(request.getProductId(), request.getQuantity()); }}
复制代码
优点:
缺点:
七、分片场景下的最佳实践
1. 设计原则:
事务粒度控制:
尽量缩小事务范围
避免跨分片的长事务
合理设计事务边界
补偿策略设计:
// 幂等性设计@Transactionalpublic boolean deductBalance(Long userId, BigDecimal amount, String txId) { // 检查是否已经处理过 if (isProcessed(txId)) { return true; } // 执行业务逻辑 User user = userMapper.selectById(userId); user.setBalance(user.getBalance().subtract(amount)); userMapper.updateById(user); // 记录处理状态 recordProcessed(txId); return true;}
复制代码
2. 监控与告警:
关键指标:
监控代码:
@Aspect@Componentpublic class TransactionMonitor { @Around("@annotation(globalTransactional)") public Object monitorTransaction(ProceedingJoinPoint pjp) throws Throwable { long startTime = System.currentTimeMillis(); try { Object result = pjp.proceed(); // 记录成功指标 recordSuccess(System.currentTimeMillis() - startTime); return result; } catch (Exception e) { // 记录失败指标 recordFailure(e); throw e; } }}
复制代码
3. 避坑指南:
❌ 不要这样做:
在分片场景下使用传统两阶段提交
忽略补偿逻辑的幂等性
不监控事务执行状态
事务边界设计过大
✅ 要这样做:
根据业务场景选择合适的模式
设计幂等的补偿逻辑
建立完善的监控体系
定期进行数据一致性检查
八、总结
在分片场景下实现分布式事务,TCC 和 Seata 各有优势。
记住这三点:
TCC 适合高性能、复杂业务场景
Seata 适合快速开发、简单业务场景
设计时要考虑补偿逻辑的幂等性
最后提醒:分布式事务是分片架构的"硬骨头",设计时一定要深思熟虑,宁可多花时间设计,也不要上线后再改!
关注服务端技术精选,获取更多后端实战干货!
你在分布式事务实现上踩过哪些坑?欢迎在评论区分享你的故事!
评论