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

Spring Boot WebClient超时异常时onErrorMap未触发问题排查

问题描述

我有user-ms和post-ms两个微服务,user-ms调用post-ms获取指定用户的帖子数量。在post-ms中设置了7秒延迟来观测user-ms的表现,user-ms里的调用方法代码如下:

public Mono<PostsCountResponse> getPostsCountByUserUuid(UUID userUuid, int delay, int faultPercent) {

        URI url = UriComponentsBuilder.fromUriString(propertiesConfig.getVirtualPostServiceUrl()
                  + "/posts/count?userUuid={userUuid}&delay={delay}&faultPercent={faultPercent}").build(userUuid, delay, faultPercent);

        logger.info("Will call the findPostsCount API on URL: {}", url);
        
        return webClient.get().uri(url).accept(MediaType.APPLICATION_JSON).retrieve()
                .bodyToMono(PostsCountResponse.class)
                .onErrorMap(WebClientResponseException.class, ex -> handleException(ex));
                
    }

    private Throwable handleException(Throwable ex) {

        logger.info("starting handleException()..");

        if (!(ex instanceof WebClientResponseException)) {
            logger.warn("Got a unexpected error: {}, will rethrow it", ex.toString());
            return ex;
        }

        WebClientResponseException wcre = (WebClientResponseException) ex;

        switch (wcre.getStatusCode()) {

        case NOT_FOUND:
            return new NotFoundException(getErrorMessage(wcre));

        case UNPROCESSABLE_ENTITY:
            return new InvalidInputException(getErrorMessage(wcre));

        default:
            logger.warn("Got an unexpected HTTP error: {}, will rethrow it", wcre.getStatusCode());
            logger.warn("Error body: {}", wcre.getResponseBodyAsString());
            return ex;
        }
    }

预期的超时异常已经在user-ms中触发,但配置的onErrorMap方法从未被调用。我想知道为什么onErrorMap没执行,并且希望通过handleException方法处理这个超时异常。以下是user-ms的日志:

instagram-clone-insta-ms-user-info-1  | 2023-05-09 21:50:10.358  INFO 1 --- [nio-8080-exec-5] c.j.i.u.i.PostServiceIntegrationImpl     : Will call the findPostsCount API on URL: http://post-ms/posts/count?userUuid=aeb7f3e9-3e66-4ad8-9bd4-15f2b6a09a7a&delay=7&faultPercent=0
instagram-clone-insta-ms-user-info-1  | 2023-05-09 21:50:12.363 ERROR 1 --- [nio-8080-exec-5] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is reactor.core.Exceptions$ReactiveException: java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 2000ms in 'onErrorResume' (and no fallback has been configured)] with root cause
instagram-clone-insta-ms-user-info-1  |
instagram-clone-insta-ms-user-info-1  | java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 2000ms in 'onErrorResume' (and no fallback has been configured)
instagram-clone-insta-ms-user-info-1  |         at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.handleTimeout(FluxTimeout.java:294) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at reactor.core.publisher.FluxTimeout$TimeoutMainSubscriber.doTimeout(FluxTimeout.java:279) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at reactor.core.publisher.FluxTimeout$TimeoutTimeoutSubscriber.onNext(FluxTimeout.java:418) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at reactor.core.publisher.MonoDelay$MonoDelayRunnable.propagateDelay(MonoDelay.java:270) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at reactor.core.publisher.MonoDelay$MonoDelayRunnable.run(MonoDelay.java:285) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:68) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:28) ~[reactor-core-3.4.7.jar:3.4.7]
instagram-clone-insta-ms-user-info-1  |         at java.base/java.util.concurrent.FutureTask.run(Unknown Source) ~[na:na]
instagram-clone-insta-ms-user-info-1  |         at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source) ~[na:na]
instagram-clone-insta-ms-user-info-1  |         at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) ~[na:na]
instagram-clone-insta-ms-user-info-1  |         at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) ~[na:na]
instagram-clone-insta-ms-user-info-1  |         at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]
instagram-clone-insta-ms-user-info-1  |
原因分析与解决方法

原因

从日志可以看到,触发的异常是TimeoutException,但你当前的onErrorMap仅针对WebClientResponseException类型的异常做处理。TimeoutException不属于WebClientResponseException的子类,所以这个异常不会进入你配置的onErrorMap逻辑,自然不会调用handleException方法。

另外,这个超时异常大概率是WebClient层面或上层调用链中设置了全局超时(比如2秒),而post-ms的7秒延迟超出了这个超时时间,导致WebClient还没收到post-ms的响应(甚至没收到错误响应)时,就触发了超时中断。

解决方法

1. 扩展异常处理范围,覆盖TimeoutException

修改onErrorMap,去掉仅处理WebClientResponseException的限制,让所有异常都进入handleException方法判断:

return webClient.get().uri(url).accept(MediaType.APPLICATION_JSON).retrieve()
        .bodyToMono(PostsCountResponse.class)
        .onErrorMap(ex -> handleException(ex));

然后在handleException方法中新增对TimeoutException的处理分支:

private Throwable handleException(Throwable ex) {

    logger.info("starting handleException()..");

    if (ex instanceof WebClientResponseException) {
        WebClientResponseException wcre = (WebClientResponseException) ex;

        switch (wcre.getStatusCode()) {
            case NOT_FOUND:
                return new NotFoundException(getErrorMessage(wcre));
            case UNPROCESSABLE_ENTITY:
                return new InvalidInputException(getErrorMessage(wcre));
            default:
                logger.warn("Got an unexpected HTTP error: {}, will rethrow it", wcre.getStatusCode());
                logger.warn("Error body: {}", wcre.getResponseBodyAsString());
                return ex;
        }
    } else if (ex instanceof TimeoutException) {
        logger.warn("Request timed out when calling post-ms");
        // 返回自定义超时异常,便于上层统一处理
        return new CustomTimeoutException("调用帖子计数服务超时");
    } else {
        logger.warn("Got an unexpected error: {}, will rethrow it", ex.toString());
        return ex;
    }
}

2. 显式设置WebClient超时并确保异常传递

如果之前的超时是全局配置,也可以在WebClient调用时显式设置超时,确保异常能被onErrorMap捕获:

return webClient.get().uri(url)
        .accept(MediaType.APPLICATION_JSON)
        .retrieve()
        .bodyToMono(PostsCountResponse.class)
        .timeout(Duration.ofSeconds(2)) // 显式设置超时时间
        .onErrorMap(ex -> handleException(ex));

内容的提问来源于stack exchange,提问作者Abdelrahman_Attya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 12:02:20