Netty同步Web应用中WebClient调用block()报错及请求挂起问题
解决方案:避免在Netty事件循环线程上执行阻塞操作
问题根源
你遇到的错误和请求挂起问题,核心原因是**block()调用在了Netty的事件循环线程(reactor-http-nio-*)上**。这类线程是专门处理非阻塞IO的,绝对不能被阻塞——一旦被阻塞,事件循环会被卡住,无法处理后续请求,并发量上来后就会出现请求挂起。
你之前给resMono加subscribeOn/publishOn只是让WebClient的请求逻辑在boundedElastic线程执行,但最终block()还是在原Netty事件循环线程上调用,所以根本没解决线程阻塞的问题。而Tomcat环境没问题,是因为Tomcat用的Servlet线程池本身就是为阻塞操作设计的,允许直接调用block()。
正确解决方式
把整个需要阻塞的逻辑提交到Schedulers.boundedElastic()线程池执行,确保block()在允许阻塞的线程上运行。修改后的代码如下:
// 将阻塞逻辑封装到Callable,切换到boundedElastic线程执行 Map<String, Object> result = Mono.fromCallable(() -> { Mono<Map<String, Object>> resMono = wc.post() .body(Mono.just(bodyStr), String.class) .retrieve() .bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {}) .timeout(Duration.ofSeconds(10)) .log("info"); return resMono.block(); }) .subscribeOn(Schedulers.boundedElastic()) .block();
关键说明
Schedulers.boundedElastic()的作用:这是Reactor专门为阻塞操作提供的线程池,会根据请求动态创建线程,避免阻塞核心IO线程,同时限制最大线程数防止资源耗尽。- 不需要额外给WebClient加
subscribeOn:Reactor Netty的WebClient默认会使用自己的IO线程池处理请求,不需要额外切换线程;我们只需要确保block()操作本身在允许阻塞的线程上执行即可。 - 禁止在Netty事件循环线程做阻塞操作:除了
block(),任何同步IO、Thread.sleep()、锁等待等阻塞行为都不能放在这类线程上,否则都会导致事件循环堵塞。
内容的提问来源于stack exchange,提问作者Jagan
相关产品推荐
相关产品推荐

