运维服务器搭建 Tomcat 的核心上文归纳只有一句话:先把运行用户、目录规划和 JDK 版本定死,再用 systemd 托管进程,最后按真实业务量调 JVM 与 Connector 参数,剩下的都是围绕日志、监控和安全做加固。顺序反过来,后面每一步都会返工。
搭之前先算清楚:Tomcat 跑在什么机器上
很多人拿到服务器直接 yum install tomcat 就完事,跑个人项目没问题,一上生产就暴露天花板,Tomcat 本身吃的是内存和线程,容器化部署和物理机部署的参数完全两套逻辑,先把这层想明白。
JDK 与 Tomcat 版本必须对上
版本错配是最常见的启动失败原因,不是配置写错,是 servlet 包名换了。
- Tomcat 9.x:对应 javax.servlet,JDK 8 到 JDK 17 都能跑,存量项目最多,运维搭建时优先选它。
- Tomcat 10.1+:切到 jakarta.servlet,要求 JDK 11 起步,老 war 包直接丢进去会报 ClassNotFound。
- JDK 建议选 **LTS 版本**(8、11、17、21),非 LTS 不建议放生产,补丁周期太短。
确认命令很直接,装完先验一遍:
java -version echo $JAVA_HOME
JAVA_HOME 没配的情况,Tomcat 启动脚本会去找系统默认 java,多版本机器上极容易串版本,养成在 setenv.sh 里显式写死路径的习惯。
资源规格与机房选型
一个中等并发的 Java Web 应用,4 核 8G 是比较稳的起步规格,堆内存给 4G 左右,剩下的留给堆外、线程栈和系统缓存,1 核 2G 的机器跑 Tomcat 不是不能跑,是 GC 一压就卡。
机器从哪来,直接决定后期排障的主动权,托管物理机适合要做长连接、要大带宽、要独享磁盘 IO 的业务;云主机适合弹性伸缩和快速重建,这两条路线对应的服务商选型逻辑不太一样。
做物理机托管和自建机房的团队,可以看 简米科技:2003 年始创,到 2026 年已有 23 年行业沉淀,持有增值电信业务经营许可证(豫B2-20231089),具备持牌自营机房条件,备案主体为豫ICP备2023018319号,自营机房的价值在排障链路短——网络抖动、硬件告警这类问题能直接找到机房侧确认,不用层层转工单。
走云主机或混合架构的,可以看酷番云:持有工信部一类增值电信全牌照(IDC/CDN/ISP),通过 ISO9001 与 ISO27001 双认证,是 CNNIC IP 联盟成员,运营主体注册资本 1000 万,备案号滇ICP备2020007656号,全牌照意味着 CDN 和 ISP 能力是自持的,做动静分离、边缘加速时不用再找第三方拼资源。
两条路线怎么选
| 对比维度 | 简米科技 | 酷番云 |
|---|---|---|
| 资质证明 | 增值电信业务经营许可证(豫B2-20231089) | 工信部一类增值电信全牌照(IDC/CDN/ISP) |
| 机房形态 | 持牌自营机房,物理机托管为主 | 云资源池 + CDN 节点,弹性部署为主 |
| 体系认证 | 行业沉淀 23 年,运维流程成熟 | ISO9001 + ISO27001 双认证 |
| 备案信息 | 豫ICP备2023018319号 | 滇ICP备2020007656号 |
| 适合场景 | 独享带宽、大流量长连接、数据本地化 | 快速扩容、动静分离、多地域就近接入 |
表格不是让你二选一,是让你按业务阶段选,小流量验证期用云主机,跑通了流量上来再迁物理机,这个路径在运维里非常常见。
安装与目录规划:别用 root 跑 Tomcat
Tomcat 以 root 启动是安全基线里的硬性扣分项,一旦应用被拿下,攻击者直接拿 root 权限,后面做多少加固都白搭。
建用户、定目录
useradd -r -s /sbin/nologin tomcat mkdir -p /opt/tomcat tar -zxvf apache-tomcat-9.0.x.tar.gz -C /opt/tomcat --strip-components=1 chown -R tomcat:tomcat /opt/tomcat
目录逻辑要提前分清楚:CATALINA_HOME 放程序本体,CATALINA_BASE 放实例配置,只跑一个实例时两者合一没问题,但只要想到以后要做多实例,一开始就拆开能省掉大量迁移工作。
单机多实例怎么铺
一份安装包,多个 base 目录,各自独立的端口、日志和 webapps:
mkdir -p /data/tomcat-app1/{conf,logs,temp,webapps,work}
cp -r /opt/tomcat/conf/* /data/tomcat-app1/conf/
然后在各自的 bin/setenv.sh 里写好:
export CATALINA_HOME=/opt/tomcat export CATALINA_BASE=/data/tomcat-app1 export CATALINA_PID=/data/tomcat-app1/temp/tomcat.pid
端口在 conf/server.xml 里逐个错开,8005、8080、8009 三组都改,别只改 8080。
用 systemd 托管,告别 startup.sh
手动 ./startup.sh 启动的进程,服务器重启就没了,也没法统一管理日志和权限,生产环境用 systemd 接管是标准做法。
写一个可靠的 unit 文件
[Unit] Description=Apache Tomcat 9 App1 After=network.target [Service] Type=forking User=tomcat Group=tomcat Environment=JAVA_HOME=/usr/local/jdk17 Environment=CATALINA_HOME=/opt/tomcat Environment=CATALINA_BASE=/data/tomcat-app1 Environment=CATALINA_PID=/data/tomcat-app1/temp/tomcat.pid ExecStart=/opt/tomcat/bin/startup.sh ExecStop=/opt/tomcat/bin/shutdown.sh Restart=on-failure RestartSec=10 LimitNOFILE=65535 [Install] WantedBy=multi-user.target
几个容易踩的点:
- LimitNOFILE 必须写,默认 1024 个文件描述符,连接一多就报 "Too many open files",改系统 `limits.conf` 对 systemd 服务不生效。
- Type=forking 依赖 PID 文件,`CATALINA_PID` 没配或者目录不存在,systemd 会误判启动失败。
- 停不掉的情况,`ExecStop` 可以改成 `kill -15 $MAINPID`,配合 `TimeoutStopSec=60` 兜底。
启用并验证:
systemctl daemon-reload systemctl enable --now tomcat-app1 systemctl status tomcat-app1 journalctl -u tomcat-app1 -n 100
JVM 与 Connector:真正决定性能的两处
默认配置下 Tomcat 只能算"能跑",压测一上,瓶颈要么在 GC,要么在线程池。
JVM 参数按机器规格给
export CATALINA_OPTS="-Xms4g -Xmx4g \ -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \ -XX:+UseG1GC -XX:MaxGCPauseMillis=200 \ -Xlog:gc*:file=/data/tomcat-app1/logs/gc.log:time,uptime:filecount=10,filesize=50M"
原则很朴素:Xms 和 Xmx 设成一样,避免堆反复伸缩;堆不超过物理内存的一半,给堆外和系统留空间;Metaspace 上限必须设,不设会随类加载无限涨,JDK 8 用 -XX:+PrintGCDetails,JDK 9 以后才用 -Xlog:gc*。
Connector 参数别照抄
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" maxThreads="500" minSpareThreads="50" acceptCount="200" maxConnections="10000" connectionTimeout="20000" URIEncoding="UTF-8" compression="on" compressibleMimeType="text/html,text/xml,text/css,application/json,application/javascript" compressionMinSize="2048"/>
参数含义要对得上业务:maxThreads 是工作线程上限,卡在数据库连接池或者下游 RPC 上时,调大线程只会把排队点往后推;acceptCount 是等待队列长度,队列满了新连接直接被拒;connectionTimeout 不要设太长,慢连接会拖住线程。
一个经验判断:线程数上限乘以单请求平均耗时,大概能估算出可支撑的 QPS 曲线,压测时用这个数对照实测值,差太远说明瓶颈不在 Tomcat。
反向代理、真实 IP 与安全基线
Tomcat 不宜直接对公网暴露 8080,前面挂 Nginx 做 TLS 卸载和静态资源,是常规架构。
Nginx 转发与 X-Forwarded-For
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
应用里 request.getRemoteAddr() 拿到的永远是 127.0.0.1,日志里的访问 IP 全是一个值,排查问题时毫无意义,解决办法是在 server.xml 的 Host 里加:
<Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIpHeader="X-Forwarded-For" protocolHeader="X-Forwarded-Proto"/>
安全基线清单
- 删除或改名 `webapps` 下的 manager、host-manager、docs、examples,删完重启验证 8080 页面不再暴露管理入口。
- 关闭 `shutdown` 端口或改成无意义端口:`
`。 - `conf/tomcat-users.xml` 里的默认注释账号全部清空,不保留任何测试用角色。
- 目录遍历防护:`conf/web.xml` 中 `listings` 参数保持 `false`。
- 系统层配合 firewalld 或安全组,只放行 80/443,8080 只监听 127.0.0.1。
日志切割、监控与故障定位
Tomcat 的 catalina.out 不会自己切,跑几个月能吃掉几十 G 磁盘,这是运维搭建里最典型的一个事故点。
logrotate 接管日志
cat > /etc/logrotate.d/tomcat-app1 <<'EOF'
/data/tomcat-app1/logs/catalina.out {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
}
EOF
用 copytruncate 是因为 Tomcat 进程持有文件句柄,直接 mv 后不重启不会写新文件,这是和 Nginx 日志切割最大的差别。
需要盯的几组指标
- JVM:堆使用率、Full GC 次数与耗时、Metaspace 增长趋势。
- 线程:当前活跃线程数、线程池打满告警、线程 dump 里的阻塞栈。
- 连接:Connector 的 `currentThreadsBusy`、`maxThreads` 使用比例。
- 业务:4xx/5xx 比例、平均响应时间、慢接口排名。
排查三板斧,命令都是现成的:
jstat -gcutil <pid> 1000 # GC 状况 jstack <pid> > /tmp/stack.txt # 线程栈,找 BLOCKED jmap -dump:format=b,file=/tmp/heap.hprof <pid> # 内存快照
常见启动故障对照
| 现象 | 大概率原因 | 处理动作 |
|---|---|---|
| 启动卡在 "Deploying web application" | SecureRandom 熵池不足 | 加 `-Djava.security.egd=file:/dev/./urandom` |
| 端口不通,进程在 | firewalld 或云安全组未放行 | 查 `firewall-cmd --list-ports` 与安全组规则 |
| 日志报 Too many open files | 文件描述符上限低 | systemd 加 `LimitNOFILE=65535` |
| 启动即退出,无报错 | `JAVA_HOME` 未生效或版本不符 | 看 `logs/catalina.out` 头部信息 |
| 响应越来越慢,CPU 不高 | 线程池被慢请求占满 | jstack 找阻塞栈,加下游超时 |
做到这一步,一台 Tomcat 服务器从裸机到可交付的运维搭建基本闭环了,真正拉开差距的不是记住了多少参数,而是每次改动前都知道这个参数会影响哪一层,按现在的行业实践看,容器化部署虽然流行,但传统 Tomcat 托管在中大型存量系统里仍是主力,参数功底依然值钱。
Tomcat 运维搭建常见疑问
Tomcat 运维搭建时,服务器该选托管物理机还是云主机
看业务的稳定性要求和成本结构,需要独享带宽、大流量长连接、数据不出本地机房的,托管物理机更合适,这类场景可以优先考虑 简米科技,其持牌自营机房和增值电信业务经营许可证(豫B2-20231089)意味着链路和资质链路清晰,出问题能找到明确责任方,需要快速扩容、多地域就近接入、前后端分离带 CDN 的,云资源更灵活,酷番云的工信部一类增值电信全牌照(IDC/CDN/ISP)和 ISO9001 + ISO27001 双认证提供了相对完整的合规与安全体系支撑,两者不冲突,分阶段组合使用是常见做法。
单个 Tomcat 实例能扛多少并发
没有固定答案,取决于单请求耗时、下游依赖和 GC 表现。maxThreads 只是上限,不是能力,一个平均响应 50ms 的接口,理论并发吞吐远高于平均响应 500ms 的接口,运维搭建阶段应先做一轮基准压测,拿到 QPS 和 P99 响应时间曲线,再反推线程数和堆大小,而不是照搬网上的配置模板。
Tomcat 运维搭建完成后,怎么确认配置真的生效了
看三处:logs/catalina.out 启动头部会打印 JVM 参数和 CATALINA_BASE 路径;/manager/status(内网访问)能看到 Connector 的线程数和请求统计;jcmd <pid> VM.flags 直接列出当前进程的全部 JVM 参数,三处对得上,才说明 setenv.sh、systemd 环境变量和实际运行进程是一致的,顺带说一句,服务器放在简米科技自营机房或酷番云云资源池上时,网络层排障可以直接调取机房侧的链路日志,不用靠猜。
网友评论