Tomcat 连接器详解
概述
连接器(Connector)是 Tomcat 的"大门":它决定请求如何被接收、解析、分发。从 BIO 到 NIO2 再到 APR,连接器的演进史就是 Java 高并发 IO 的发展史。本文聚焦四种 IO 模型、NIO 的线程模型、线程池参数调优,以及 HTTPS 配置的完整链路。
一、四种 IO 模型对比
| 模型 | 全称 | 线程模型 | 优势 | 劣势 | 现状 |
|---|---|---|---|---|---|
| BIO | Blocking IO | 一线程一连接 | 简单直观 | 高并发线程爆炸 | 8.5 移除 |
| NIO | Non-blocking IO | 少量线程 + Poller | 高并发稳定 | 编程复杂 | 默认 |
| NIO2 | Asynchronous IO | 回调驱动 | 异步无阻塞 | 复杂度高 | 可选 |
| APR | Apache Portable Runtime | 操作系统原生 | 性能上限最高 | 需编译 native | 可选 |
选择建议:默认 NIO 已经满足绝大多数场景;追求极致吞吐且愿意引入原生依赖时用 APR;NIO2 在长连接高并发下也有优势,但复杂度和收益需要权衡。
二、NIO 连接器线程模型
NIO 连接器的核心是"少量线程管理大量连接",由三个角色协作:
TCP 连接队列(acceptCount)
│
┌─────────▼──────────┐
│ Acceptor 线程 │ 1~2 个:不断 accept 新连接
└─────────┬──────────┘
│ 新连接注册到 Poller
┌─────────▼──────────┐
│ Poller 线程 │ 1~2 个:NIO Selector 轮询
│ (Selector 多路复用) │ 检测读写事件
└─────────┬──────────┘
│ 就绪事件交给工作线程
┌─────────▼──────────┐
│ 工作线程池 │ Executor(maxThreads)
│ (执行 Servlet) │
└────────────────────┘2.1 三个角色的职责
| 角色 | 数量 | 职责 |
|---|---|---|
| Acceptor | 1 | 循环 serverSocket.accept(),接收新 TCP 连接 |
| Poller | 1 | 持有 Selector,把连接注册到感兴趣的事件,检测可读/可写 |
| Executor 线程池 | 可配置 | 真正执行请求处理(解析报文、调用容器) |
2.2 事件流转
1. Acceptor 接到新连接 → 包装成 NioSocketWrapper → 注册到 Poller
2. Poller 的 Selector 检测到读事件 → 从线程池取一个工作线程
3. 工作线程读取请求头 → 交给 Http11Processor 解析 → 进入容器处理
4. 处理完成后写响应 → 连接归还 Poller 继续监听(keep-alive)工作线程不长期占用连接——大部分时间线程在执行业务逻辑,空闲连接只消耗 Poller 上的注册项,这就是 NIO 能支撑上万连接的原因。
三、线程池配置详解
3.1 默认执行器
未显式配置 Executor 时,Tomcat 使用连接器内置线程池,关键参数:
| 参数 | 默认值 | 含义 |
|---|---|---|
maxThreads | 200 | 最大工作线程数 |
minSpareThreads | 10 | 最小空闲线程数 |
acceptCount | 100 | 连接积压队列长度(backlog) |
maxConnections | 8192 | 最大并发连接数(NIO 下是"连接队列"上限) |
connectionTimeout | 20000 | 等待读取请求的超时(毫秒) |
keepAliveTimeout | 5000 | keep-alive 连接超时(毫秒) |
3.2 显式线程池
xml
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="400" minSpareThreads="50" maxQueueSize="200" />
<Connector port="8080" protocol="HTTP/1.1"
executor="tomcatThreadPool" />| 参数 | 默认值 | 说明 |
|---|---|---|
namePrefix | — | 线程名前缀,便于 jstack 定位 |
maxQueueSize | Integer.MAX_VALUE | 等待队列长度,防止任务无限堆积 |
threadPriority | 5 | 线程优先级 |
daemon | true | 是否守护线程 |
3.3 线程池调优思路
线程数不是越大越好,公式化的经验:
最佳线程数 ≈ CPU 核数 × (1 + 平均等待时间 / 平均计算时间)- CPU 密集(计算为主):线程数 ≈ CPU 核数,过多反而上下文切换损耗
- IO 密集(等待网络/磁盘):线程数可数倍于核数
- 关键指标:观察
maxThreads是否被打满、请求队列是否堆积、CPU 是否空闲
配合监控判断:队列持续堆积而 CPU 空闲 → IO 等待多,可加线程;CPU 跑满且线程打满 → 加机器或削并发。
四、连接器参数与连接生命周期
4.1 acceptCount、maxConnections、maxThreads 的关系
客户端连接
│
▼
acceptCount 积压队列(TCP backlog)──满──▶ 拒绝连接(客户端看到超时/拒连)
│
▼
maxConnections 连接数上限(NIO 的连接队列)──满──▶ 新连接进入积压/等待
│
▼
maxThreads 线程池上限 ──满──▶ 任务进入等待队列三者逐级兜底:acceptCount 控制内核积压、maxConnections 控制连接总量、maxThreads 控制并发处理能力。
4.2 keep-alive 与连接复用
- 同一客户端复用同一 TCP 连接处理多个请求,省去握手开销
keepAliveTimeout决定空闲连接何时关闭- 长连接场景下
maxConnections更易打满,但线程占用不增加
五、HTTPS 配置
5.1 使用 JKS/PKCS12 密钥库
xml
<Connector port="8443" protocol="HTTP/1.1"
SSLEnabled="true" maxThreads="200"
scheme="https" secure="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/keystore.p12"
certificateKeystorePassword="changeit"
type="RSA" />
</SSLHostConfig>
</Connector>5.2 使用 PEM 证书(NIO/APR 均支持)
xml
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" >
<SSLHostConfig>
<Certificate certificateFile="conf/cert.pem"
certificateKeyFile="conf/key.pem"
type="RSA" />
</SSLHostConfig>
</Connector>5.3 配置要点
| 项 | 说明 |
|---|---|
scheme="https" secure="true" | 让容器感知安全连接,避免生成错误的重定向地址 |
redirectPort | HTTP 端口收到安全请求时重定向到该端口 |
| TLS 版本 | <SSLHostConfig protocols="TLSv1.2,TLSv1.3"> 显式指定 |
| 密码套件 | 按安全基线裁剪,禁用弱套件 |
| 性能 | 启用会话缓存、禁用 TLS 1.0/1.1 |
5.4 全站 HTTPS 的两种做法
- Tomcat 直接终止 TLS:8443 端口 SSL,HTTP 8080 重定向到 8443
- Nginx 终止 TLS 后转发:Nginx 处理证书,HTTP 明文转发给 Tomcat(
X-Forwarded-Proto头标记原始协议)
方案 2 更常见——证书管理集中、卸载 SSL 开销、利于水平扩展。Tomcat 侧需配置 RemoteIpValve 信任代理头。
六、AJP 连接器
xml
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"
secretRequired="true" secret="your-secret" />AJP 用于 Apache httpd 或 Nginx(需第三方模块)与 Tomcat 的二进制通信。安全提示:未使用的 AJP 端口应直接删除该 Connector;使用则必须配置 secret(CVE-2020-1938 后强制要求)。
七、生产性能调优清单
| 项 | 建议 |
|---|---|
| IO 模型 | 保持默认 NIO,特殊场景评估 APR |
| maxThreads | 按 CPU/IO 密集程度与压测结果调整 |
| acceptCount | 结合 TCP backlog 调整,防连接风暴 |
| connectionTimeout | 2~5 秒,太短误杀慢客户端,太长浪费连接 |
| keepAliveTimeout | 配合前端代理,通常 5~15 秒 |
| maxKeepAliveRequests | 建议设限(如 100),防止连接被无限复用 |
| 压缩 | compression="on",大响应开启 gzip |
| 域名解析 | 关闭 enableLookups="false"(默认),避免 DNS 反查拖慢请求 |
压测验证:用 JMeter/压测工具观察不同参数下的 TPS、P99 延迟与线程使用率,以数据为准,不要照搬公式。
八、常见故障排查
| 现象 | 原因 | 定位 |
|---|---|---|
大量 Connection reset | 连接数超限或 backlog 溢出 | 检查 maxConnections/acceptCount |
| 请求堆积响应变慢 | maxThreads 打满 + 队列堆积 | jstack 看线程状态,监控队列长度 |
| 偶发 503 | maxQueueSize 满,拒绝新任务 | 观察 Executor 队列指标 |
| HTTPS 握手失败 | 证书链/密码套件不匹配 | 用 openssl s_client 验证 |
| keep-alive 连接被频繁断开 | keepAliveTimeout 过短 | 结合代理层统一调整超时 |
参考链接: