調優兩大維度
- 外部環境:JVM 參數、作業系統、前端 Web Server 整合、負載均衡
- Tomcat 自身:連接器執行緒、JSP 編譯、容量規劃、會話管理
一、JVM 記憶體與 GC 調優
堆記憶體核心參數
# catalina.sh / catalina.bat 開頭加入
JAVA_OPTS="-Xms256m -Xmx512m -XX:PermSize=64m -XX:MaxPermSize=128m"
# JDK 8+ 使用 Metaspace
JAVA_OPTS="-Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
-Xms:初始堆大小,建議與 -Xmx 設定相同,避免動態擴容抖動
-Xmx:最大堆,建議 ≤ 物理記憶體 80%,預留 OS、Metaspace、Code Cache、Thread Stack 空間
GC 策略選擇
| 收集器組合 | 適用場景 | JDK 版本 |
| ParNew + CMS | 低延遲、響應敏感型應用 | JDK 7/8 |
| Parallel Scavenge + Parallel Old | 高吞吐、批次處理、科學計算 | JDK 7/8 |
| G1GC | 大堆(>4GB)、可預測停頓、混合負載 | JDK 7u4+ / 8/9+ |
| ZGC / Shenandoah | 超大堆(>16GB)、亞毫秒級停頓 | JDK 11+ / 12+ |
# CMS 範例
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly
# G1 範例
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45
GC 日誌與分析
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/tomcat/gc.log
-XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M
工具:GCViewer、GCEasy.io、Arthas、JProfiler
二、作業系統層面優化
- 檔案描述符:
ulimit -n 65535(/etc/security/limits.conf)
- TCP 參數:
net.core.somaxconn=65535、net.ipv4.tcp_tw_reuse=1、net.ipv4.tcp_fin_timeout=15
- 關閉 THP:
echo never > /sys/kernel/mm/transparent_hugepage/enabled(減少 JVM 延遲尖峰)
- NTP 時間同步:分布式系統基礎
- Swappiness:
vm.swappiness=1(避免 JVM 堆被 swap 出)
三、Tomcat 連接器調優
關閉 DNS 查詢(立竿見影)
connectionTimeout="20000"
redirectPort="8443" />
執行緒池尺寸規劃
minSpareThreads="100"
maxSpareThreads="200"
acceptCount="1000"
connectionTimeout="20000"
enableLookups="false" />
- maxThreads:CPU 核心數 × (1 + 平均等待時間/平均計算時間),典型 200-800
- acceptCount:擋住突發流量,超過拋棄連接(回傳 503)
- 監控
ThreadPool MBean:currentThreadCount、currentThreadsBusy
NIO / APR 原生庫提升吞吐
四、前端整合:Apache / Nginx 反向代理
- 靜態資源由 Nginx/Apache 直接服務,Tomcat 僅處理動態請求(JSP、Servlet、API)
- 連接複用:上游保持長連接(
proxy_http_version 1.1; proxy_set_header Connection "";)
- 負載均衡演算法:Least Connections、IP Hash(Session Sticky)、Weighted Round Robin
Nginx 範例
upstream tomcat_cluster {
least_conn;
server 10.0.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 weight=2 max_fails=3 fail_timeout=30s;
keepalive 100;
}
server {
location /static/ { root /var/www; expires 30d; }
location / {
proxy_pass https://tomcat_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
五、Session 複寫與集群決策
| 方案 | 優點 | 缺點 | 建議場景 |
| Tomcat DeltaManager(全量廣播) | 配置簡單 | 節點>3 時網路風暴、效能崩潰 | 極小規模(2-3 節點) |
| BackupManager(主備) | 流量較小 | 單點備份、擴展性差 | 雙節點熱備 |
| Redis / Memcached Session 共享 | 水平擴展、解耦、高可用 | 引入外部依賴、序列化開銷 | 生產環境標準選擇 |
| Sticky Session(IP Hash) | 無需同步、效能最好 | 節點故障導致 Session 丟失 | 可容忍短暫登出的業務 |
實戰教訓:某專案引入 DeltaManager 導致 CPU 飆升、GC 頻繁,最終改為 Nginx IP Hash + Redis Session 徹底解決。
六、容量規劃與壓力測試
- 經驗式:參考同類業務 QPS、單請求記憶體、CPU 估算
- 科學式:基準測試 + 壓力模型 + 目標 SLA(P99 < 200ms、錯誤率 < 0.1%)
- 工具:
JMeter、Gatling、wrk、Locust
七、綜合實戰案例:Apache + Tomcat + MySQL 架構優化
背景
- 目標:3 萬在線、100 並發
- 初始:Apache 2.0.54 + Tomcat 5.0.30 + MySQL 4.1.14 + JDK 1.5
- 硬體:3 台 2.4GHz/1GB RAM,100M 內網
踩坑與解決
| 問題 | 根因 | 解決 |
| Tomcat Cluster 導致極慢 | Session 複寫風暴、不支援 Application 複寫 | 移除 Cluster,改 Sticky Session |
| Windows 2000 專業版並發限制 15 | 作業系統連接數上限 | 全環境升級 Windows 2003 Server |
| DBCP 連接池初始化只有 1 條、擴容慢 | JDK 1.5 JDBC 驅動相容性問題 | 降級 JDK 1.4、改用 C3P0 |
| DBCP 連接斷開後不重建 | 已知 Bug | 遷移至 C3P0(Hibernate 3 預設) |
最終調優配置摘要
# Tomcat (server.xml)
# JVM
-Xms512m -Xmx768m (視實例權重)
# Apache (httpd.conf + workers.properties)
ThreadsPerChild 1900
MaxRequestsPerChild 10000
worker.tomcat1.lbfactor=900
worker.tomcat2.lbfactor=2000
# MySQL (my.cnf)
innodb_buffer_pool_size=1G
max_connections=1000
query_cache_size=64M
# C3P0 (Spring)
hibernate.c3p0.max_size=200
hibernate.c3p0.min_size=5
hibernate.c3p0.timeout=12000
hibernate.c3p0.max_statements=50
八、避坑指南速查
- ❌ 生產環境直接用 JDK 預設 GC/堆設定 → ✅ 依負載測試調整
- ❌ Tomcat 直暴露 8080 給用戶 → ✅ Nginx 反代、隱藏內部端口
- ❌ 盲目開啟 Session 複寫 → ✅ 評估 Redis 共享或 Sticky Session
- ❌ 忽略 GC 日誌 → ✅ 持久化 GC 日誌、定期分析
- ❌ 壓測腳本過於複雜 → ✅ 從單一介面漸進式驗證(登入 → 查詢 → 寫入 → 混合)