一个Java War包在Tomcat集群中的完整部署实战
目录导读
- War包的本质与结构 – 为什么你的Spring Boot应用还需要War包?
- 部署前环境准备 – JDK版本、Tomcat参数、防火墙规则
- 五步核心部署流程 – 从上传War到平滑运行(附命令)
- 常见故障处理矩阵 – 内存溢出、端口占用、类冲突的急救方案
- 生产环境优化策略 – 开启APR连接器、JVM调优、监控指标
- QA:部署中最容易踩的6个坑 – 实战问答总结
War包的本质与结构
Java Web应用标准打包格式是WAR(Web Application Archive),很多人在开发时习惯用spring-boot-maven-plugin打成可执行Jar,但若需要部署到外部Tomcat、WebLogic或进行灰度分流时,传统War包反而更灵活。

War包核心结构:
myapp.war
├── META-INF/ (清单文件、上下文配置)
├── WEB-INF/ (核心区,外部不可直接访问)
│ ├── classes/ (编译后的.class与资源文件)
│ ├── lib/ (项目依赖的第三方Jar)
│ └── web.xml (Servlet映射、Filter/Listener注册)
└── static/ 或 asset/ (静态资源,如CSS/JS/图片)
关键区别: Spring Boot默认内嵌Tomcat,打成WAR后必须标记provided依赖,并排除内嵌容器,否则会引发ClassNotFoundException。
部署前环境准备(决定成败的隐性因素)
1 JDK版本对齐
Tomcat 9.x/10.x必须匹配JDK 8+,检查命令:
java -version /usr/local/tomcat/bin/version.sh
2 Tomcat参数调优(至少这三项)
编辑conf/server.xml中的<Connector>:
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="500" minSpareThreads="50"
connectionTimeout="20000" redirectPort="8443"
maxPostSize="2097152" disableUploadTimeout="true" />
3 防火墙与反向代理
若使用Nginx,需放行8080端口,并配置:
proxy_pass http://tomcat-cluster;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
五步核心部署流程(真实生产命令)
步骤1:上传War包至指定目录
mkdir -p /opt/tomcat/webapps scp local-app.war root@server:/opt/tomcat/webapps/
步骤2:处理同名冲突(关键!)
ls webapps/ | grep app rm -rf app app.war # 先停Tomcat再删
步骤3:配置上下文根路径
修改conf/server.xml的<Host>段,保证访问路径唯一:
<Context path="/myapp" docBase="myapp.war" reloadable="true" />
若想以根路径访问,则path=""。
步骤4:启动并监控日志
/opt/tomcat/bin/startup.sh && tail -f /opt/tomcat/logs/catalina.out
启动成功的标志:
INFO: Deployment of web application archive [myapp.war] has finished
若出现Oracle.jdbc.driver.OracleDriver找不到,检查WEB-INF/lib是否存在驱动。
步骤5:验证多节点(可选)
在两台Tomcat上重复以上流程,用Nginx做负载均衡:
upstream backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
常见故障处理矩阵(重点记忆)
| 现象 | 根本原因 | 应急命令/修改 |
|---|---|---|
OutOfMemoryError: Metaspace |
类加载过多 | set JAVA_OPTS=-XX:MaxMetaspaceSize=256m |
| Tomcat无法关闭 | 线程泄漏 | kill -9 $(cat /opt/tomcat/temp/tomcat.pid) |
Caused by: java.lang.IllegalArgumentException |
pom中打包漏了JSP | mvn war:exploded 强制重新生成 |
| 访问404 | 上下文路径错误 | 在conf/Catalina/localhost下添加同名XML |
| 静态资源无法加载 | Spring Security拦截 | 在Security配置中放行/static/** |
生产环境优化策略
1 启用APR连接器(提升30%并发)
yum install tomcat-native -y # server.xml改为 protocol="org.apache.coyote.http11.Http11AprProtocol"
2 JVM堆内存分级设置
export CATALINA_OPTS="-Xms2048m -Xmx4096m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError"
3 监控核心指标
# 使用JVisualVM远程连接 jmxremote.port=1099 jmxremote.ssl=false jmxremote.authenticate=false
QA:部署中最容易踩的6个坑
Q1:明明删掉了旧War,为何还会打包多次?
A:因为webapps/目录下的同名文件夹残留导致热加载多次,必须执行双重删除rm -rf myapp myapp.war。
Q2:开发时正常,部署后报ClassNotFoundException?
A:开发IDE会自动加载target/目录,但Tomcat不会,检查WEB-INF/classes是否存在编译产物,或用mvn clean package重新构建。
Q3:数据库连接失败,但本机测试OK?
A:War包内application.properties可能写死了localhost,改为jdbc:mysql://内网IP:3306/并开启MySQL远程访问。
Q4:无限重启循环(OutOfMemory)如何排查?
A:查看logs/localhost.log中是否有java.lang.OutOfMemoryError,立刻加上-XX:+HeapDumpOnOutOfMemoryError,用MAT分析dump文件。
Q5:如何实现War包版本回滚?
A:在webapps下保留myapp.war和myapp.war.bak. 回滚时执行:
rm -rf myapp && mv myapp.war.bak myapp.war && touch myapp.war
Q6:高并发下连接池爆满怎么办?
A:修改Tomcat的maxConnections和数据库连接池(HikariCP):
spring.datasource.hikari.maximum-pool-size: 50
从War到DevOps的下一步
当War包稳定运行两周后,建议引入Jenkins + Ansible自动部署,并将部署日志输出到ELK,但基础仍然是这一套手动流程——只有理解了War的结构、Tomcat的类加载机制和上下文路径逻辑,才能在后序容器化(Docker+K8s)中游刃有余。
最后提醒一句: 每次修改server.xml前先备份,并用configtest工具验证:
/opt/tomcat/bin/configtest.sh
返回SUCCESS后再重启,能避免90%的意外宕机。