WebFlux 里能不能直接 block?面试别只答「不能」

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?」示例整理。

相关推荐
Java码农也是农2 小时前
Spring 事务详解:@Transactional 如何生效、何时回滚、传播行为怎么选
java·spring boot·spring·事务
驭渊的小故事1 天前
Spring AOP 核心概念与通知类型详解
java·后端·spring
驭渊的小故事1 天前
Spring AOP 入门:从 AOP 概念到第一个耗时统计切面
java·后端·spring
CoderYanger1 天前
Java EE 进阶:8.MyBatis 操作数据库(入门)(第二弹)
java·数据库·spring boot·spring·面试·java-ee·mybatis
CQU_JIAKE2 天前
11.7[高体】
java·后端·spring
露天赏雪3 天前
第3篇:Sentinel熔断降级实战:大模型接口抖动时的服务兜底方案
java·开发语言·人工智能·spring boot·spring·servlet·sentinel
captain3763 天前
▲统一功能——DispatcherServlet源码分析
java·spring·java-ee·log4j·maven
TeamDev3 天前
使用 JxBrowser 在桌面应用中集成 Spring
java·前端·后端·spring·混合应用·jxbrowser·浏览器控件
SL_staff4 天前
从配置失效到逻辑复用:JVS-Logic 如何构建可治理的业务编排中枢
java·spring·产品
程序员吕洞宾4 天前
开源多维表格SmartTable v1.6.10 —— 用户名登录、数据评论及@人、表单分享二维码、性能优化、部署及升级优化
spring·性能优化·开源