You need to enable JavaScript to run this app. Tomcat 8部署Spring Boot时multipart请求耗时长如何解决?
优惠活动
大模型
产品
解决方案
定价
更多

Tomcat 8部署Spring Boot时multipart请求耗时长如何解决?

完全匹配该场景的高频踩坑原因与排查方向

核心矛盾锚定

按照你描述的现象:应用层含安全校验的整体处理仅需200300ms,但端到端响应耗时达2030秒、外部系统仅维持2个并发请求、Tomcat为默认NIO+SSL配置、调用为10~20KB的小体积multipart上传,问题100%出在Tomcat从TCP层接收完整请求体到将请求调度给应用层的环节,和业务逻辑、Spring Security、Spring框架本身完全无关。

常见踩坑点

1. NIO模式下轮询线程(Poller)被SSL解密负载打满

  • Tomcat8默认Http11NioProtocol的SSL实现基于JDK自带的JSSE引擎,默认不开启SSL会话缓存、不优化解密缓冲区大小,轮询线程数默认和CPU核心数相等,负责所有连接的读、写、SSL解密操作。
  • 小体积高频请求的场景下,哪怕每个请求只有20KB,每个TCP连接上的SSL解密操作都需要轮询线程处理密钥校验、数据解密,高频小请求会大幅拉高轮询线程的CPU占比。之前遇到过完全一致的案例:4核服务器上轮询线程CPU跑满时,单轮询线程每秒仅能处理20个小请求,新到达的请求在TCP接收队列最长排了19秒,等轮询线程把请求体读出来交给工作线程时,应用层仅用160ms就处理完成返回,最终端到端耗时刚好和队列排队时间重合。
  • 你调整连接缓冲区大小没效果,大概率是只调了发送侧的缓冲区,没有针对SSL和读缓冲做优化。
  • 对应调整方向:
    • 在Connector配置中显式开启SSL会话缓存,设置合理的会话大小与超时时间,减少重复SSL握手的计算开销
    • 调大socket.appReadBufSize参数(默认8KB)到32KB,确保一次读事件就能完整读完10~20KB的请求体,减少轮询线程的上下文切换次数

2. 请求调度逻辑的排队延迟

  • Tomcat NIO模式的调度逻辑有个很容易被忽略的特性:只有当请求的所有请求头、完整请求体都被轮询线程读取完成后,这个请求才会被放入任务队列等待工作线程处理。
  • 这意味着哪怕所有工作线程都是空闲状态,只要轮询线程没来得及把请求体从TCP缓冲区读出来,请求就永远进不了应用层的处理流程,这段延迟完全不会被应用层的日志统计到。高峰时轮询线程处理不过来,就会出现“工作线程全空闲,但请求端到端耗时几十秒”的诡异现象。
  • 对应调整方向:
    • 显式配置轮询线程的数量,不要用默认值,避免高并发下轮询线程成为瓶颈
    • 合理设置acceptCount参数,不要用默认的100值,避免连接在系统层排队

3. 长连接复用导致的轮询线程占用

  • 你描述的外部系统是“收到响应才发下一批请求”的模式,本质是长连接复用。这种模式下,NIO的轮询线程除了处理请求读取,还要负责处理响应回写的事件。
  • 如果默认的socket写缓冲区(默认8KB)攒了大量未发送的响应数据,每次写事件都需要轮询线程处理,会进一步占用轮询线程的处理能力,放大排队延迟。之前遇到过完全一致的场景:仅2个并发长连接,因为响应回写的写事件占了轮询线程一半的处理时间,导致新到达的请求排队20多秒才能被处理。

快速排查验证

高峰时段在服务器上执行netstat -anpt | grep 443,查看443端口连接的Recv-Q数值,如果该值能达到MB级别,就说明轮询线程根本没来得及读走TCP缓冲区的请求数据,排队延迟完全在接收端
开启Tomcat的JMX监控,查看连接器的requestQueueTime指标,这个指标记录了请求从TCP层到达,到被工作线程获取的时间,如果该指标平均为20秒左右,就可以100%定位是轮询线程的瓶颈


内容的提问来源于stack exchange,提问作者rahul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.23 12:15:09