bigywater.tech
Spring · 54

Tomcat 线程池满了会怎样:max-threads、accept-count 与 max-connections

线程池满了请求是报错还是排队?accept-count 和 max-connections 到底管什么?为什么慢接口会把快接口也拖慢?排队的请求会不会超时?这篇把 Tomcat 线程数、连接数和等待队列调小,用并发请求在本机回环上逐个测量(平台是 macOS,Linux 的表现没有验证)。

3770 ms
threads.max=2 时,一个只需几毫秒的 /fast 请求被八个慢请求拖到的耗时
9 / 12
accept-count=5、max-connections=4 的第 2 轮,12 个同时发起的请求里成功的个数
4070 ms
connection-timeout=1500ms 时,最久排队的请求仍然成功的总耗时

默认值

实测 · 本机执行输出
  threads.max=200 threads.min-spare=10 max-connections=8192 accept-count=100 connection-timeout=null

200 个线程、8192 个连接、100 的等待队列。

线程满了

实测 · 本机执行输出
  8 个并发请求:成功 8,失败 0
      成功请求的总耗时(ms,从小到大):[1054, 1054, 2061, 2062, 3065, 3067, 4071, 4072]
      成功请求建立 TCP 连接的耗时(ms,从小到大):[0, 0, 0, 0, 0, 1, 1, 1]
      压力期间另发一个 /fast:ok,总耗时 3770 ms,状态 200;exec 线程数=2

排队不报错,快请求也会被慢请求拖慢。

连接超过上限

实测 · 本机执行输出
  max-connections=4、accept-count=2:
    第 1 轮:12 个同时发起的请求 → {'成功': 2, '失败:ConnectionResetError@连接阶段': 9, '失败:BrokenPipeError@发请求阶段': 1};成功的里最慢的总耗时 1051 ms;失败请求耗时最长 1 ms
    第 2 轮:12 个同时发起的请求 → {'成功': 6, '失败:ConnectionResetError@连接阶段': 5, '失败:BrokenPipeError@发请求阶段': 1};成功的里最慢的总耗时 3020 ms;失败请求耗时最长 1 ms
    第 3 轮:12 个同时发起的请求 → {'成功': 5, '失败:ConnectionResetError@等响应阶段': 1, '失败:ConnectionResetError@连接阶段': 6};成功的里最慢的总耗时 3020 ms;失败请求耗时最长 1 ms
  max-connections=4、accept-count=5:
    第 1 轮:12 个同时发起的请求 → {'成功': 5, '失败:ConnectionResetError@连接阶段': 5, '失败:BrokenPipeError@发请求阶段': 2};成功的里最慢的总耗时 3071 ms;失败请求耗时最长 1 ms
    第 2 轮:12 个同时发起的请求 → {'成功': 9, '失败:ConnectionResetError@连接阶段': 2, '失败:BrokenPipeError@发请求阶段': 1};成功的里最慢的总耗时 5020 ms;失败请求耗时最长 0 ms
    第 3 轮:12 个同时发起的请求 → {'成功': 9, '失败:ConnectionResetError@连接阶段': 3};成功的里最慢的总耗时 5025 ms;失败请求耗时最长 0 ms
  max-connections=4、accept-count=8:
    第 1 轮:12 个同时发起的请求 → {'成功': 7, '失败:ConnectionResetError@连接阶段': 5};成功的里最慢的总耗时 4072 ms;失败请求耗时最长 1 ms
    第 2 轮:12 个同时发起的请求 → {'成功': 12};成功的里最慢的总耗时 6026 ms;失败请求耗时最长 0 ms
    第 3 轮:12 个同时发起的请求 → {'成功': 12};成功的里最慢的总耗时 6035 ms;失败请求耗时最长 0 ms
  max-connections=4、accept-count=100:
    第 1 轮:12 个同时发起的请求 → {'成功': 12};成功的里最慢的总耗时 6077 ms;失败请求耗时最长 0 ms
    第 2 轮:12 个同时发起的请求 → {'成功': 12};成功的里最慢的总耗时 6033 ms;失败请求耗时最长 0 ms
    第 3 轮:12 个同时发起的请求 → {'成功': 12};成功的里最慢的总耗时 6027 ms;失败请求耗时最长 0 ms
  max-connections=100、accept-count=2:
    第 1 轮:12 个同时发起的请求 → {'成功': 2, '失败:ConnectionResetError@连接阶段': 6, '失败:ConnectionResetError@等响应阶段': 1, '失败:OSError@连接阶段': 1, '失败:BrokenPipeError@发请求阶段': 2};成功的里最慢的总耗时 1066 ms;失败请求耗时最长 1 ms
    第 2 轮:12 个同时发起的请求 → {'成功': 12};成功的里最慢的总耗时 6036 ms;失败请求耗时最长 0 ms
    第 3 轮:12 个同时发起的请求 → {'成功': 12};成功的里最慢的总耗时 6032 ms;失败请求耗时最长 0 ms

accept-count 很小时,多余的连接被立即重置,每轮成功数波动。

排队不超时

实测 · 本机执行输出
  8 个并发请求:成功 8,失败 0
      成功请求的总耗时(ms,从小到大):[1052, 1053, 2056, 2059, 3064, 3064, 4070, 4070]
      成功请求建立 TCP 连接的耗时(ms,从小到大):[0, 0, 0, 0, 0, 0, 0, 1]
      压力期间另发一个 /fast:ok,总耗时 3764 ms,状态 200;exec 线程数=2

connection-timeout 踢不掉排队等线程的请求,要在更外层限时。

分步演示

线程池满了
本步的对象

    本文用到的工具与边界

    • 本机 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 解析;数字每次运行会有小幅抖动。用 Spring Boot 4.0.8 的 SpringApplication(spring-boot-starter-webmvc,内嵌 Tomcat 11.0.24 NIO);macOS 上本机回环 127.0.0.1,客户端用 Python 线程同时发起请求,连接与读超时 10 秒;默认值取自应用里注入的 TomcatServerProperties;第三节每个组合连做 3 轮,成功个数不稳定(第一次冲击新启动的进程更容易失败)
    • 没有验证:Linux 上 accept-count 溢出的表现(内核的 SYN 队列与 tcp_abort_on_overflow);max-connections 到达上限后 Tomcat 接受器停止接受连接的内部机制(只测了外部现象);虚拟线程下线程池的变化(见第 60 篇);Undertow、Jetty 的对应参数;keep-alive 空闲连接占用 max-connections 的情况;server.tomcat.threads.max 的动态调整;Actuator 的 tomcat.threads.busy 指标(见第 80 篇)
    京ICP备2026067502号-1京公网安备11010502063487号