欢迎光临
我们一直在努力

如何解决Sentinel和Seata AT分布式事务的冲突?

一、冲突根源

  • Seata AT:@GlobalTransactional 依赖异常向上抛出感知分支调用失败,触发全局事务回滚;
  • Sentinel Feign 自动熔断:远程调用超时、异常、触发熔断阈值时,直接执行本地Fallback降级逻辑,吞掉原始异常;
  • 最终现象:下游RM分支未执行业务SQL、未注册分支事务,但TM无异常感知,全局事务正常提交,多库数据不一致。
  • ⚠️ 关键区分:

    • 限流规则(FlowRule):抛出BlockException,异常会向上抛出,不会干扰Seata事务,可以正常共存;
    • 熔断降级规则(DegradeRule)+ Feign Fallback:吞异常,是唯一冲突点。

    二、五大落地解决方案

    方案1:分层开关隔离

    生产标准首选,零侵入

    核心思路:全局开启Sentinel限流能力,仅对@GlobalTransactional包裹的事务链路,单独关闭Feign Sentinel自动熔断;非事务查询接口保留熔断降级。

    1)全局配置

    开启限流,默认关闭Feign熔断

    # 全局启用Sentinel
    spring.cloud.sentinel.dashboard: 127.0.0.1:8080
    # 默认全局关闭Feign sentinel降级
    feign.sentinel.enabled: false

    此时所有Feign调用不会触发Fallback降级,但接口QPS、热点限流规则依然正常生效。

    2)非事务查询接口单独开启降级(局部开启)

    不修改全局配置,单独给查询类Feign客户端指定Sentinel降级工厂:

    @FeignClient(value = "goods-service",
    fallbackFactory = GoodsFeignFallbackFactory.class,
    configuration = FeignSentinelConfig.class)
    public interface GoodsFeignClient {
    // 纯查询接口,无分布式事务,允许降级
    }

    配套配置类单独开启该Feign客户端的Sentinel熔断,互不影响事务链路。

    优势
    • 写事务链路绝对安全,Seata回滚不受任何干扰;
    • 查询接口正常享受熔断降级高可用保护;
    • 无需改动业务代码,纯配置隔离,运维清晰。

    方案2:事务内Feign降级后主动抛异常

    无法全局关闭降级时,在Fallback降级方法里主动抛出业务运行时异常,让TM捕获异常触发全局回滚。

    局限:下游RM根本没执行SQL,无分支事务,Seata只能回滚上游已执行本地事务,下游无任何操作,不会造成脏数据,但无法重试恢复下游服务。

    1)Feign降级工厂代码

    @Component
    public class StockFeignFallbackFactory implements FallbackFactory<StockFeignClient> {
    @Override
    public StockFeignClient create(Throwable cause) {
    return new StockFeignClient() {
    @Override
    public void deduct(Long goodsId, Integer num) {
    log.error("库存服务远程调用熔断降级", cause);
    // 主动抛出运行时异常,向上传递给TM
    throw new RuntimeException("下游库存服务不可用,事务回滚");
    }
    };
    }
    }

    2)TM全局事务注解

    @GlobalTransactional(rollbackFor = Exception.class)
    public void createOrder() {
    insertOrder();
    stockFeignClient.deduct(1L,10);
    }

    降级抛出异常 → TM感知 → 全局事务回滚订单插入操作。

    适用场景

    老项目已经全局开启feign.sentinel.enabled=true,不方便整体回滚,临时兼容改造。

    方案3:返回值判断+手动触发Seata回滚

    降级方法返回标记结果(布尔/枚举),TM收到降级标识后,手动调用Seata API强制回滚全局事务。

    1)Feign接口定义返回值

    public interface StockFeignClient {
    // true:扣减成功;false:降级/调用失败
    Boolean deduct(Long goodsId, Integer num);
    }

    2)降级方法返回失败标识

    @Override
    public Boolean deduct(Long goodsId, Integer num) {
    log.warn("库存服务熔断降级");
    return false;
    }

    3)TM业务代码主动判断并回滚

    @GlobalTransactional
    public void createOrder() {
    insertOrder();
    Boolean res = stockFeignClient.deduct(1L, 10);
    if (!res) {
    // 手动触发全局事务回滚
    String xid = RootContext.getXID();
    GlobalTransactionContext.reload(xid).rollback();
    throw new RuntimeException("下游服务降级,事务已回滚");
    }
    }

    方案4:线程隔离策略改造

    禁止Sentinel线程池隔离
    Sentinel两种隔离模式:

  • 线程池隔离:Feign调用新开独立线程,XID上下文丢失,必然断事务;
  • 信号量隔离:复用调用方原有线程,XID上下文保留,不会丢失事务标识。
  • 事务内Feign调用的Sentinel规则,强制设置隔离模式为信号量隔离,避免线程切换丢失XID。
    ⚠️ 该方案仅保证上下文不丢失,依然存在降级吞异常问题,必须搭配方案2一起使用,不单独推荐。

    方案5:架构拆分根治

  • 写操作(分布式事务链路)
    不使用Feign同步调用,改用RocketMQ本地消息表+可靠消息最终一致性异步解耦;
    事务提交成功后发送MQ,库存、余额服务消费消息异步执行扣减,彻底规避同步Feign熔断冲突。
  • 读操作(查询、非核心附属信息)
    保留Feign + Sentinel熔断降级,本地缓存兜底,提升可用性。
  • 三、配套边界约束细则

    1)限流规则完全兼容,无需改动

    Sentinel流控、热点参数限流触发时抛出BlockException,异常会向上抛出,Seata正常捕获回滚,可以放心共存,仅熔断降级需要隔离。

    2)全局异常处理器注意事项

    @RestControllerAdvice不能吞掉BlockException、业务运行时异常,否则Seata依然感知不到异常,事务无法回滚。

    3)Nacos持久化规则拆分

    Sentinel规则持久化到Nacos时,分开维护两套规则:

    • 事务写接口:仅配置流控规则,不配置降级规则;
    • 查询读接口:流控+降级规则完整配置。

    四、最终落地选型建议

  • 新项目首选方案1(分层开关隔离)
    架构干净、无业务代码侵入、事务安全性最高,阿里微服务生产标准实践;
  • 存量老项目改造选方案2
    改动量小,只修改Fallback降级类,快速修复事务不一致问题;
  • 高并发秒杀长事务场景
    直接采用方案5消息异步化拆分,从架构层面彻底消除同步调用冲突。
  • 五、高频踩坑

  • 误区:降级方法抛异常就能让下游RM回滚
    降级时远程Feign根本没有发起HTTP请求,下游RM没有执行SQL、没有注册分支事务,只能回滚上游TM本地事务,下游无操作,不会产生脏数据,但无法重试恢复下游服务。
  • 误区:Sentinel所有规则都会和Seata冲突
    只有DegradeRule熔断降级会吞异常冲突,FlowRule限流无任何冲突,可正常使用。
  • 误区:信号量隔离就能彻底解决冲突
    仅保留XID上下文,无法解决降级吞异常问题,必须额外抛出异常触发回滚。
  • 赞(0)
    未经允许不得转载:171主机测评 » 如何解决Sentinel和Seata AT分布式事务的冲突?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址