
WebFlux 里能不能直接 block?面试别只答「不能」
这题要是只答「WebFlux 不能阻塞」,大概率马上被追问。
我会先把问题掰回线程模型:WebFlux 常见的 Netty 运行方式,靠少量事件循环线程处理很多连接。它的优势不是让单次 SQL 或 HTTP 调用凭空变快,而是线程不用傻等 I/O。
所以,真正不能做的是把等待留在事件循环上。
上面这张图可以当作回答的主线。WebClient、R2DBC 这类非阻塞链路可以留在事件循环里;JDBC、老的 RPC SDK、文件读写这类同步调用,得在进入链路时就明确切出去。
Spring 官方文档也说得很克制:非阻塞栈的收益在于用较少、固定数量的线程扩展并发,但前提是应用里的组件也得尽量非阻塞。不是一换 WebFlux 就自动高并发。
遇到同步 SDK,别在 map 里顺手调
下面这种写法很常见,代码也能跑:
java
@GetMapping("/users/{id}")
Mono<User> user(@PathVariable String id) {
return webClient.get()
.uri("/profiles/{id}", id)
.retrieve()
.bodyToMono(User.class)
.map(profile -> {
auditClient.write(profile); // 同步网络 I/O
return profile;
});
}
问题不在 map,而在 auditClient.write。它会占住当前执行这段回调的线程;如果这正好是 Netty 的事件循环,几个慢请求就够让后面的连接排队。
Reactor FAQ 给出的包裹同步调用的模式是 fromCallable 加 subscribeOn(Schedulers.boundedElastic()):
java
@GetMapping("/users/{id}")
Mono<User> user(@PathVariable String id) {
return Mono.fromCallable(() -> legacyUserClient.fetch(id))
.subscribeOn(Schedulers.boundedElastic());
}
fromCallable 的意思很朴素:别现在就执行,把同步调用封进一个延迟执行的任务。再由 boundedElastic 提供专门等待阻塞 I/O 的有限工作线程。
如果要在一条非阻塞链路旁边写同步审计,也别塞进 map:
java
return webClient.get()
.uri("/profiles/{id}", id)
.retrieve()
.bodyToMono(User.class)
.flatMap(profile ->
Mono.fromRunnable(() -> auditClient.write(profile))
.subscribeOn(Schedulers.boundedElastic())
.thenReturn(profile)
);
这不是为了把代码写复杂。边界被写出来,压测时才知道到底是事件循环慢了,还是同步依赖慢了。
publishOn 和 subscribeOn 别混着背
面试官接着问「为什么是 subscribeOn」时,可以这么讲:
subscribeOn 影响订阅和上游执行的位置。同步调用放在 Mono.fromCallable 里,就应该在它旁边指定 subscribeOn,让这段源头工作去 boundedElastic。
publishOn 更像从它出现的位置开始,把后续信号切到另一个调度器。它不是给一个已经堵住事件循环的同步方法补救用的。
还有个误区是把 boundedElastic 当无限线程池。它的名字里有 bounded:线程数和排队任务都有边界,突发量上来时任务会被延后。这个延后反而是有价值的,它把同步依赖的容量问题暴露出来,而不是继续把机器拖垮。
那 block() 到底为什么不行
在 WebFlux 控制器的请求链里调用 block(),等于把本来可以继续流转的响应式链改成当前线程干等。
Reactor 的非阻塞调度器会拒绝这类阻塞调用,常见结果是 IllegalStateException。即使某个普通线程上没报错,它也没有变安全,只是悄悄把异步链路又写回了同步等待。
我会用这句话收口:旧系统有少量 JDBC 或同步 SDK,可以在边界上用 fromCallable + boundedElastic 隔开;如果整条业务链主要都是 JDBC、同步 RPC 和阻塞存储,那瓶颈还是那些依赖,继续硬上 WebFlux 往往只会增加维护成本。Spring MVC 未必更差,选和依赖模型匹配的栈更重要。
文中模式依据 Spring Framework Reference 的 Web on Reactive Stack 章节,以及 Project Reactor FAQ 的「How Do I Wrap a Synchronous, Blocking Call?」示例整理。