ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux部署Tomcat全流程:JDK、systemd与生产优化

Linux部署Tomcat全流程:JDK、systemd与生产优化 1. 部署前的准备与设计思路1.1 先搞清楚Tomcat在Linux上到底扮演什么角色很多新手把Tomcat当成一个“装完就能跑”的Java Web容器但我更愿意把它看作一个Servlet容器 HTTP服务器的组合体。你在Linux上装Tomcat本质上是在搭建一个能接收HTTP请求、把请求转给Java业务代码处理、再把响应返回给客户端的运行环境。它自带HTTP服务器所以不需要额外装Nginx或者Apache就能对外提供服务只是并发能力远不如Nginx这类专业Web服务器生产环境通常会用Nginx做前置反向代理Tomcat只负责跑应用。先说清楚Tomcat的适用场景中小型Java Web应用、内部管理系统、开发测试环境、以及不需要超高并发的生产服务。如果你准备跑高并发业务最好把Tomcat放在Nginx后面或者干脆考虑Spring Boot内置容器直接打包成Fat Jar运行。但无论怎么选理解Tomcat在Linux上的部署方式和运行机制依然是Java后端开发者的基本功面试也爱问。这篇文章适合谁刚接触Linux运维的Java开发、需要自己搭建测试环境的测试工程师、还有准备部署第一个Java Web项目的学生。我会从零开始把环境准备、JDK版本选择、目录规划、systemd托管、常见报错排查全部过一遍尽量把每一步“为什么这么做”讲清楚。1.2 部署方案选型不要一上来就apt install网上很多教程会让你直接apt install tomcat9我劝你谨慎。系统自带的Tomcat版本通常落后官方好几个小版本而且包管理器会把配置文件和目录结构按照发行版习惯重新布局比如Debian系的Tomcat会把CATALINA_HOME拆得很散webapps默认指到/var/lib/tomcat9/webapps配置文件在/etc/tomcat9/日志在/var/log/tomcat9/。这样用起来倒也没问题但你在网上搜到的很多Tomcat优化资料是基于官方标准目录结构CATALINA_HOME/bin、CATALINA_HOME/conf、CATALINA_HOME/webapps写的路径对不上会让你踩坑。所以我推荐二进制发布包部署从Apache官网下载tar.gz压缩包解压到指定目录自己掌控一切。这种做法有三个好处版本完全可控想用哪个版本就用哪个、目录结构符合官方文档标准任何教程都能直接参考、卸载干净删目录即可。代价是你得自己写systemd服务文件或者init脚本但这并不难我会在后面给出完整的配置。部署前先规划几个问题JDK用哪个版本用哪个系统用户跑Tomcat安装目录放哪是否开启AUTO_DEPLOY日志怎么管理这些看似小事实际影响后面的维护体验。我见过太多生产事故源于“部署时图省事用root启动Tomcat”或者“两个应用挤在同一个Tomcat里没做隔离”。先把方案想清楚再动手比啥都强。1.3 JDK版本选择8还是11OpenJDK还是OracleTomcat是Java写的所以第一件事是装JDK。Tomcat 8.5和9.0要求Java 7但实际生产推荐Java 8Tomcat 10.0要求Java 8推荐Java 11Tomcat 10.1彻底转向Jakarta EE 9要求Java 11。这里有个非常容易踩的坑Tomcat 10之后包名从javax.*变成了jakarta.*。如果你用的是Tomcat 9或者更早代码里import javax.servlet.*没问题但用Tomcat 10.0及以上旧代码直接无法运行必须改用jakarta.servlet.*。所以对大多数存量项目老老实实选Tomcat 9 JDK 8或者Tomcat 9 JDK 11别追新给自己找事。JDK本身选OpenJDK就行Oracle JDK在Java 8之后不再免费提供商业授权没必要折腾。Ubuntu/Debian可以直接apt install openjdk-11-jdkCentOS/RHEL用yum install java-11-openjdk-devel云服务器也可以用云厂商的镜像源装完用java -version验证。有个细节容易被忽略编译和运行JDK版本不一致导致的ClassNotFoundException或UnsupportedClassVersionError。本机开发用JDK 17编译服务器只装了JDK 8Class文件版本号对不上启动直接抛异常。所以部署前务必确认应用编译的目标版本服务器JDK版本不低于编译版本但如果低于编译的Target版本会直接报UnsupportedClassVersionError。2. 在Linux上安装JDK并验证环境2.1 安装OpenJDK并配置JAVA_HOME这里以Ubuntu 22.04 LTS、CentOS 7.9两个主流环境为例。Debian/Ubuntu系列用aptRedHat/CentOS系列用yum或dnf底层逻辑都一样先更新软件源再安装。Debian/Ubuntusudo apt update sudo apt install -y openjdk-11-jdk java -versionRedHat/CentOSsudo yum install -y java-11-openjdk-devel java -version验证出来的输出类似这样openjdk version 11.0.21 2023-10-17 OpenJDK Runtime Environment (build 11.0.219-post-Ubuntu-0ubuntu122.04) OpenJDK 64-Bit Server VM (build 11.0.219-post-Ubuntu-0ubuntu122.04)JDK装好后JAVA_HOME环境变量通常不会自动设置。Tomcat启动脚本会优先找JAVA_HOME找不到就找JRE_HOME再找不到就尝试直接用java命令。为了让Tomcat稳定找到JDK建议手动配置# 先找到JDK安装的真实路径 sudo update-alternatives --config java # 或者直接看 readlink -f $(which java)然后编辑/etc/profile或者当前用户~/.bashrcexport JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATH执行source /etc/profile使其生效再用echo $JAVA_HOME验证。你可能会问既然Tomcat找不到JAVA_HOME也能用java命令跑为啥还要配我的经验是配好JAVA_HOME能让你在写systemd服务文件时明确指定JDK路径避免系统有多个JDK版本时Tomcat被意外切换到错误的JDK上。尤其服务器上同时装JDK 8和JDK 17的时候不显式指定路径Tomcat可能用错版本启动时各种诡异报错排查半天才发现是JDK冲突。2.2 创建专用系统用户与目录规划线上环境千万别用root跑Tomcat。Tomcat进程如果有漏洞被利用拿到的是root权限整个服务器都危险。主流做法是创建一个无登录权限的系统用户只给它读写Tomcat目录的权限。sudo useradd -r -s /sbin/nologin -d /opt/tomcat tomcat sudo mkdir -p /opt/tomcat sudo chown -R tomcat:tomcat /opt/tomcat-r表示创建系统用户UID小于1000-s /sbin/nologin禁止该用户登录Shell-d /opt/tomcat指定家目录。这样即使Tomcat被入侵也无法直接拿到Shell权限。目录我习惯这样规划路径用途说明/opt/tomcat安装主目录CATALINA_HOMETomcat二进制文件、配置、Web应用全在这/opt/tomcat/logs日志目录默认就有建议软链接到独立数据盘便于日志清理/data/backup备份目录定期打包webapps和conf出问题能快速回滚生产环境最好把logs目录挂到独立磁盘上避免日志把系统盘撑爆。云服务器的话把数据盘挂载到/data再把logs软链过去sudo mkdir -p /data/tomcat_logs sudo chown -R tomcat:tomcat /data/tomcat_logs sudo mv /opt/tomcat/logs /data/tomcat_logs sudo ln -s /data/tomcat_logs /opt/tomcat/logs这一步我在刚做运维的时候完全没想到直到一次磁盘告警把我从半夜喊醒——Tomcat的localhost_access_log每天几百MB三个月没清理磁盘满了。3. 下载解压Tomcat与核心配置3.1 获取Tomcat二进制包并校验完整性到Apache官网的Tomcat下载页选择你需要的版本。注意两个镜像地址一个是Apache官方镜像https://dlcdn.apache.org/tomcat/一个是备份镜像https://archive.apache.org/dist/tomcat/。dlcdn只有最新版本历史版本要去archive找。截至我写这篇文章时Tomcat 9.0.x仍是Java Web项目最常见的生产版本。下载时选tar.gz而不是zip然后顺手下载同目录下的sha512校验文件cd /tmp wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.85/bin/apache-tomcat-9.0.85.tar.gz wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.85/bin/apache-tomcat-9.0.85.tar.gz.sha512 sha512sum -c apache-tomcat-9.0.85.tar.gz.sha512输出出现OK再解压。这一步看着多余但对生产环境太重要了——软件包可能被劫持或下载不完整解压时出问题会让你误以为自己的操作有错实际上就是包坏了。我有一次在公司内网下载Tomcat镜像源同步了一半校验值对不上解压后启动直接找不到类排查了半小时才想到是压缩包损坏。从那以后校验这步再也没跳过。解压并移动到目标目录sudo tar -zxvf apache-tomcat-9.0.85.tar.gz -C /opt/tomcat --strip-components1--strip-components1会去掉压缩包里的顶层目录名apache-tomcat-9.0.85直接把内容解到/opt/tomcat。这样CATALINA_HOME不带有版本号路径以后升级Tomcat时不用改一堆配置文件里的路径。3.2 配置Tomcat环境变量与管理脚本虽然Tomcat启动脚本本身会推测环境变量但我强烈建议在/etc/tomcat目录下单独建一个环境变量文件让systemd能读到sudo mkdir -p /etc/tomcat编辑/etc/tomcat/tomcat.confJAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 CATALINA_HOME/opt/tomcat CATALINA_BASE/opt/tomcat CATALINA_TMPDIR/tmp JAVA_OPTS-Djava.awt.headlesstrue -Dfile.encodingUTF-8 -server -Xms512m -Xmx1024m CATALINA_PID/var/run/tomcat.pid这里解释几个关键项CATALINA_HOME是Tomcat二进制安装目录CATALINA_BASE是实例运行目录。单实例默认两者相同多实例部署时把BASE指向不同目录实现一套二进制跑多个应用实例后面有时间专门写一篇。JAVA_OPTS里-Xms512m -Xmx1024m是Java堆内存初始值和最大值。生产环境建议把Xms和Xmx设为相同值避免堆动态伸缩带来的GC开销。如果服务器内存8GTomcat独占1G2G比较合理。CATALINA_PID让systemd能精确获取Tomcat主进程PID停止服务时更可靠。然后写systemd服务单元文件。使用systemd托管Tomcat是CentOS 7和Ubuntu 16.04的标配方式优点是开机自启、崩溃自动拉起、日志统一交给journald管理。编辑/etc/systemd/system/tomcat.service[Unit] DescriptionApache Tomcat Web Application Container Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentFile/etc/tomcat/tomcat.conf EnvironmentCATALINA_HOME/opt/tomcat EnvironmentCATALINA_BASE/opt/tomcat EnvironmentCATALINA_TMPDIR/tmp ExecStart/opt/tomcat/bin/startup.sh ExecStop/opt/tomcat/bin/shutdown.sh SuccessExitStatus143 Restarton-failure RestartSec10 [Install] WantedBymulti-user.targetTypeforking的意思是启动脚本会fork出子进程后退出systemd跟踪那个派生出来的主进程。SuccessExitStatus143是因为Tomcat默认关闭脚本在停止进程时返回14312815SIGTERM如果不声明这个退出码systemd会认为服务停止失败。配置完成后sudo systemctl daemon-reload sudo systemctl enable tomcat sudo systemctl start tomcat sudo systemctl status tomcat看到active (running)就说明服务起来了。但别急着高兴先验证端口再部署应用。3.3 验证启动与访问管理页面Tomcat默认监听8080端口启动完成后检查端口ss -tlnp | grep 8080输出应该包含java进程监听0.0.0.0:8080。然后看启动日志sudo tail -f /opt/tomcat/logs/catalina.out正常情况下能看到INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/opt/tomcat/webapps/docs] INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/opt/tomcat/webapps/docs] has finished INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds浏览器访问http://服务器IP:8080/能看到默认页面就说明部署成功。默认页面右上角有“Server Status”、“Manager App”、“Host Manager”三个入口点进去会让你输入账号密码。默认状态下Tomcat是没有任何管理账号的这需要单独配置。这里说个典型新手困惑Tomcat默认页面里的管理入口点进去报403。原因是Tomcat 8之后的webapps/manager、webapps/host-manager默认只允许本机IP访问。这是安全设计不是故障。要解除限制得改conf/manager.xml或者conf/context.xml的Valve配置下面会专门讲。4. 核心配置端口、线程池、内存与管理账号4.1 server.xml端口详解与调整场景Tomcat的conf/server.xml是核心配置文件里面定义了三种通信端口。初学者经常把8080和8005、8009搞混这里一次讲透端口配置项默认端口作用出现场景Connector port80808080HTTP服务端口处理用户浏览器请求对外提供Web服务Connector port80098009AJP协议端口接受Apache通过mod_jk转发的请求Nginx/Apache前置反向代理到TomcatServer port80058005Tomcat管理端口接收SHUTDOWN指令执行shutdown.sh时连接这个端口有个安全注意事项8005端口不要暴露到公网。它只接受一句SHUTDOWN指令如果被外部访问到任何人都能远程关闭Tomcat。建议在云安全组里放行格式只开8080如果直接用Tomcat对外服务8005和8009只允许内网IP访问或者干脆改掉默认端口号。调整端口也很简单找到server.xml里对应Connector改port属性Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 maxThreads200 minSpareThreads10 maxPostSize10485760/这里额外挤出一个问题8080端口被占用怎么办常见的是服务器上同时跑了别的Java应用也用了8080或者之前没停干净的Tomcat还占着端口。先ss -tlnp | grep 8080看是哪个进程占用确认是旧的Tomcat就kill掉如果是别的程序就改server.xml里的端口。4.2 线程池与连接器参数优化server.xml里的Connector参数直接决定Tomcat能扛多少并发。默认配置比较保守遇到稍微大一点的流量就会看到请求排队。生产环境我一般这样配Executor nametomcatThreadPool namePrefixcatalina-exec- maxThreads300 minSpareThreads20/ Connector port8080 protocolHTTP/1.1 executortomcatThreadPool connectionTimeout20000 keepAliveTimeout15000 maxKeepAliveRequests1 maxPostSize10485760 enableLookupsfalse acceptCount200 compressionon compressionMinSize1024 noCompressionUserAgentsgozilla, traviata compressibleMimeTypetext/html,text/xml,text/plain,text/css,text/javascript,application/javascript/逐个解释关键参数的选择逻辑maxThreads300最大处理线程数。不是越大越好每个线程都占用内存300对于4C8G的机器已经偏激进8C16G可以调到500。超过这个数后面的请求进入acceptCount排队。acceptCount200排队等待的请求数量。超过maxThreadsacceptCount之后新请求会被拒绝客户端直接收到Connection refused而不是干等。maxKeepAliveRequests1控制长连接最多复用次数。设为1是让每个连接处理完就断开这对某些浏览器和服务端配合不佳的场景反而有效——避免一个长连接占着线程不释放。但注意这会增加TCP握手次数对静态资源少的纯动态接口影响不大。compressionon开启Gzip压缩对文本类资源HTML、CSS、JS效果明显压缩率通常70%以上能大幅减少带宽消耗。但注意如果前置了Nginx且已经开启压缩这里建议关闭否则双重压缩浪费CPU。这些参数不是随手填的网上找一份压测报告对拍一下然后用ab或wrk做一轮压测观察线程池和响应时间变化再做微调。没有压测就按默认值先跑别在生产上一上来就调得太激进。4.3 配置JVM内存参数避免OOMJava应用程序最常见的问题之一就是内存溢出。Tomcat默认JVM堆内存比较保守取决于机器内存通常1/4左右很多部署上去没跑几天就OutOfMemoryError撑爆堆内存。推荐在/etc/tomcat/tomcat.conf里显式配置JAVA_OPTS-server -Xms1024m -Xmx1024m -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m -XX:UseG1GC -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/opt/tomcat/logs/heapdump.hprof-Xms和-Xmx建议相等避免JVM频繁扩容和缩容堆内存降低GC停顿。Metaspace是JDK 8之后替代PermGen的元数据区域如果不限制有的应用会产生大量动态类导致元空间无限增长直至物理内存耗尽。-XX:HeapDumpOnOutOfMemoryError是救命参数——OOM时自动导出堆快照文件后续用MAT分析定位哪个对象爆了。还有个容易混淆的参数CATALINA_OPTS和JAVA_OPTS。Tomcat启动脚本里两者的区别是JAVA_OPTS会传给所有Java子进程包括启动脚本本身CATALINA_OPTS只传给Tomcat主进程。严格来说应该把Tomcat专用的配置放CATALINA_OPTS把JDK全局属性放JAVA_OPTS。但实际使用中大多数场景两者混用也不会出问题因为Tomcat没有别的子进程。我自己的习惯是内存和GC配置放JAVA_OPTS这样即使以后脚本调整也不容易被覆盖。4.4 配置管理账号与Manager应用访问限制默认Tomcat没有管理账号改配置文件前先看懂结构。控制管理页面的配置文件是conf/tomcat-users.xml在tomcat-users标签内添加role rolenamemanager-gui/ role rolenamemanager-script/ role rolenameadmin-gui/ user usernameadmin password你的强密码 rolesmanager-gui,admin-gui/Tomcat定义了多个角色常用这几个manager-gui允许访问/manager/html图形化部署管理页面manager-script允许访问/manager/text脚本化部署支持API方式上传War包manager-jmx允许通过JMX管理admin-gui允许访问/host-manager/html虚拟主机管理页面配置完重启Tomcat登录Manager页面就能在线部署、卸载、重载Web应用。但这里就是你们热词里搜到的问题——“tomcat后台页面上传war被限制ip”。Tomcat 8/9的webapps/manager/META-INF/context.xml和webapps/host-manager/META-INF/context.xml里默认写了Valve classNameorg.apache.catalina.valves.RemoteAddrValve allow127\.\d\.\d\.\d|::1|0:0:0:0:0:0:0:1 /这个Valve的意思是只允许本机IP(127.0.0.1和IPv6 ::1)访问Manager页面。所以你从远程浏览器访问Manager时即使账号密码全对也会看到403 Access Denied。解决方案有两种。如果你确定服务器在内网且只给自己用可以直接注释掉Valve。但如果你有安全洁癖更好的做法是只放开你办公网的固定IPValve classNameorg.apache.catalina.valves.RemoteAddrValve allow192\.168\.1\.\d|127\.\d\.\d\.\d|::1|0:0:0:0:0:0:0:1 /注意IP匹配用的是正则192.168.1.后面的\\d表示任意数字。修改后重启Tomcat生效。5. 部署Web应用与生产环境实践5.1 War包部署的三种方式Web应用在Tomcat有三种部署方式自动部署、指定位置部署、在线部署。方式一最简单把xxx.war放到webapps目录Tomcat启动或reload后自动解压并部署。因为配置里的autoDeploytrue默认开启目录发生变更会自动触发部署。但我必须提醒生产环境慎用自动部署——你往webapps丢一个半成品war包Tomcat立刻会尝试部署如果包结构不对可能污染当前运行的应用状态。我见过有人误传一个损坏的war包结果Tomcat直接部署失败还影响到了同目录其他应用。方式二改server.xml的Host标签Host namelocalhost appBasewebapps unpackWARstrue autoDeployfalse Context path/myapp docBase/data/apps/myapp.war / /Host这种方式的优势是Web应用可以放在Tomcat目录之外并且指定路径访问。比如把多个应用分散到不同磁盘目录避免挤在同一个webapps里。autoDeployfalse意味着只有重启Tomcat或者手动reload才会加载新版本适合发布流程严格控制的场景。方式三通过Manager页面在线部署。登录/manager/html后在“Deploy”区域选择war包上传Tomcat会直接部署。这是最方便的方式但刚才说过别忘记解决Manager的IP限制问题。还有一种脚本化方式用curl执行manager-script上传curl -u admin:密码 http://localhost:8080/manager/text/deploy?path/myapp \ -F file/tmp/myapp.war推荐在CI/CD流水线里用这种方式发布比你手动上传省事得多也更容易集成到Jenkins或者GitLab CI里。5.2 context.xml修改Servlet版本与目录权限部署过程中还有一个高频坑应用打包时的Servlet版本高于Tomcat支持的版本。比如你本地用Tomcat 10开发打出来的war包里web.xml声明了version5.0Servlet 5.0对应Tomcat 10但服务器是Tomcat 9只支持Servlet 4.0部署时日志就会报org.apache.catalina.startup.HostConfig deployWAR Error deploying web application archive java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[...]] Caused by: java.lang.IllegalArgumentException: Servlet [xxx] in web应用程序 [/myapp] 与 JSP 版本不匹配解决办法要么把服务器换成Tomcat 10要么把war包里的web.xml声明改回4.0或者直接删掉web.xml里的version声明让Tomcat按默认值处理。更彻底的做法是检查开发环境pom.xml里的Servlet API依赖版本确保编译目标与运行时容器一致。还有权限问题。用tomcat用户运行服务时war包的属主必须是tomcat否则部署时无法写入解压目录sudo chown -R tomcat:tomcat /data/apps/注意Tomcat对war包的归属有严格检查属主不对会报Access is denied异常表现为部署超时或目录创建失败。别问我怎么知道的我第一次部署时忘了chown盯着日志看了一下午。5.3 多实例部署与session共享的架构考量如果你的服务器内存充足一个Tomcat扛不住所有流量可以考虑多实例部署。比如在/opt/tomcat安装二进制文件然后建/opt/tomcat-instance1、/opt/tomcat-instance2每个目录只放conf、logs、webapps、temp、work通过CATALINA_BASE区分mkdir -p /opt/tomcat-instance1/{conf,logs,webapps,temp,work} cp -r /opt/tomcat/conf/* /opt/tomcat-instance1/conf/然后修改第二个实例的server.xml端口8081和8006修改CATALINA_BASE环境变量后启动CATALINA_BASE/opt/tomcat-instance1 /opt/tomcat/bin/startup.sh多实例部署的好处是应用隔离——一个应用OOM不会连带把另一个拖垮。代价是维护成本变高每个实例都要单独管理而且如果多个实例间需要共享Session还得引入Redis等外部Session存储。多实例模式下共享会话一般这样规划引入Redis Session Manager把Session信息从Tomcat内存挪到Redis。这样多台Tomcat之间可以共享登录状态前端负载均衡随便转发。简单说不用改业务代码只加依赖和配置下载tomcat9-redis-session-manager相关jar包放进lib在context.xml中添加Valve和Manager配置重启Tomcat这个方案我实际部署过注意Redis的maxIdle连接数和Tomcat线程数要匹配否则高并发时Redis连接池会成为瓶颈。另外Session序列化器务必统一用JAVA序列化或者JSON别一半实例用Java一半用JSON那样Session无法共享。5.4 小贴士日志轮转与监控Tomcat日志默认不会自动轮转切割catalina.out会无限增长时间长了能把磁盘塞满。生产环境要么用Linux自带的logrotate要么在cron里写脚本定期切割。简单可靠的方案是配置logrotate。创建/etc/logrotate.d/tomcat/data/tomcat_logs/*.out { daily rotate 7 compress delaycompress missingok copytruncate notifempty }copytruncate是重点——它先拷贝日志再清空原文件Tomcat进程不重启也能完成切割。rotate 7保留7天日志compress压缩旧日志。如果没有logrotate至少写个cron任务超过500MB就清空一次。监控方面至少盯三个指标8080端口存活检测curl -I http://localhost:8080/、JVM堆内存使用率、线程数。轻量方案是zabbix或者Prometheus node_exporter再加一个黑盒探针探HTTP状态码。别等用户报障再去查catalina.out那样太被动。6. 常见问题与排查技巧实录6.1 Tomcat启动失败/启动慢的定位思路Tomcat启动失败我先看日志再看端口最后看权限。基本排查链路是查服务状态systemctl status tomcat -l查日志tail -100 /opt/tomcat/logs/catalina.out查端口ss -tlnp | grep 8080查Java进程ps -ef | grep java查目录权限ls -ld /opt/tomcat/*启动慢的常见原因/dev/random熵池不足导致UUID生成卡顿、DNS反解超时、大量应用部署启动。针对熵不足可以在catalina.sh的JAVA_OPTS里加-Djava.security.egdfile:/dev/urandom这个参数的原理是让JVM从/dev/urandom而不是/dev/random读取随机数。/dev/random在熵池耗尽时是阻塞的导致Tomcat生成Session ID时卡住几秒钟甚至几十秒/dev/urandom则不会阻塞。对非加密应用安全性几乎没有差异但启动速度明显提升。DNS反解问题Connector默认enableLookupsfalse但有的应用日志里还是会看到连接客户端IP反解。检查server.xml里Connector是否加了enableLookupsfalse没加就加上。6.2 war包部署失败的5类高频报错我整理了这几年遇到最多的部署报错做成速查表供你对照报错特征根因解决方案ZipException: invalid LOC headerwar包损坏或不完整重新打包上传校验MD5UnsupportedClassVersionError编译版本高于JVM版本降低编译target或升级JDKNoClassDefFoundError依赖jar缺失或冲突检查WEB-INF/lib用dependency tree排查java.lang.IllegalArgumentException: Document base ... does not existContext的docBase路径错误检查server.xml/context.xml路径是否正确且属主一致org.apache.jasper.JasperException: Unable to compile class for JSPJSP编译环境问题确认JAVA_HOME已配置编译临时目录可写其中NoClassDefFoundError最让人头疼。如果你在开发环境运行正常传到Linux服务器上却报类找不到多半是依赖冲突——多个jar包里有同名类Tomcat的类加载机制按照“web应用lib优先、父类加载器次之”的顺序加载到了错误版本。排查办法找到报错类名去WEB-INF/lib里sAll一下看有没有多个jar包含相同包名用jar tf xx.jar | grep 类名定位。有时候还得开启Tomcat的类加载器调试日志-Dorg.apache.catalina.loader.WebappClassLoaderBase.DEBUGtrue加了之后catalina.out会刷出大量类加载信息虽然啰嗦但能直观看到每个类的来源jar包。6.3 双亲委派机制与Tomcat类加载的相爱相杀热词里出现“tomcat打破双亲委派机制”这确实是个经典问题。JVM默认的双亲委派模型Parent Delegation Model是类加载器收到加载请求时先委托给父加载器父加载器找不到才自己加载。这样做避免类被重复加载也保证核心类不被篡改。但Tomcat为了做到Web应用之间类隔离你的应用A想用Spring 4应用B想用Spring 5互不干扰打破了双亲委派机制每个Web应用有一个独立的WebappClassLoader它遵循“先自己加载再委托父加载器”的顺序。具体规则是先检查WEB-INF/classes目录的类再检查WEB-INF/lib目录的jar包找不到才委托给父加载器CommonClassLoader加载共享库这个机制带来的好处是应用隔离坏处是如果你把一些本该共享的公共jar比如数据库驱动、日志库也打进了WEB-INF/lib每个应用各加载一份就会出现多个同名实例静态变量不共享、类比较不相等、ClassCastException莫名其妙地冒出来。一个典型的ClassCastException场景应用A和B都包含log4j的jar你在Tomcat的lib目录放一份应用里也放一份然后应用代码里强转Logger类型就会报类型不匹配。解决办法公共库放CATALINA_HOME/lib应用私有库留在WEB-INF/lib不要重复。这个机制也解释了为什么修改WEB-INF/lib里的jar后需要重启Tomcat——重新部署不等于重新加载类旧类还驻留在PermGen/Metaspace里只有重启才能彻底清理。6.4 磁盘满与日志清理实战我前面提过日志撑爆磁盘的事故再补充一个具体案例某天晚上收到磁盘告警df -h一看/dev/vda1使用率99%。排查先挂/opt/tomcat/logs目录du -sh *发现catalina.out已经7.8G。为什么这么大因为应用里有一段异常日志打印了堆栈每次执行SQL语句都往Error里写一次QPS稍微一上去日志就疯长。处理过程先清理在线日志然后用logrotate规则限制大小。清理命令# 立即截断当前日志文件不重启Tomcat sudo -u tomcat sh -c cat /dev/null /opt/tomcat/logs/catalina.out # 删除已轮转的旧日志 sudo find /data/tomcat_logs/ -name *.out.* -mtime 7 -exec rm {} \;用cat /dev/null而不是rm是因为Tomcat进程还开着这个文件句柄rm删掉后进程依然往那个inode写数据磁盘空间根本不会释放只有重启进程才释放。很多人删除日志后df显示空间没变就是这个原因。cat /dev/null则保证原文件被清空进程继续正常写入。做完后再去代码层面修掉疯狂打日志的问题——否则你再怎么清理日志还是会涨回来。这也是我坚持在生产环境配置logrotate而不是依赖人肉维护的原因。6.5 端口冲突和进程残留的终极排查部署Tomcat最烦的是“明明stop了端口还是被占用”。Tomcat的shutdown脚本依赖8005端口发指令如果某些异常情况下进程无响应shutdown脚本会失败主进程还在端口自然还在。这时候得手工处理# 查出占用8080端口的PID sudo lsof -i :8080 # 或者 sudo ss -tlnp | grep :8080 # 杀掉该进程 sudo kill -9 PID # 清理pid文件 sudo rm -f /var/run/tomcat.pid还有种情况是shutdown脚本已经成功但是日志里出现Failed to destroy the connection实际上进程挂了但端口还没释放等待几分钟再查。如果多实例部署注意检查每个实例的CATALINA_BASE/tomcat.pid是否写入了各自的PID避免停止一个实例把另一个也杀了。至于Tomcat重启后端口变了实际不会除非你改错了server.xml检查conf/server.xml是否真的生效用grep -E port|Connector /opt/tomcat/conf/server.xml确认。7. 从个人实践角度给新手部署者的几点建议说句实在话Tomcat安装部署的难度不算高真正考验人的是遇到问题时的排查思路。我自己带过不少新人发现他们在Tomcat部署上的通病是“只知道跟着教程复制粘贴出了问题就懵了”。所以最后分享几条个人经验第一部署前先把server.xml和catalina.sh这两个文件通读一遍不求全看懂但至少知道哪些参数在哪。很多问题其实都不复杂比如端口被占用、线程池配小、内存参数写错你如果熟悉配置文件一眼就能发现问题根源。第二所有操作前先备份。改配置前拷贝一份server.xml、context.xml、catalina.sh到/opt/tomcat/backup改坏了随时还原。别嫌麻烦这个习惯救过我无数次。第三生产环境永远用systemd托管服务不要在终端里直接跑startup.sh然后关掉SSH窗口就完事。那样Tomcat会随着Shell退出而终止除非你用了nohup。systemd不仅能开机自启还能在进程崩溃后自动拉起这才是线上服务应有的待遇。第四日志别省着看。catalina.out是排查问题的第一线索很多人出问题第一反应是Google其实答案就在日志里。学会看日志、查端口、看进程是Linux运维的基本功比背多少命令都有用。最后再补一个我常用的小技巧部署完成后写一个验证脚本一键检查所有关键项#!/bin/bash echo Java版本 java -version 21 echo echo Tomcat进程 ps -ef | grep catalina | grep -v grep echo echo 端口监听 ss -tlnp | grep -E 8080|8005 echo echo 健康检查 curl -s -o /dev/null -w HTTP状态码: %{http_code}\n http://localhost:8080/ echo echo 磁盘使用 df -h /opt/tomcat | tail -1这个脚本花不了几分钟但每次部署完跑一遍心里有底得多。线上环境出问题时第一件事永远是先收集信息再动手改配置而不是一上来就乱改server.xml。希望这篇文章能帮你少踩几个坑把Tomcat部署这件事从“玄学”变成“常规操作”。
返回列表