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
相关产品推荐
相关产品推荐

