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
相关产品推荐
相关产品推荐

