文章 555
评论 5
浏览 199932
Spring Cloud Gateway 限流降级插件冲突:多个 Filter 同时拦截报错?责任链优先级调度+冲突检测

Spring Cloud Gateway 限流降级插件冲突:多个 Filter 同时拦截报错?责任链优先级调度+冲突检测

公司的网关配了限流和降级两个 Filter。一次大促,流量触发了限流——RequestRateLimiter 返回了 429。按说限流拦截了就不该再走后续的 Filter 了。但 Hystrix 降级 Filter 也触发了,返回了 503。两个 Filter 同时想写响应,冲突了——客户端收到的状态码是 429,但 body 是 503 的降级 JSON。前端判断逻辑直接崩了。

Spring Cloud Gateway 的 Filter 是按责任链模式执行的,一个请求依次经过所有 Filter。问题在于:当一个 Filter 已经决定要拦截并返回了,它之后的 Filter 不知道前面已经拦截了,还在继续执行。

今天聊聊怎么给 Filter 设明确的优先级,让它们在冲突时按规则来,而不是同时抢着写响应。


责任链里的冲突怎么来的

Gateway 的 Filter 链执行顺序由 @Order 注解决定。默认情况下,Spring Cloud Gateway 的内置 Filter 顺序大致是:

NettyRoutingFilter(-1)
  → 自定义 Filter A(0)
  → 自定义 Filter B(0)
  → 自定义 Filter C(0)
  → 最后执行业务逻辑

如果你配了 RequestRateLimiter 和 CircuitBreaker,它们在同一个优先级上的行为是不确定的——这取决于 Spring 扫描 Bean 的顺序。不同环境、不同版本可能不一样。

而且更关键的是:即使限流 Filter 已经把响应设成了 429,Gateway 仍然会继续执行后面的 Filter——除非前面的 Filter 明确标记"到此为止"。


解法一:明确优先级,让关键拦截在前面

给每个 Filter 指定明确的 @Order

@Order(-100)  限流 Filter      —— 最先执行,不放行的直接返回
@Order(-90)   认证鉴权 Filter   —— 没登录的不让过
@Order(-80)   降级熔断 Filter   —— 服务挂了走降级
@Order(-70)   日志追踪 Filter   —— 以上都过了才记录
@Order(0)     默认

限流必须排在最前面。如果限流已经拦了,后面的鉴权和降级都不需要执行。如果鉴权没过,降级也没必要触发——你都还没走到业务逻辑。

@Component
@Order(-100)
public class PriorityRateLimitFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        if (isOverLimit(exchange)) {
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            // ★ 直接返回,不调 chain.filter,后面的 Filter 不再执行
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange); // 正常通过
    }
}

关键是:被拦截时调用 response.setComplete() 而不是 chain.filter() 后者会把请求传给下一个 Filter,前者直接在当前位置结束整个链路。


解法二:冲突检测——两个 Filter 都想写响应就报错

光靠优先级不能解决所有问题。如果两个 Filter 在同一个优先级上,又没有冲突检测,还是各写各的。

一个防御性的做法:在 Filter 写响应之前,检查响应是不是已经被前面的 Filter 写过了。

@Component
@Order(-80)
public class SafeCircuitBreakerFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 检查响应是否已被前面的 Filter 处理(如限流已返回 429)
        if (exchange.getResponse().isCommitted()) {
            log.warn("响应已被前置 Filter 提交,跳过降级逻辑");
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }
}

response.isCommitted() 检查响应头是否已经发送。如果前面已经有 Filter 写了响应,当前 Filter 就应该跳过。这是最低成本的冲突防护——不判断状态码对不对,只判断"有没有人已经动过响应了"。


实际部署中的建议

三种方式叠起来用:

Filter 执行流程:
  限流 Filter (@Order -100)
    ├─ 过限 → 返回 429 + setComplete() → 停止
    └─ 通过 → chain.filter() → 下一个

  鉴权 Filter (@Order -90)
    └─ response.isCommitted()? → 是 → 跳过
    └─ chain.filter()

  降级 Filter (@Order -80)
    └─ response.isCommitted()? → 是 → 跳过
    └─ chain.filter()

排序原则:拦截型的排前面,旁路型(日志、追踪)排后面。 拦截型的 "提前结束" 让旁路型不需要再判断 isCommitted


总结

多个 Filter 抢着写响应,不是因为 Filter 太多了,而是因为没人管它们谁先谁后。

三条规则:

  • @Order 明确优先级——限流必须排第一,降级排第二,日志追踪排最后
  • 拦截时直接 setComplete(),不调 chain.filter()——后面 Filter 不执行
  • response.isCommitted() 做冲突检测——有人写过响应了就跳过

配完之后,网关的 Filter 链从"谁先抢到算谁的"变成"按规矩排队"。



标题:Spring Cloud Gateway 限流降级插件冲突:多个 Filter 同时拦截报错?责任链优先级调度+冲突检测
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/06/29/1782636509921.html
公众号:服务端技术精选

服务端开发博客:后端架构、高并发、性能优化与微服务实战教程

取消