第24章 分布式事务异常初探

1 阅读3分钟

第24章 分布式事务异常初探

24.1 为什么本地事务方案在分布式场景下失效

单体应用里 @Transactional 能保证的是单个数据库连接内的 ACID,一旦业务操作跨越了多个数据库实例/多个微服务(比如下单要同时扣减库存服务的库存、更新订单服务的订单状态),本地事务完全无法感知另一个服务的事务状态,任何一方失败都可能导致数据不一致,这类不一致往往不会表现为一个直接的"异常",而是业务数据层面的静默错误(比如库存扣了但订单没建成功),排查成本远高于普通异常。

24.2 Seata 框架的核心异常类型(了解层面,供后续深入参考)

Seata 是目前 Java 生态使用较广泛的分布式事务框架,其 AT 模式(自动补偿模式)下常见的异常包括:

异常/现象触发场景
GlobalTransactionException全局事务提交/回滚过程中出现异常(比如某个分支事务的 RM 在提交阶段网络异常导致提交失败)
分支事务超时未上报某个参与分布式事务的服务处理时间超过全局事务超时设置,被 TC(事务协调者)判定为失败,触发全局回滚
悬挂(Dangling)问题二阶段回滚消息比一阶段执行还先到达(网络乱序),导致分支事务状态与预期不一致,需要 Seata 框架内部的防悬挂机制处理

24.3 更轻量的替代思路:可靠消息 + 最终一致性

对于不是所有场景都需要引入 Seata 这类重量级分布式事务框架的情况,更常见的工程实践是使用可靠消息 + 补偿机制实现最终一致性,异常处理的关注点也随之转变:

// 本地消息表方案:业务操作和"待发送消息"记录在同一个本地事务里,保证要么都成功要么都失败
@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);
    // 在同一个本地事务内,把"需要通知库存服务"这件事作为一条消息记录落库
    messageRepository.save(new PendingMessage("DEDUCT_STOCK", order.getId()));
}
// 独立的定时任务/消息中间件消费者负责扫描 PendingMessage 表,可靠地把消息发送出去,
// 即使发送失败也能重试,而不会因为"本地事务已提交但消息发送失败"造成数据不一致

这种方案下,"异常处理"的核心不再是某一次调用抛出的具体异常类型,而是如何设计幂等的补偿逻辑(消息可能会被重复消费,接收方必须能安全地处理重复请求而不产生副作用),这是分布式系统设计里一个比"某个具体异常类怎么 catch"更根本的话题,值得在实际项目中作为独立专题深入学习。