首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >DeferredResult真相:没有提高QPS,没有优化系统性能!那Spring为什么要发布这个功能?有示例源码

DeferredResult真相:没有提高QPS,没有优化系统性能!那Spring为什么要发布这个功能?有示例源码

作者头像
烟雨平生
发布2026-04-14 18:27:01
发布2026-04-14 18:27:01
230
举报

先讲为什么:DeferredResult可以提升Servelt容器工作线程池的复用率。同时,也没有改变Servelt容器的最大物理连接数。如果新请求超过Servelt容器的最大连接,会报错:HTTP connect timed out。

Tomcat容器最多能同时处理2个请求,

即使用了DeferredResult,后面的所有请求仍然会timed out。

源码见下

同时DeferredResult有增加系统负载的风险,如何使用要提前做好评估。是不是感觉与Service层方法加@Async的效果相同?是的,都是在优化worker线程利用率。不过@Async会多出往异步线程池中“任务提交动作完成”的耗时,一旦提交成功会同时释放worker线程和Connections连接,更彻底。

是的,你没有看错,DeferredResult就只有这个作用。

来回应下常见的疑问:

是否能提升吞吐量?

是否能提升QPS?

标准的回答是:不确定,要看情况。

大概率是可以提升系统吞吐量的。

大概率是可以提升系统QPS的。

为什么?

场景1:如果请求进来的都是DeferredResult的慢请求,系统吞吐量和系统QPS都不会提升。

场景2:如果DeferredResult的慢请求不多,DeferredResult是可以提升其它轻量级接口的QPS的。如果没有DeferredResult,Servlet容器中的工作线程都被占用,这些接口也会能阻塞等待,QPS自然就降低,系统吞吐量自然就下降。

上面讲的“提升线程池的复用率”,到底是提升哪些线程的复用率。线程池很多,有点乱。。。

好问题,一个请求进来,经过的线程池真的很多。

以常见的Tomcat为例,来一块看看这个线程池:

就是这个max-threads

Spring Boot中配置会略有不同,在3.3.5中工作线程数配置如下:

代码语言:javascript
复制
server.tomcat.max-connections: 设置连接器的最大连接数。
server.tomcat.accept-count: 设置连接器的接受队列长度,即当所有工作线程都被占用时,可以接受的并发请求数量。
server.tomcat.threads.max: 设置连接器的工作线程(线程池)的最大线程数。
server.tomcat.threads.min-spare: 设置连接器的工作线程(线程池)的最小空闲线程数。

有点感觉了没?下面我们来全视角、全链路地看一下这个线程池的位置。

仍然以Tomcat为例:

图片
图片

有两部分:Servlet容器的连接器、处理业务的Servlet容器。

图片
图片

图中的线程池(Executor)就是DeferredResult提升复用率的那个!

图中的Worker线程(max-threads工作线程)

就是DeferredResult提升复用率的那个!

上面看着是不是有些乱,下面汇总一下:

一、它主要解决什么问题 DeferredResult 的核心作用,是提升Servelt容器工作线程池的复用率,是把控制器中耗时的业务计算移到独立的用户自定义的线程池中异步执行,从而让负责接收请求的容器线程立刻释放、返回。

这样做有两个明显的好处:

  1. 避免控制器线程长时间阻塞,让它能快速回到线程池,继续处理其他请求;
  2. 在负载尚未达到连接上限时,整体的请求处理效率更高、批次完成时间更短。

关键要明确:它的价值在于释放 Servlet 工作线程,提升了利用率,而不是提升系统的连接接纳能力。


二、为什么它有效(从请求链路看) 要理解 DeferredResult,得先回顾 Tomcat 处理请求的几个关键配置:

  • max-connections:限制同时持有的连接数;
  • threads.max:工作线程数上限;
  • accept-count:工作线程全忙时,请求排队的队列长度。

一个常见的容量直觉是:系统真正能同时处理的请求数,约等于 min(max-connections, threads.max)accept-count 只是队列缓冲。

容量评估实践:一个Tomcat最多能同时处理多少个HTTP请求?

同步模型下,工作线程一旦进入控制器并执行耗时操作(如 sleep、IO、RPC),在该请求完成之前,这个线程一直被占用,无法响应其他请求。

而使用 DeferredResult

  1. 控制器创建 DeferredResult 后立即返回,工作线程随之释放;
  2. 实际业务在独立线程池中执行,执行完毕后再通过回调将结果写回响应。

这里必须澄清它的边界

  • 连接在响应完全返回前始终占用一个连接名额,因此 DeferredResult 不会让系统接纳更多连接;
  • 最终网络 I/O 写回响应仍由容器线程执行,网络开销不变。

三、结合代码与测试验证 我们通过实际代码来看实现与效果。

https://github.com/helloworldtang/spring-boot-study

代码入口(可对照项目定位)

  • 同步端点:/sync/process 对应 AsyncDemoController.java:25,业务阻塞在 :29
  • 异步端点:/async/process 对应 AsyncDemoController.java:35
  • DeferredResult 创建与回调: :41 创建,:43 设置超时回调,:48 错误处理。
  • 提交到线程池::53,完成日志在 :58
  • 释放控制器线程的关键位置::68(直接 return)。

线程池配置 独立任务池在 TaskConfig.java:11 定义(ThreadPoolTaskExecutor)。

关键配置(application.yml)

代码语言:javascript
复制
server:
  port: 8080
  tomcat:
    threads:
      max: 2
      min-spare: 1
    max-connections: 5
    accept-count: 1

日志中增加了类名、行号与 X-Request-Id 跟踪(:15)。

并发对比测试 线程数限制为 2,模拟 4 个并发请求,每个耗时约 1.5 秒:

  • 同步模式总耗时约 3.0 秒AsyncDemoIntegrationTest.java:58
  • 异步模式总耗时约 1.7–1.8 秒:76

结果说明:DeferredResult 快速释放工作线程,后台池并行执行,同一批请求完成更快。

连接数严苛场景测试 设置 max-connections=2accept-count=1,模拟 30 个请求(客户端连接超时 200ms):

  • 异步同样出现大量连接超时/拒绝(见 StrictConnectionLimitsTest.java:94 及统计行 :101
  • 同步模式在连接阶段更容易被拒绝(对比 :31:59

结论再次印证:DeferredResult 不改变连接占用周期,不提升连接接纳能力;一旦连接或队列饱和,仅靠控制器异步化无法显著提升 QPS。


四、工程落地建议

容器层配置

  • 根据流量与延迟要求,合理设置 max-connectionsaccept-count,避免连接阶段成为瓶颈;
  • 配置合适的 server.tomcat.connection-timeout
  • 保证 threads.maxmin-spare-threads 足够,让请求接入与响应写回有可用线程。

业务层实现

  1. 长耗时操作应交由独立线程池,并设置合适的队列容量与拒绝策略,避免后台池成为新瓶颈;
  2. 用好 onTimeout / onError 回调,结合 X-Request-Id、类名与行号打通全链路日志;
  3. 对 DB、RPC 等外部依赖做好容量评估与限流,避免瓶颈转移到依赖层。

五、一句话总结 DeferredResult 的真正价值,在于释放控制器线程、让慢任务并行化,从而提高线程利用率与系统在负载内的处理效率。但它并不突破连接、队列、网络 I/O 等底层约束。要想整体提升吞吐,必须同步优化连接容量、线程池配置以及外部依赖的承载能力。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 的数字化之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档