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

Spring Cloud Gateway配置Retry与CircuitBreaker后出现提前返回响应的问题求助

Spring Cloud Gateway配置Retry与CircuitBreaker后出现提前返回响应的问题求助

各位好,我在配置Spring Cloud Gateway的认证请求拦截器时遇到了一个棘手的问题:当同时启用Retry和CircuitBreaker过滤器后,请求会被CircuitBreaker提前拦截,在AuthService还没返回响应的时候就把结果返回给客户端了。如果我把Retry和CircuitBreaker的配置块去掉,整个网关的请求流程就正常工作,但这两个功能是业务必须的,所以想请教大家有没有遇到类似的问题,或者能帮我排查下哪里配置出错了?


相关代码配置

1. GatewayConfig路由配置类

@Configuration
class GatewayConfig(
    private val errorFilter: GlobalErrorFilter,
) {
    @Bean
    fun authRequestFilter(
        builder: RouteLocatorBuilder,
    ) = builder.routes()
        .route(authService.name) { r ->
            r.path(authService.path)
                .filters { f ->
                    f.retry { retryConfig ->
                        retryConfig.apply {
                            setStatuses(
                                HttpStatus.PRECONDITION_FAILED, // 令牌验证失败
                                HttpStatus.BAD_REQUEST
                            )
                            retries = RETRY_ATTEMPTS.toInt()
                            setBackoff(
                                INITIAL_RETRY_DELAY,
                                MAX_DELAY_BETWEEN_RETRY,
                                RETRY_FACTOR,
                                false
                            )
                            setMethods(
                                // 仅对以下方法重试
                                HttpMethod.GET,
                                HttpMethod.POST
                            )
                            setExceptions(
                                ConnectException::class.java,
                            )
                        }
                    }.circuitBreaker { config ->
                        config.setName("fallback") // 名称与application.properties中配置一致,不可修改
                            .setStatusCodes(
                                // 触发断路器的状态码集合
                                setOf(
                                    HttpStatus.INTERNAL_SERVER_ERROR.value().toString()
                                )
                            )
                            .setFallbackUri("forward:${Endpoint.FALLBACK}") // 降级路由
                    }.filter(
                        errorFilter,
                        FILTER_ORDER // 当前配置为-2
                    ).modifyResponseBody( // 处理AuthService返回的响应
                        ResponseWrapper::class.java,
                        Any::class.java
                    ) { exchange, response ->
                        if (response.status == ResponseStatus.SUCCESS && response.payload != null)
                            Mono.just(response.payload)
                        else
                            Mono.just(
                                ResponseWrapper(
                                    status = response.status,
                                    payload = response.payload ?: response.status.value,
                                )
                            )
                    }
                }.uri(authService.uri)
        }
        .build()!!
}

2. GlobalErrorFilter全局错误过滤器

@Component
@Primary
class GlobalErrorFilter(
    private val mapper: ObjectMapper,
) : GatewayFilter {
    override fun filter(
        exchange: ServerWebExchange,
        chain: GatewayFilterChain,
    ) = chain.filter(exchange)
        .onErrorResume { handleAuthenticationError(exchange, it) }

    fun handleAuthenticationError(
        exchange: ServerWebExchange,
        error: Throwable,
    )= when (error) {
        is NonRetryableAuthenticationException ->
            error.status to ResponseWrapper(
                status = error.responseStatus,
                payload = error.message ?: error.responseStatus.value
            )
        is RetryableAuthenticationException ->
            error.status to ResponseWrapper(
                status = error.responseStatus,
                payload = error.message ?: error.responseStatus.value
            )
        else -> {
            logger.error("Unexpected error: ${error.message}")
            HttpStatus.INTERNAL_SERVER_ERROR to ResponseWrapper(
                status = ResponseStatus.INTERNAL_SERVER_ERROR,
                payload = ResponseStatus.INTERNAL_SERVER_ERROR.value
            )
        }
    }.let { (statusCode, responseWrapper) ->
        writeErrorResponse(exchange, statusCode, responseWrapper)
    }

    private fun writeErrorResponse(
        exchange: ServerWebExchange,
        statusCode: HttpStatusCode,
        responseWrapper: ResponseWrapper<*>,
    ): Mono<Void> {
        exchange.response.statusCode = statusCode
        exchange.response.headers.contentType = MediaType.APPLICATION_JSON
        return exchange.response.writeWith(
            Mono.fromCallable { mapper.writeValueAsBytes(responseWrapper) }
                .map { exchange.response.bufferFactory().wrap(it) }
        )
    }

    companion object {
        private val logger = LoggerFactory.getLogger(JWTAuthenticationFilter::class.java)
    }
}

