首页 / 资讯中心 / 文章详情

Tomcat安装配置、部署调优与常见故障排查指南

Tomcat安装配置、部署调优与常见故障排查指南 ★ FEATURED ARTICLE
1. 装个 Tomcat 而已为什么值得单独占一章刚入行那会儿我对Tomcat安装和配置这七个字的理解就是下个压缩包解压敲startup.sh浏览器打开 8080 看到那只猫收工。直到第一次把项目往测试环境上一扔页面 404 找不到路、日志里一堆ClassNotFoundException、同事连不上我的端口、重启机器后服务自己没了——我才明白装起来和配好之间隔着一整条生产环境的鸿沟。这一章我想聊的就是这条鸿沟怎么填。Tomcat 本质上是一个 Servlet 容器说白了它是个能跑 Java Web 程序的壳子。你写的那些WebServlet、Filter、JSP自己不成立得有个东西负责接收 HTTP 请求、解析、找到对应的处理类、把结果写回响应。Tomcat 干的就是这活儿。它不像 Nginx 那样只做静态转发也不像完整的应用服务器那样把 EJB、事务、消息全包进来它的定位很清晰——轻量、够用、启动快。正因如此绝大多数中小规模的 Java Web 项目、内部管理后台、接口服务最终落地的时候都是往 Tomcat 里一塞。这篇文章适合谁看如果你是完全没碰过 Java Web 部署的新手我会从目录结构、端口、启动脚本一层层拆开你能照着抄如果你已经装过几次但总在权限、端口、版本上翻车中间关于 JDK 匹配、systemd 服务化、连接器参数的部分应该能帮你把坑填上如果你是在做运维或者带团队第七章的排查速查表和第八章的调优点可以直接拿去当内部文档用。我不打算写成一份官方手册的复述。官方文档写得比我严谨但它不会告诉你为什么unpackWARs和autoDeploy一起开会把你的上传目录清空也不会提醒你 1024 以下的端口为什么普通用户绑不上。这些是踩出来的。下面我按先想清楚为什么这么装、再一步步装、装完怎么配、配完怎么排错的顺序来。2. 动手之前先把方案定下来2.1 三种装法别一上来就选最省事的实际工作中装 Tomcat 大致三条路选择哪条取决于你要拿它干什么。第一条是包管理器直接装yum install tomcat或者apt install tomcat9这类。好处是省心依赖、服务脚本、配置文件路径、日志目录全都按发行版规范摆好了systemctl start就能起。缺点是版本往往滞后配置文件被拆散到/etc/tomcat9/、/var/lib/tomcat9/、/usr/share/tomcat9/好几个地方CATALINA_HOME和CATALINA_BASE分离新手第一次找server.xml能在文件系统里转十分钟。另外包管理器装的版本升级会覆盖配置你改过的server.xml可能在某次apt upgrade之后被打回原形。第二条是官方二进制压缩包下载apache-tomcat-x.y.z.tar.gz解压到自选目录。这是我在生产环境最常用的方式原因很简单所有东西都在一个目录里conf、logs、webapps、bin一目了然迁移的时候整目录打包带走升级的时候新目录并排放着改个软链接或者 systemd 单元里的路径就能回滚出问题不慌。缺点是环境变量、服务脚本、权限都得自己来。第三条是容器镜像。适合 CI/CD 流水线、需要横向扩缩的场景。但对学习 Tomcat 到底怎么跑起来这件事其实是种遮蔽——容器里往往已经把server.xml改好、JVM 参数设好、日志重定向好了你看到的是一层封装真出问题时不知道往哪看。我的建议是先用二进制包在自己的机器或虚拟机上完整装一遍、改一遍、踩一遍把机制搞明白再去用镜像。注意如果你只是想跑个 Demo 验证代码包管理器那条路最快如果你要交付给别人的环境二进制包那条路最可控。别在图省事和可控性之间反复横跳来回换装法是最浪费时间的。2.2 版本和 JDK 的对应关系配错了后面全是白工这一段是重点因为它是新手翻车率最高的地方而且报错信息往往不指向真正原因。Tomcat 的版本和 Java 版本、Servlet 规范是绑定的。大概对应关系是这样的Tomcat 版本最低 Java 版本Servlet 规范包命名空间8.5.xJava 7Servlet 3.1javax.servlet9.0.xJava 8Servlet 4.0javax.servlet10.0.xJava 8Servlet 5.0jakarta.servlet10.1.xJava 11Servlet 6.0jakarta.servlet11.xJava 17Servlet 6.1jakarta.servlet表里最要命的是最后一列。从 Tomcat 10 开始Servlet API 的包名从javax.servlet.*变成了jakarta.servlet.*。这意味着一个用javax.servlet写的老项目扔进 Tomcat 10 里编译好的 class 是不会变的运行时找不到javax.servlet.http.HttpServletRequest这个类直接NoClassDefFoundError页面 500日志里刷一屏。这时候你去查Tomcat 启动报错 找不到 servlet 类能翻出一堆互相矛盾的答案实际上根因就一句话版本不匹配。我的判断原则很粗暴老项目、第三方组件依赖javax的老老实实用 Tomcat 9新项目、框架已经迁到jakarta的用 10.1 或更高。如果非要让老项目跑在新 Tomcat 上业内有个过渡方案是把jakarta的包重命名回javax的转换工具但那属于权宜之计长期还是得让上游框架升级。JDK 这边同样有坑。Tomcat 9 官方要求 Java 8 及以上但如果你的项目用到某些新语法或者依赖的库要求 Java 17那就得往上顶。反过来Java 17 跑 Tomcat 9 是没问题的别被最低版本这三个字误导成只能这个版本。真正需要注意的是 JDK 实现的选择OpenJDK 各发行版都有装哪个都行但要确认java -version输出的主版本号和你预期的完全一致——机器上装了两三个 JDKJAVA_HOME指错是很常见的低级错误而它引发的报错往往长得很像配置错误。2.3 目录规划和运行用户一开始就该想好很多人装 Tomcat 的习惯是直接解压到/root或者当前用户的家目录然后用 root 跑。开发机上这么干问题不大但只要这台机器将来要对外提供服务这就是个定时炸弹。Tomcat 是网络服务监听端口、解析外部请求、解析上传的文件用 root 跑意味着一旦应用层出现漏洞攻击者拿到的就是最高权限。哪怕你不考虑安全用 root 启动还会带来另一个麻烦应用写出来的日志、上传的文件、临时目录全都属主是 root后续换个普通用户去维护动不动就 permission denied。我的做法是固定一套目录约定无论在哪个环境都保持一致安装目录/opt/tomcat软链接指向具体版本目录比如/opt/tomcat-9.0.xx数据基目录/var/lib/tomcat放webapps、temp、work如果有必要可以跟安装目录分离日志目录/var/log/tomcat单独挂盘方便轮转和清理运行用户一个专用的系统用户比如tomcat禁止登录 shell这里有个概念值得提前说清楚CATALINA_HOME是安装目录CATALINA_BASE是运行时基目录。单实例部署时两者通常是同一个路径不用管。但当你需要在一台机器上跑多个 Tomcat 实例、共享同一份程序文件时CATALINA_BASE就派上用场了——每个实例有自己的conf、logs、webapps、temp、work程序文件共用一份省空间也好升级。这个设计在资源紧张的小机器上很实用一开始不了解也没关系知道有这回事就行。3. 从零到跑起来二进制包安装全流程3.1 下载、校验、解压拿到安装包的方式我一般是在一台能访问外网的机器上下好再传到目标机器或者用内网的制品库。不管哪种方式拿到之后先做完整性校验这一步经常被跳过但压缩包下了一半断掉、传输过程被截断解压出来的文件是残缺的后面启动时报的错会非常离谱你会花几个小时往配置文件上找原因。校验的方式是比对官方公布的哈希值sha512sum或者sha256sum看一眼就行sha512sum apache-tomcat-x.y.z.tar.gz比对一致后再解压。解压到一个带版本号的目录比如/opt/tomcat-9.0.xx然后建一个current软链接指向它。这个软链接的技巧看起来多此一举实际上非常有用配置文件里、systemd 单元里、脚本里统一写/opt/tomcat/current将来升级只要把软链接指到新版本目录再重启服务回滚就是再把链接指回去全程不用改任何配置。tar -xzf apache-tomcat-x.y.z.tar.gz -C /opt ln -sfn /opt/tomcat-9.0.xx /opt/tomcat解压之后先别急着启动花两分钟看一眼目录因为后面所有操作都围绕这几个目录转目录作用你会经常动它吗bin启动、停止、版本检查脚本偶尔主要加setenv.shconf全部配置文件经常server.xml是主战场lib容器和应用的公共依赖 jar偶尔放数据库驱动之类logs日志输出天天看temp临时文件基本不管但要注意磁盘webapps应用部署目录每次发布都动workJSP 编译产物、会话序列化基本不管出问题时可清空work目录值得单独提一句。JSP 第一次被访问时会被编译成 Java 再编译成 class产物就放在这里。偶尔会遇到改了 JSP 页面但效果没变的情况多半是这个目录里的旧产物没被清掉停服务后把work下的内容删空再启动问题通常消失。这是新手最容易被我刚才明明改了折磨的地方。3.2 运行用户与文件权限建用户这一步不要省useradd -r -s /sbin/nologin -d /opt/tomcat tomcat-r表示系统用户-s /sbin/nologin表示不给登录 shell-d指定家目录。然后把需要的目录属主改过去chown -R tomcat:tomcat /opt/tomcat-9.0.xx chown -R tomcat:tomcat /var/log/tomcat chmod -R urwX /opt/tomcat-9.0.xx这里有个实操细节chmod用的是urwX而不是urwx大写的X表示只给目录和已经有执行位的文件加执行权限不会把一堆配置文件也变成可执行。虽然对功能没影响但审计的时候看起来会清爽很多。至于 8080 端口它大于 1024普通用户直接就能绑不需要任何额外操作。只有当你打算让 Tomcat 直接监听 80 或 443 时才需要处理低端口问题——要么加CAP_NET_BIND_SERVICE能力要么在前面放一层反向代理把 80 转给 8080。后者更常见也更灵活因为证书、压缩、限流这些通常都在代理层做。注意千万别用chmod -R 777去解决权限问题。第一次遇到写不进日志的时候很多人条件反射就上 777当下确实通了但它把问题从权限不足变成了任何进程都能改你的程序文件而且真正的原因属主错了、SELinux 策略、父目录没有执行位被彻底掩盖。3.3 环境变量与 setenv.shTomcat 的启动脚本会读JAVA_HOME如果没设脚本会尝试从PATH里找java。为了确定性强我倾向于显式设置。设置的位置有两个选择写进/etc/profile.d/全局生效或者写进bin/setenv.sh只对 Tomcat 生效。我更推荐后者因为这是 Tomcat 官方支持的扩展点catalina.sh启动时会自动 source 它而且升级 Tomcat 时这个文件不会被覆盖# /opt/tomcat/current/bin/setenv.sh export JAVA_HOME/usr/lib/jvm/java-17-openjdk export CATALINA_OPTS-Xms512m -Xmx1024m -XX:MetaspaceSize128m -XX:MaxMetaspaceSize256m export JAVA_OPTS-Dfile.encodingUTF-8 -Duser.timezoneAsia/Shanghai这里要区分JAVA_OPTS和CATALINA_OPTS。前者会传给所有由脚本启动的 JVM包括shutdown.sh那个只负责发关闭指令的短命进程后者只传给真正跑 Tomcat 的那个 JVM。所以内存参数应当放在CATALINA_OPTS避免关闭脚本也去申请一大块堆内存。编码和时区这类通用参数放JAVA_OPTS也无妨但如果机器上有多个 Java 应用共用环境变量还是分开写更保险。-Dfile.encodingUTF-8这条在近几年的 JDK 上其实已经默认了但显式写着不吃亏尤其是那些容器里 locale 被设成POSIX的环境不写就容易出现中文文件名乱码、日志里中文变问号的情况。3.4 注册成系统服务startup.sh起的进程是脱离终端的但机器重启之后它不会自动回来而且进程挂了也没有任何守护。生产环境必须交给 init 系统管。现在主流发行版都用 systemd写一个单元文件放/etc/systemd/system/tomcat.service[Unit] DescriptionTomcat Servlet Container Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentJAVA_HOME/usr/lib/jvm/java-17-openjdk EnvironmentCATALINA_HOME/opt/tomcat/current EnvironmentCATALINA_BASE/opt/tomcat/current EnvironmentCATALINA_PID/opt/tomcat/current/temp/tomcat.pid ExecStart/opt/tomcat/current/bin/startup.sh ExecStop/opt/tomcat/current/bin/shutdown.sh Restarton-failure RestartSec10 LimitNOFILE65535 [Install] WantedBymulti-user.target几个参数值得解释。Typeforking是因为startup.sh本身会退出、把真正的 Java 进程留在后台systemd 需要知道这一点。CATALINA_PID指定 pid 文件路径这样shutdown.sh知道该给谁发信号Restarton-failure才能准确判断进程是否异常退出。LimitNOFILE65535是打开文件句柄上限默认值在很多发行版上是 1024Tomcat 在并发稍高的时候会撞到这个限制日志里报Too many open files而这个问题在小流量测试时根本不会出现一上生产就来非常隐蔽。写完之后systemctl daemon-reload再enable加start。验证分三步走systemctl status看进程状态ss -lntp | grep 8080看端口监听curl -I http://127.0.0.1:8080/看有没有返回 200。三步都过才算真的起来了。4. 配置文件逐个拆开看4.1 server.xml 的骨架逻辑conf/server.xml是 Tomcat 的核心配置第一眼看上去层层嵌套很唬人其实结构就四层Server是最外层代表整个容器里面一到多个Service把连接器和引擎绑在一起Service里是Connector加EngineEngine里是HostHost里是Context。用生活化的说法Server是整栋楼Service是一个业务单元Connector是大门决定什么样的请求能进来、怎么进来Engine是楼里的调度中心Host是其中一户人家一个域名/虚拟主机Context是这户人家里的某个房间一个 Web 应用。大多数情况下你只需要动Connector和Host的属性Engine和Server保持默认。Server上那个port8005的关闭端口官方默认允许本机通过这个端口发送SHUTDOWN字符串来停止服务这在多用户机器上有风险要么改成随机强口令要么直接port-1禁用它改用shutdown.sh发信号的方式关。我一般选后者简单干净。4.2 Connector 的端口、协议与线程参数一个典型的 HTTP 连接器长这样Connector port8080 protocolorg.apache.coyote.http11.Http11NioProtocol maxThreads400 minSpareThreads50 acceptCount200 maxConnections8192 connectionTimeout20000 URIEncodingUTF-8 compressionon compressionMinSize2048 compressibleMimeTypetext/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json serverweb /protocol那一行Tomcat 8 以后写HTTP/1.1也默认走 NIO但显式写全类名更直观也方便将来换成 NIO2。maxThreads是处理请求的工作线程上限默认 200。这个值不是越大越好每个线程都要占栈内存默认约 1MB 虚拟地址空间线程过多会导致上下文切换开销上升反而拖慢。经验上的估算方法是看并发并发数大致等于 QPS 乘以平均响应时间秒。比如峰值 500 QPS、平均响应 100 毫秒那并发约 50maxThreads设到 150 到 200 就足够留出余量了。反过来如果不算直接按网上抄来的 2000 设置很可能把机器拖垮。acceptCount是等待队列长度工作线程全忙时新来的连接先在这里排队队列也满了就开始拒绝。它和maxThreads的关系是前面排队、后面干活队列太短会造成瞬时高峰直接报错太长则会让请求等待时间变得不可控。200 是个比较中庸的值。maxConnections是 NIO 模式下的连接数上限注意它和maxThreads不是一回事——一个连接在 keep-alive 空闲时可以不占用工作线程所以maxConnections通常远大于maxThreads默认 8192多数场景不用改。serverweb这个属性比较冷门它控制 HTTP 响应头里Server字段的值。默认会暴露 Tomcat 的版本号改成web或者干脆留空能减少一点信息泄露。这属于安全基线里的小项成本几乎为零顺手就做了。4.3 虚拟目录与外部部署默认情况下应用要放在webapps下面但现实里经常遇到几种情况上传的文件放在数据盘上、前端构建产物在别的目录、多个应用共享一份静态资源。这时候就需要把外部路径挂进来。第一种方式是在Host里加一个ContextHost namelocalhost appBasewebapps unpackWARstrue autoDeploytrue Context path/uploads docBase/data/uploads reloadablefalse / /HostdocBase指向外部绝对路径path是访问前缀。但要注意把Context直接写在server.xml里意味着改一次配置就得重启整个容器。更灵活的做法是在conf/Catalina/localhost/下建一个 XML 文件文件名就是访问路径内容只有一行Context定义。这种方式支持热加载改完不用重启全容器而且不会因为一次配置改动影响到其他应用。注意path属性在外部配置文件方式下应当省略因为文件名已经决定了路径两个都写且不一致会导致启动报错。这个错很常见报错信息也不够直白新手容易卡住。4.4 日志配置别等出事了才想起它Tomcat 有两套日志容易混淆。一套是logs/catalina.out它是启动脚本把标准输出重定向过去产生的包含 JVM 的报错、System.out.println的输出、未捕获异常堆栈属于什么都往里扔的杂货铺。另一套是logging.properties配置的按天分割的日志比如catalina.YYYY-MM-DD.log、localhost.YYYY-MM-DD.log通过 Java 自带的日志框架管理能配级别、能配格式。catalina.out最大的问题是不会自动轮转。服务跑上几个月这个文件能涨到几个 G某天磁盘满了整个服务崩掉而且崩得很莫名其妙。处理方式有两种一是改用 systemd 管理后标准输出进了 journalcatalina.out不再生成日志交给系统的日志服务去轮转二是保留文件但用系统的日志轮转工具logrotate定期切割。我倾向于前者少一个需要操心的文件。应用自身的日志比如用 Logback 或 Log4j2 输出的则完全由应用自己配置Tomcat 不管。这也是个常见误区——有人改了logging.properties发现业务日志的格式没变那是因为业务日志走的是应用自己的配置两者是不同体系。5. 部署应用几种姿势和它们的代价5.1 自动部署的机制与陷阱Host上有两个属性配合工作unpackWARstrue表示把 WAR 包解压成目录再运行autoDeploytrue表示定期扫描webapps目录发现新的 WAR 就发布发现 WAR 被删掉就把对应的应用卸载。这两个开关在开发环境很方便扔个 WAR 进去等几秒就好了。但在生产环境它有两个真实的坑。第一个坑是删除行为。当你把xxx.war删掉时Tomcat 不只会卸载应用通常还会把解压出来的xxx目录一起删掉。如果你的应用把用户上传的文件存在了自己的目录下这是很不好的做法但确实有人这么干删 WAR 的那一刻数据就没了。我见过一次上线回滚运维把 WAR 换回旧版本顺手删了新 WAR结果把上传目录清了事后从备份恢复花了大半天。第二个坑是部署时机不可控。autoDeploy的扫描是有间隔的你无法精确知道应用什么时候被加载这在需要严格发布窗口的场景下不合适。生产环境的稳妥做法是关掉自动部署autoDeployfalse、unpackWARsfalse把 WAR 的发布纳入发布流程停服务或停应用、放包、启动、验证。如果嫌停整个容器太重可以用外部Context配置文件的方式单独控制某个应用的重载。牺牲一点便利换来的是可预期。5.2 部署到根路径与上下文路径访问路径由 WAR 包的文件名决定。app.war部署后访问路径是/app想部署到根路径直接http://host/访问WAR 包要命名为ROOT.war。这个大小写是固定的改成root.war只会得到一个/root路径的应用很多人第一次配置根路径失败就是因为这个。还有一个容易踩的点如果webapps下已经存在一个ROOT目录默认自带的欢迎页你再放一个ROOT.war行为取决于unpackWARs的配置可能出现新的没生效、旧的还在的诡异现象。干净的做法是先清掉默认的ROOT目录和docs、examples、manager、host-manager这些用不上的自带应用再放自己的包。顺便说一句那几个自带的管理应用在生产环境应该直接删掉。它们不是必需的留着反而多几个可能的入口。删的时候把webapps下对应目录和 WAR 一起删光删目录的话下次启动如果开了自动部署它可能又从 WAR 里解出来。5.3 静态资源与上传目录的处理前端构建产物HTML、JS、CSS、图片如果也交给 Tomcat 服务有两种做法塞进 WAR 包里或者用虚拟目录映射到外部磁盘。塞进包里简单但每次改一个 CSS 都要重新打包发布代价太大。更实际的做法是用Context映射外部目录前端单独发布只更新文件不动应用。这时候要注意DefaultServlet的几个参数它在conf/web.xml里全局配置servlet servlet-namedefault/servlet-name servlet-classorg.apache.catalina.servlets.DefaultServlet/servlet-class init-param param-namelistings/param-name param-valuefalse/param-value /init-param init-param param-namereadonly/param-name param-valuetrue/param-value /init-param load-on-startup1/load-on-startup /servletlistings控制目录下没有默认首页时是否列出文件清单默认就是false但有些老版本的默认配置或者被改过的环境里是true那等于把整个目录结构暴露给任何访客必须关掉。readonly为true时禁止通过 HTTP 的 PUT、DELETE 方法修改文件这个默认也是true除非你有特殊需求否则别动。如果静态资源量大更好的选择是在前面放一层反向代理让代理直接读磁盘返回请求根本不进 Tomcat。Tomcat 处理静态文件的效率不如专业的 Web 服务器把这块卸掉容器就能专心处理动态请求这是很划算的分工。6. 安全与运行规范上不能省的几件事6.1 关掉一切用不到的东西装完先做减法。自带的示例应用、文档、管理控制台全删。conf/tomcat-users.xml里如果配了管理账号要么删掉整个文件内容要么确认它没有任何可用角色。server.xml里被注释掉的 AJP 连接器不要随手打开——那个协议是给前置 Web 服务器用的如果代理层配置不严存在请求头被伪造的风险。现代的部署方式里反向代理走 HTTP 就够了没必要启用 AJP。关闭端口8005前面提过改成-1禁用。错误页也要处理一下默认的 500 页面会带上异常堆栈和 Tomcat 版本信息对攻击者来说是现成的情报。可以在Host里加一个ErrorReportValveValve classNameorg.apache.catalina.valves.ErrorReportValve showReportfalse showServerInfofalse /这样对外只显示一个干净的错误页详细信息进日志自己人看日志排查。6.2 反向代理后面的真实 IP只要前面套了代理应用里拿到的request.getRemoteAddr()就会变成代理的地址所有用户的 IP 都一样风控、限流、审计全废。解决办法是配RemoteIpValve让它从代理传来的头里解析真实地址Valve classNameorg.apache.catalina.valves.RemoteIpValve remoteIpHeaderX-Forwarded-For protocolHeaderX-Forwarded-Proto internalProxies10\.\d\.\d\.\d|192\.168\.\d\.\d /internalProxies这个正则很关键它声明了哪些来源地址是自己人。只有来自这些地址的请求头里的X-Forwarded-For才会被信任否则任何人都能伪造一个头把自己的 IP 改成任意值。这个参数写错的后果比不配还严重——不配只是拿不到真实 IP配错了是把身份伪造的口子敞开。6.3 非 root 运行与文件权限的持续性前面建了tomcat用户但要确认它真的有权限写所有需要写的目录。容易漏掉的是temp、work、logs这三个它们在任何时候都可能被写入包括运行时动态创建的临时文件。另外如果你用了外部docBase映射上传目录那个目录的属主也得是对的而且要注意 Web 进程有写权限就意味着能写代码进去比如上传一个 JSP 文件所以上传目录必须禁止执行脚本——可以在应用层校验扩展名也可以让上传目录单独由代理服务不进 Tomcat。还有一个实际问题将来换人维护用另一个账号登录去查日志发现logs目录进不去。要么把维护账号加进tomcat组要么在日志目录上设setgid让新文件继承组权限。这类小事不定规矩团队里每来一个人就要重新踩一遍。7. 排查实录那些年卡住我的错误7.1 启动不起来先看这三个地方第一个地方是logs/catalina.out的末尾。启动失败的原因九成写在最后几十行里关键是要能认出典型的几种。Address already in use是端口被占用ss -lntp | grep 8080找出占用进程。JAVA_HOME is not defined correctly是环境变量问题别去改server.xml那个文件跟它没关系。NoSuchMethodError或UnsupportedClassVersionError基本就是 JDK 版本不对后者尤其明确会直接告诉你 class 文件版本号和当前 JVM 支持的版本号。第二个地方是conf目录下的配置文件语法。XML 对格式很敏感少一个引号、多一个标签闭合启动就会失败而且报错位置往往指向文件开头而不是真正的错误行。改完server.xml之后用个 XML 校验工具过一遍能省很多时间。第三个地方是权限。用systemctl status看到进程退出码非零但日志里啥也没有很可能是因为进程压根没起来——比如bin/*.sh没有执行权限或者logs目录不可写导致重定向失败。切到tomcat用户手动执行一次catalina.sh run所有问题都会直接打在终端上这是最快的定位方式。7.2 起来了但访问不通服务在跑端口在听浏览器就是连不上这个现象要分层排查。先确认监听地址。ss -lntp的输出里如果显示的是127.0.0.1:8080而不是0.0.0.0:8080或:::8080说明只监听了本机回环外部当然连不上。这通常是因为Connector上配了address127.0.0.1去掉即可。再确认防火墙。这个不用展开ss能看到监听但外部连不上八成是这层拦的。最后确认应用路径。返回 404 说明容器是通的只是路径不对——检查 WAR 包名、Context的path、有没有配ROOT。返回 403 通常是权限或者DefaultServlet的readonly返回 502/503 则要看是不是应用启动失败但容器还活着。7.3 常见问题速查表现象大概率原因处理方向启动报Address already in use端口被其他进程占用ss -lntp定位换端口或停占用进程启动报UnsupportedClassVersionErrorJDK 版本低于 class 编译版本核对 JDK 主版本改JAVA_HOME启动报NoClassDefFoundError: javax/servlet/...Tomcat 10 跑javax老应用降级到 Tomcat 9 或迁移到jakarta页面 500日志有ClassNotFoundException依赖 jar 没打包进WEB-INF/lib检查打包配置补依赖中文参数变乱码编码不一致确认URIEncodingUTF-8与file.encoding日志报Too many open files文件句柄上限太低systemd 单元加LimitNOFILE改了 JSP 不生效work目录有旧编译产物停服务清空work后重启上传目录文件消失删 WAR 触发自动部署清理关闭autoDeploy数据目录独立服务运行一段时间后无响应内存溢出或线程耗尽看是否OutOfMemoryError加堆转储参数应用里拿到的 IP 全是同一个前面有代理未配真实 IP 解析配RemoteIpValve并限定内网段表里最后一行那个问题我印象特别深。当时做登录风控所有用户都被判定成同一个 IP登录次数限制把所有人一起锁了。查了半天应用代码最后一层一层往上翻才发现是代理层没透传跟应用逻辑一点关系没有。从那以后我养成一个习惯任何线上和本地表现不一致的问题先看请求经过了哪些环节再看代码。8. 跑稳之后还能再调的地方8.1 JVM 参数不是随便填的-Xms和-Xmx我一般设成一样大比如都是 1G。原因是如果初始堆和最大堆差距大JVM 会在运行过程中反复扩堆每次扩堆都伴随一次较重的回收表现为服务偶发的卡顿。设成一样堆一开始就到位运行期行为更稳定。-XX:MetaspaceSize和-XX:MaxMetaspaceSize管的是类的元数据区。Tomcat 里频繁热部署、反复加载卸载应用会导致元数据区增长如果不设上限极端情况下会吃光机器内存。设一个 256M 的上限超了就早点暴露问题而不是拖到整机 OOM 才被发现。再补两个排查用的参数-XX:HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath/path/to/dumps。内存溢出的时候自动把堆转储下来事后用分析工具打开就能看到是哪个对象撑爆的。这个参数平时完全无感出事的时候价值极高属于必须提前加、临时加没用的一类配置。8.2 连接器的经验参数线程数那个部分前面说过计算方法。补一点minSpareThreads是保持的最小空闲线程数默认 10。如果流量有明显的高峰低谷把它适当调大比如 50可以让高峰来的时候不用临时创建线程响应更平滑代价是平时多占一点内存。纯内网后台、流量很小的系统默认值就够。connectionTimeout是连接建立后等待请求行的超时设太小会误伤慢网络的正常用户设太大则慢连接会长期占着资源。20 秒是官方默认多数场景合适。真正要注意的是 keep-alive 的超时和最大请求数如果前面有代理代理的连接复用策略和 Tomcat 这边的超时最好对齐否则会周期性出现一批 502——代理认为连接还活着Tomcat 已经把它关了。这种问题很难复现只能靠对齐参数预防。8.3 监控与日志的三个必看项服务跑起来之后至少盯三样东西。一是进程存活交给 systemd 或者监控系统挂了要能告警。二是堆内存使用率用 JVM 自带的管理端口或者采集工具看长期在 80% 以上徘徊就要考虑扩容或者查内存泄漏。三是logs目录里的异常日志量几个业务异常是正常的突然成倍增长一定有问题。日志轮转的规则要提前定至少保留 7 到 15 天具体看磁盘容量。只保留一天的日志出了问题根本没法回溯保留一年的日志磁盘迟早被撑爆。折中方案是按天切割、按大小二次切割、按天数清理三条规则一起用。最后分享一个我在实际操作中的体会Tomcat 这东西装一次只要十分钟配好却需要理解它的每一个默认值为什么是那个值。我前几次装的时候凡是能用默认值的都用默认值跑得也挺好等到流量上来、等到要做安全加固、等到别人接手维护那些默认值一个个都成了需要解释的东西。现在我装完的第一件事不是启动而是把server.xml从头到尾读一遍把每个属性对应到它的实际作用上。这个习惯花不了二十分钟但能省掉后面无数个为什么它突然这样了的夜晚。
阅读完成 · 觉得有帮助?
咨询建站