bigywater.tech
Spring · 123

Gateway 的响应式模型:阻塞调用的坑

为什么网关的过滤器里不能写阻塞代码?阻塞会让吞吐掉到什么程度?Mono.block() 会怎样?放到 boundedElastic 上能救吗?这篇用 20 与 64 个并发请求在 Spring Cloud Gateway 5.0.3 上量出来。

2.06 秒
2 个事件循环线程下,20 个并发请求、过滤器里 Thread.sleep(200)
500
过滤器里调用 Mono.block() 的结果,Reactor 拦住事件循环线程上的阻塞
0.23 秒
把阻塞调用放到 boundedElastic 上,同样的 20 个请求

固定 2 个线程

实测 · 本机执行输出
  过滤器什么都不做:20 个请求总耗时 0.02 秒,状态码 {200: 20};过滤器运行过的线程: [reactor-http-nio-N],其中 reactor-http-nio 线程共 2 个
  过滤器里 Thread.sleep(200)(阻塞事件循环线程):20 个请求总耗时 2.06 秒,状态码 {200: 20};过滤器运行过的线程: [reactor-http-nio-N],其中 reactor-http-nio 线程共 2 个
  过滤器里 Mono.delay(200ms)(不阻塞):20 个请求总耗时 0.22 秒,状态码 {200: 20};过滤器运行过的线程: [reactor-http-nio-N],其中 reactor-http-nio 线程共 2 个
  过滤器里把 sleep(200) 放到 Schedulers.boundedElastic() 上:20 个请求总耗时 0.23 秒,状态码 {200: 20};过滤器运行过的线程: [reactor-http-nio-N],其中 reactor-http-nio 线程共 2 个
  过滤器里调用 Mono.block():20 个请求总耗时 0.01 秒,状态码 {500: 20};过滤器运行过的线程: [reactor-http-nio-N],其中 reactor-http-nio 线程共 2 个;block() 的异常:IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-1

阻塞调用让总耗时变成请求数除以线程数乘以单次耗时。

默认线程数

实测 · 本机执行输出
  总耗时 1.47 秒,状态码 {200: 64};过滤器运行过的线程: [reactor-http-nio-N],其中 reactor-http-nio 线程共 10 个
  对照,Mono.delay(200ms):总耗时 0.23 秒,状态码 {200: 64}

等于 CPU 核数,阻塞只是被核数摊薄,不会消失。

分步演示

响应式阻塞
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。后端是立即返回的 Python HTTP 服务;客户端用 Python 线程同时发出 N 个请求并量总耗时(先预热 4 个请求);本机 10 核;线程数用 -Dreactor.netty.ioWorkerCount 设置
    • 没有验证:BlockHound 对阻塞调用的检测;在过滤器里用 RestTemplate / JDBC 这类真实的阻塞调用的表现(这里只用 Thread.sleep 模拟);Gateway Server MVC(基于虚拟线程 / Servlet)是否绕开这个问题;boundedElastic 线程池的上限与排队;请求体较大时的内存与缓冲
    京ICP备2026067502号-1京公网安备11010502063487号