3. application.properties中的Resilience4j配置

resilience4j.circuitbreaker.instances.fallback.registerHealthIndicator=true
resilience4j.circuitbreaker.instances.fallback.sliding-window-size=10
resilience4j.circuitbreaker.instances.fallback.minimum-number-of-calls=5
resilience4j.circuitbreaker.instances.fallback.permitted-number-of-calls-in-half-open-state=3
resilience4j.circuitbreaker.instances.fallback.wait-duration-in-open-state.seconds=30
resilience4j.circuitbreaker.instances.fallback.failure-rate-threshold=50
resilience4j.circuitbreaker.instances.fallback.slow-call-duration-threshold.seconds=3
resilience4j.circuitbreaker.instances.fallback.slow-call-rate-threshold=50
resilience4j.circuitbreaker.instances.fallback.automatic-transition-from-open-to-half-open-enabled=true
resilience4j.circuitbreaker.instances.fallback.sliding-window-type=count_based

可能的问题排查方向与解决方案建议

1. 调整过滤器执行顺序

当前GlobalErrorFilter的执行顺序是-2(优先级很高),可能会在CircuitBreaker和Retry之前捕获错误并返回响应,导致后两者的逻辑被跳过或误触发。

建议:

  • 将GlobalErrorFilter的order值调整为更大的数值(比如100),让它在CircuitBreaker和Retry之后执行,确保断路器和重试逻辑先完成错误判断。
  • 或者明确指定Retry和CircuitBreaker的执行顺序,确保CircuitBreaker包裹整个重试流程(即先配置CircuitBreaker,再在内部配置Retry)。

2. 检查CircuitBreaker的触发条件

当前CircuitBreaker配置了slow-call-duration-threshold=3秒,如果AuthService的响应时间接近或超过3秒,会被标记为慢调用,当慢调用率超过50%时,断路器会直接打开触发降级。

建议:

  • 检查AuthService的响应时间是否稳定在3秒以内,若业务允许,可适当延长慢调用阈值。
  • 暂时扩大CircuitBreaker的状态码触发范围,排查是否有重试过程中的中间状态被误判为失败。

3. 修正Retry与CircuitBreaker的组合逻辑

当前配置是先执行Retry再执行CircuitBreaker,这会导致断路器仅监控最后一次重试的请求,而非整个重试流程。推荐的做法是让CircuitBreaker包裹Retry。

修改示例:

.circuitBreaker { config ->
    // 原CircuitBreaker配置
}.retry { retryConfig ->
    // 原Retry配置
}

4. 排查错误处理逻辑冲突

GlobalErrorFilter的onErrorResume会捕获所有错误并返回响应,可能与CircuitBreaker的降级逻辑冲突,导致提前返回。

建议:

  • 暂时注释掉GlobalErrorFilter的配置,测试Retry和CircuitBreaker是否正常工作,确认是否为错误过滤器的问题。
  • 若存在冲突,可缩小GlobalErrorFilter的错误捕获范围,仅处理断路器和重试未覆盖的错误类型。

5. 验证响应修改逻辑

modifyResponseBody过滤器会修改响应体,可能导致CircuitBreaker误判响应状态。

建议:

  • 暂时注释掉modifyResponseBody配置,测试核心流程是否正常,确认是否为响应修改逻辑导致的问题。
  • 在响应修改逻辑中添加日志,查看修改前后的响应状态和内容,排查是否有异常转换。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 13:39:30