You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Spring Cloud的微服务跨服务事务回滚实现方案咨询

解决跨服务调用后的分布式事务回滚问题

首先得明确:你现在遇到的核心问题是本地事务无法跨服务生效——Application 2的@Transactional只管控自己数据库的操作,一旦它成功保存账户并提交事务,哪怕Application 1后续抛出NullPointerException,也没法让Application 2回滚已经写入的数据。要解决这个问题,你需要引入分布式事务机制,这里推荐用Spring生态友好的Seata来实现强一致性的分布式事务。

具体实现步骤

1. 引入Seata依赖

在两个服务的pom.xml(Maven项目)中添加Seata的Spring Cloud Starter适配依赖:

<dependency>
    <groupId>io.seata</groupId>
    <artifactId>seata-spring-boot-starter</artifactId>
    <version>最新稳定版</version>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    <version>与你的Spring Cloud版本匹配的适配版</version>
</dependency>

2. 配置Seata事务协调者(TC)

你需要启动一个Seata Server作为全局事务的协调中心(生产环境推荐独立部署,测试阶段也可以用内嵌模式)。在两个服务的application.yml中添加Seata基础配置:

seata:
  enabled: true
  application-id: accounts-service-1 # Application1用这个ID,Application2改成accounts-service-2
  tx-service-group: my_custom_tx_group
  config:
    type: file # 或nacos等配置中心,根据你的技术栈选择
    file:
      name: file.conf
  registry:
    type: nacos # 或eureka,对应你的服务发现组件
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848

3. 改造代码中的事务注解

  • Application 1(全局事务发起方):把WebAccountsService中的@Transactional替换为Seata的@GlobalTransactional,这个注解会开启全局事务,自动协调所有参与的服务:
// 替换原有的@Transactional注解
@GlobalTransactional(rollbackFor = Exception.class)
public void createAccount(Account account) {
    try {
        account.setOwner(account.getOwner()+System.currentTimeMillis()%1000);
        HttpEntity<Account> request = new HttpEntity<>(account);
        System.out.println("==================="+account.getOwner());
        String url=serviceUrl + "/create-accounts";
        ResponseEntity<String> response = restTemplate.postForEntity(
            url, request , String.class );
        String s = null;
        s.equalsIgnoreCase("asa"); // 这里触发NPE
    } catch (NullPointerException e) {
        throw e;
    }
}
  • Application 2(事务参与方):保持原来的@Transactional即可,Seata会自动将这个本地事务纳入全局事务的管控范围:
@PostMapping(value = "/create-accounts")
@Transactional(rollbackFor = Exception.class)
public void createAccount(@RequestBody Account account) throws Exception {
    try {
        logger.info("========================================================= " + account.getOwner());
        accountRepository.save(account);
        logger.info("accounts-service byNumber() found: " + account);
    } catch (Exception e) {
        throw e;
    }
}

4. 配置Seata代理数据源

Seata需要代理你的数据源来实现分支事务的拦截与回滚,所以在两个服务中添加数据源代理配置:

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource rawDataSource() {
        return new DruidDataSource(); // 替换为你项目中使用的数据源类型
    }

    @Bean
    public DataSourceProxy dataSourceProxy(DataSource rawDataSource) {
        return new DataSourceProxy(rawDataSource);
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSourceProxy dataSourceProxy) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSourceProxy);
        // 保留你原有的MyBatis配置,比如mapperLocations等
        return factoryBean.getObject();
    }
}

方案原理说明

当Application 1的createAccount方法开启全局事务后,调用Application 2接口时,Seata会自动生成分支事务并绑定到全局事务上下文。如果Application 1后续抛出NullPointerException,全局事务会触发回滚指令,Seata会协调Application 2的分支事务回滚已经写入的账户数据,实现跨服务的事务一致性。

备选方案:最终一致性(基于消息队列)

如果你的业务场景允许最终一致性(不需要实时回滚),可以用事务消息实现:

  • Application 1先发送一条半事务消息到MQ(如RocketMQ)
  • Application 2消费消息并创建账户,成功后通知MQ确认消息
  • 如果Application 1后续抛出异常,取消半事务消息;若Application 2已执行,则发送补偿消息让其删除账户

这种方案适合对一致性要求不是强实时的场景,但实现复杂度更高。

内容的提问来源于stack exchange,提问作者Mr Special

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:53:59