商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Java面试——微服务架构下的分布式事务处理

Java面试——微服务架构下的分布式事务处理

  发布于2026-07-08 阅读(0)

扫一扫,手机访问

微服务架构下的分布式事务:本地事务为什么会“失灵”?

很多在单体应用里习以为常的技术方案,一旦迁移到微服务架构,瞬间就不灵了。最典型的一个问题:为什么一个简单的 @Transactional 注解,在微服务里几乎等于没用?

问题的根源其实很直接:@Transactional 本质上只认单个 JVM 内的数据库连接。而当你把订单、库存、支付拆成三个独立服务,它们各自跑在不同的进程甚至不同的机器上,@Transactional 根本鞭长莫及——它管不了别人家数据库里的事务。

所以你会看到一些非常典型的“事故现场”:
- 订单服务写入成功了,库存服务因为网络超时默默回滚,结果用户看到“下单成功”,但后台库存没扣;
- 有人试图用 Spring 的 TransactionSynchronizationManager 在跨进程调用中传播事务上下文——这在单体里或许可行,但在分布式环境下完全无效。

这一切的核心原因在于:分布式系统没有全局锁、没有共享内存、没有统一的事务协调器。除非你显式引入这样的协调者,否则必须换一套思路。

Ja va面试——微服务架构下的分布式事务处理

Seata AT 模式:真正落地需要注意的几个“坑”

Seata 是目前 Ja va 生态中最成熟的分布式事务方案之一,它的 AT 模式(Automatic Transaction)对业务代码的侵入最小,听起来很美好。但配置和使用中,稍不留神就会翻车。

几个必须盯紧的实操点:

  • 必须确保每个参与服务都接入 Seata 的 DataSourceProxy。如果不这么做,SQL 不会被解析,undo_log 表不会写入。这是最常见的“事务看似生效实则没回滚”的原因。
  • global_transaction_timeout 默认只有 60 秒。如果某个服务响应慢——比如调用第三方接口卡住了——整个全局事务会直接触发 GlobalRollback。但此时下游服务可能已经提交了,结果就是状态不一致。
  • MySQL 必须开启 binloglog-bin=ON),且隔离级别不能是 READ_UNCOMMITTED,否则 Seata 无法正确生成 undo_log。
  • 避免在事务分支里做非幂等操作,比如发信息或调外部 HTTP 接口。因为回滚重试时会重复执行这些操作,后果可想而知。
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource")
    public DruidDataSource druidDataSource() {
        return new DruidDataSource();
    }

    @Primary
    @Bean("dataSource")
    public DataSource dataSource(DruidDataSource druidDataSource) {
        // 注意:必须将数据源包装成 DataSourceProxy,否则 AT 模式不生效
        return new DataSourceProxy(druidDataSource);
    }
}

什么时候该“放弃”分布式事务,改用最终一致性?

需要明确的一点是:并不是所有跨服务操作都值得动用 Seata 或 TCC。当业务对短时间不一致有较高容忍度时,消息队列加上本地事务表,往往更稳、更轻量。

当遇到以下场景,可以认真思考为什么不试试分布式事务:
- 用户注册后发送欢迎邮件(失败时可以告警重试,不阻塞主流程);
- 订单创建后更新推荐系统画像(延迟几秒用户完全无感知);
- 积分变动与优惠券发放耦合度很低,且各自有补偿机制。

核心做法其实不复杂:
- 在订单服务的本地事务里,把需要“扣库存”的事件写入一个 transaction_message 表(注意这个表和订单表处于同一数据库);
- 通过定时任务或监听 binlog,将消息投递到 RocketMQ 或 Kafka;
- 库存服务消费消息,执行扣减,并记录处理状态;
- 如果消费失败,依靠消息重试机制 + 死信队列 + 人工干预来兜底。

相比 Seata,这种方案省去了 TC 组件的运维成本,也避免了长事务锁表、全局事务超时等棘手问题。

TCC 和 Saga 到底怎么选?一句话说清楚

这两个都是柔性事务模型,但设计哲学完全不同。

  • TCC 要求业务自己实现 Try / Confirm / Cancel 三个接口。比如“冻结余额”、“确认扣款”、“解冻余额”。它非常强调资源预留,适合资金类强一致性场景。但代价是开发成本高,而且空回滚、悬挂问题必须手动处理。
  • Saga 是基于事件驱动的长事务。每个步骤发布一个完成事件,下游监听并触发下一步,同时定义好对应的补偿动作。它不要求预留资源,更适合流程长、参与方多、异构系统混搭的场景,比如电商履约:下单 → 仓配 → 物流 → 签收。

一句话判断原则:
- 如果你能提前锁定资源(比如库存、额度),且愿意为每个接口都配上三个方法,选 TCC
- 如果流程不可逆,各环节由不同团队维护,部分服务还是 PHP 或 Go 写的,那用 Saga 会灵活得多(通过 MQ + 补偿任务表可以实现)。

一个经常被忽略的细节是:TCC 的 ConfirmCancel 必须设计成幂等,并且 Try 阶段不能做真实的业务变更——否则 Cancel 根本没办法干净地撤回。

本文转载于:https://www.php.cn/faq/2417696.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注