Spring WebClient使用Retry.withThrowable时非429异常被吞致无限等待问题
Spring WebClient 429重试逻辑引发非429异常无限等待的修复方案
问题背景
使用Spring WebClient实现429(请求过多)状态码的重试逻辑:提取响应头Retry-After的值,延迟对应时间后重试。当前实现对429场景有效,但遇到500等非429错误时,应用会无限等待。注释掉.retryWhen(retryAfterFixedDelay())后,异常可正常抛出,推测是该重试逻辑抑制了WebClientResponseException。
原代码示例
public SomeObject getSomeObject(int id) { return apiClient.get() .uri("/some-object/{id}", id) .retrieve() .onStatus(status -> status.equals(TOO_MANY_REQUESTS), this::throwRateLimitException) .bodyToMono(SomeObject.class) .retryWhen(retryAfterFixedDelay()) .block(); } private static Mono<RateLimitException> throwRateLimitException(ClientResponse response) { // 提取Retry-After头值逻辑... final RateLimitException rateLimitException = new RateLimitException(message, retryAfterSeconds); return Mono.error(rateLimitException); } private static Retry retryAfterFixedDelay() { return Retry.withThrowable(throwableFlux -> throwableFlux .filter(RateLimitException.class::isInstance) .flatMap(throwable -> { RateLimitException rateLimitException = (RateLimitException) throwable; log.warn(rateLimitException.getMessage()); return Mono.delay(Duration.ofSeconds(rateLimitException.getRetryAfterSeconds())); })); }
问题原因
原retryAfterFixedDelay()方法中,throwableFlux.filter(RateLimitException.class::isInstance)会过滤掉所有非RateLimitException的异常。当出现500这类错误时,过滤后的流没有元素,retryWhen会一直等待重试信号,导致应用无限挂起。
修复方案
调整重试逻辑,确保非RateLimitException的异常能被直接抛出,而非被过滤后停滞。以下是三种可行的修改方式:
方式一:直接在flatMap中区分异常类型
private static Retry retryAfterFixedDelay() { return Retry.withThrowable(throwableFlux -> throwableFlux .flatMap(throwable -> { if (throwable instanceof RateLimitException) { RateLimitException rateLimitException = (RateLimitException) throwable; log.warn(rateLimitException.getMessage()); return Mono.delay(Duration.ofSeconds(rateLimitException.getRetryAfterSeconds())); } else { // 非429相关异常,直接抛出终止重试流程 return Mono.error(throwable); } })); }
方式二:保留过滤+switchIfEmpty处理剩余异常
private static Retry retryAfterFixedDelay() { return Retry.withThrowable(throwableFlux -> throwableFlux .filter(RateLimitException.class::isInstance) .flatMap(throwable -> { RateLimitException rateLimitException = (RateLimitException) throwable; log.warn(rateLimitException.getMessage()); return Mono.delay(Duration.ofSeconds(rateLimitException.getRetryAfterSeconds())); }) // 若过滤后无元素,说明是其他异常,直接抛出 .switchIfEmpty(Mono.defer(() -> throwableFlux.next().flatMap(Mono::error)))); }
方式三:使用Retry.from更清晰控制逻辑
private static Retry retryAfterFixedDelay() { return Retry.from(companion -> companion .filter(retrySignal -> retrySignal.failure() instanceof RateLimitException) .flatMap(retrySignal -> { RateLimitException ex = (RateLimitException) retrySignal.failure(); log.warn(ex.getMessage()); return Mono.delay(Duration.ofSeconds(ex.getRetryAfterSeconds())); })); }
验证说明
修改后,只有RateLimitException(对应429状态)会触发延迟重试,500等其他异常会直接抛出,不会导致应用无限等待。同时保持了429场景下按Retry-After头延迟重试的逻辑。
内容的提问来源于stack exchange,提问作者Robert Strauch
相关产品推荐
相关产品推荐

