☰
Tomcat 7.0.108:Servlet 3.0 老项目部署与调优
2026/10/26 9:39:32 网站建设 项目流程

简介:面向Java Web开发与部署学习者的Tomcat 7.0.108完整安装包,适合需要本地搭建Servlet/JSP运行环境、验证旧版本兼容性或进行课程实验的初中级开发者。压缩包内共640个文件,涵盖html、class、java、jsp、jar、xml、properties及bat/sh脚本等类型,其中jar与class构成运行依赖,jsp/html提供页面示例,xml/properties承载配置,bat/sh便于跨平台启停,整体约10.38MB。目录保留标准Tomcat结构,bin、conf、lib、webapps、logs等模块齐全,可直观了解容器组成、部署目录与配置分层,并含war包和示例应用,便于对照学习Servlet容器如何加载与运行Web项目。已有634人学习,适合作为Tomcat 7系列部署练习与旧项目复现的参考包,也可用于排查类加载、端口与日志等常见问题。

1. 解压就能跑的 Tomcat 7.0.108:先弄清它能救哪类老项目

接手一套十年前的后台系统,pom.xml里还写着javax.servlet3.0,服务器上只装了 JDK 7,这时候把tomcat-7.0.108.zip解压出来,往往比升级到 Tomcat 10 再改一遍全项目 import 更省事。这个 zip 是 7.0.x 分支收尾阶段的 Windows 二进制包,解压即用、不写注册表,Servlet 3.0 / JSP 2.2 / EL 2.2 / WebSocket 1.0 全在里面,bin目录还自带注册 Windows 服务的两个 exe。

它不适合新项目,但适合三类人:维护javax.servlet老代码的、要在内网机器上快速起一个容器的、需要复现老版本行为做对比测试的。7.0.x 分支已经停止维护,所以这份资源的价值不在“先进”,而在“对得上”——你的代码、你的 JDK、你的web.xml版本号,它都接得住。下面按选型、参数、编码、排查、多实例的顺序,把它拆成能照着敲的步骤。

2. 版本边界:7.0.108 卡在 Servlet 3.0 这条线,选错就得重写代码

选 Tomcat 版本这件事,本质上不是选容器,是选javax.servlet的 API 版本。很多人栽在“装个新版总没错”这个直觉上,结果 Spring 5 的应用扔进 Tomcat 7,启动就报NoSuchMethodError,因为 Spring 5 要求 Servlet 3.1。所以先把这条分水岭弄清楚,再决定这个 zip 该不该用。

2.1 一张表看清 7/8.5/9/10 的接口分水岭

Tomcat 7 实现的是 Servlet 3.0,这个版本引入了异步 Servlet 和注解式@WebServlet,但没有 3.1 的非阻塞 IO,也没有 3.1 对 WebSocket 握手升级的完整支持。对照下面这张表看,基本能判断手上的项目落在哪一档。

版本ServletJSP最低 JDK包名典型迁移代价
7.0.1083.02.26javax.servlet无(就是老代码的家)
8.5.x3.12.37javax.servlet低,web.xml版本号与少量 API
9.0.x4.02.38javax.servlet中,web.xml4.0、部分监听器行为变化
10.0/10.15.0/6.03.x8/11jakarta.servlet高,全项目 import 与依赖坐标重写

判断项目属于哪一档,别靠猜,看三个地方就够。第一看WEB-INF/web.xml根节点的version属性,写 3.0 的基本可以确定停在 Tomcat 7/8 这条线上;第二看依赖树里有没有spring-web5.x 或spring-boot-starter-web2.x,有的话最低要求 Servlet 3.1,得上 8.5;第三看代码里import javax.servlet还是import jakarta.servlet,后者只能进 Tomcat 10。

还有一个容易被忽略的细节:JDK 版本会反向限制 Tomcat。7.0.108 官方支持 Java 6 及以上,实际跑在 JDK 7 上最稳,JDK 8 也没问题,但 JDK 11 以上会出现 JSP 编译报错(4.2 节会讲怎么绕)。如果你的机器上只有 JDK 17,那这个 zip 只能当“读代码、跑老包”的临时环境,不适合长期挂着。

2.2 zip 包里的七个目录,哪些能动哪些别动

解压出来是apache-tomcat-7.0.108一个目录,里面七个子目录各有分工。搞不清楚就往里塞 jar,是后面类冲突的主要来源,所以这里先把边界划清。

目录作用能不能动
bin启动脚本、bootstrap.jar、tomcat7.exe只放setenv.bat,别改catalina.bat
confserver.xml、web.xml、context.xml、tomcat-users.xml主要工作区,改前先备份
lib容器自身 jar 与公共类库(含tomcat-dbcp.jar、tomcat-jdbc)只加公共驱动,绝不放servlet-api.jar
logs日志输出可清,别删目录
temp临时文件,上传组件常用可清
webapps默认部署目录生产环境要清理自带应用
workJSP 编译产物可随时删,删了访问时重新编译

lib目录的重灾区是servlet-api.jar和jsp-api.jar。这两个包容器自己已经加载了,你从别处拷一份进去,症状是启动没报错但运行时报ClassCastException或者NoSuchMethodError,日志里看不出所以然,属于典型的黑匣子问题。需要放进去的是 JDBC 驱动这类全容器共享的东西;应用私有的依赖,一律放WEB-INF/lib,让应用类加载器自己管,卸载时才能跟着回收。

conf/Catalina/localhost/这个目录默认不存在,需要自己建。它用来给外部目录的应用打一个 context 描述文件,3.3 节会用到,是避免把 war 包塞进webapps的标准做法。

2.3 落地:三分钟把 zip 跑成可访问实例

先别急着配这配那,把最小可运行环境拉起来,确认 JDK 认到了、端口通、首页能出。解压路径不要带空格和中文,Windows 上尤其要注意,C:\Program Files\tomcat这种路径会让部分脚本解析失败。

# 1) 解压到纯英文、无空格的路径 unzip tomcat-7.0.108.zip -d /opt/ mv /opt/apache-tomcat-7.0.108 /opt/tomcat7 # 2) JAVA_HOME 指向 JDK 根目录(不是 jre 子目录),末尾不要带斜杠 export JAVA_HOME=/usr/lib/jvm/java-7-openjdk export CATALINA_HOME=/opt/tomcat7 # 3) 先看版本,能打印 Server version 才说明 JDK 被正确识别 $CATALINA_HOME/bin/catalina.sh version # 4) 前台启动,日志直接打到终端,排查阶段别用 startup.sh $CATALINA_HOME/bin/catalina.sh run

第 3 步是整套流程里最值得养成习惯的一步。catalina.sh version会输出 Tomcat 版本、JVM 版本、CATALINA_HOME、CATALINA_BASE四个关键信息,任何一个不对,后面都是白折腾。很多“启动闪退”的问题,在这一步就能看到 Java 路径是空的。

第 4 步用run而不是startup.sh,是因为run把catalina.out的内容直接输出到当前终端,异常栈、ClassNotFoundException、端口绑定失败全在眼前;startup.sh在后台起进程,日志落在logs/里,排查时多绕一圈。等配置稳定了再换回后台启动。

启动成功的标志是终端最后出现Server startup in XXXXX ms。此时另开一个终端验证:

curl -I http://127.0.0.1:8080/ # 期望看到 HTTP/1.1 200 和一段默认首页的 HTML

Windows 上对应的是bin\startup.bat,双击闪退是新手最常遇到的现象,原因是环境变量没配好。稳妥做法是在bin目录下新建setenv.bat,把JAVA_HOME写死在里面,然后用cmd进到bin目录手动执行,报错信息会留在窗口里。

3. server.xml 与启动参数:让 8080 端口按真实并发来配

默认的server.xml是给开发机用的,maxThreads200、无压缩、无访问日志。直接搬到内网生产环境,第一个问题是并发上来之后请求排队,第二个问题是出了问题连访问记录都查不到。这一章把 Connector 和 JVM 参数拆开讲,都是改一次管很久的配置。

3.1 Connector 参数:maxThreads 不是越大越好

打开conf/server.xml,找到 8080 那个<Connector>,默认就三四个属性。下面这份是能直接用的改法,注释写在参数旁边:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" maxThreads="200" minSpareThreads="20" acceptCount="200" maxKeepAliveRequests="100" URIEncoding="UTF-8" compression="on" compressableMimeType="text/html,text/css,application/javascript,application/json" redirectPort="8443" />

maxThreads是同时处理请求的工作线程上限,Tomcat 7 默认 200。调大之前先想清楚瓶颈在哪:如果每个请求都要查一次数据库,而连接池只有 50 个连接,把线程数调到 500 只会让 450 个线程堵在拿连接上,响应时间反而更长。常见做法是按“DB 连接池上限 × 1.5”来估,剩下的交给acceptCount排队。

acceptCount是等待队列长度,默认 100。它和maxThreads的关系是:线程全忙时新连接进队列,队列满了直接拒绝。所以实际能扛的连接数是两者之和,200 + 200 就是 400,超出的部分客户端会收到连接重置。这个值不是越大越好,队列太长会让超时请求堆积,把acceptCount设到 200 已经够大多数内部系统用。

connectionTimeout单位是毫秒,20000 是 Tomcat 7 的默认值,指连接建立后等待请求行的最长时间。内网接口可以压到 8000,能更快释放僵死连接。

compression="on"只对compressableMimeType里列出的类型生效。小于 1KB 的 JSON 响应压缩后可能更大,真正有收益的是几 KB 以上的列表接口。如果接口返回大量小对象,把application/json从列表里去掉更划算。

URIEncoding="UTF-8"这一项在 Tomcat 8 之后是默认值,在 7.0.108 里必须手写,否则 GET 请求的中文参数会变成乱码,4.1 节展开讲。

redirectPort只在使用security-constraint强制 HTTPS 时才起作用,没配 SSL 的情况下改不改都不影响。

3.2 setenv.bat / setenv.sh:把堆和 PermGen 一次定死

Tomcat 的启动脚本会自动source(Windows 上是call)bin目录下的setenv脚本,这是唯一推荐注入 JVM 参数的地方。直接改catalina.sh的后果是升级或重新解压时被覆盖,血泪经验。

# 文件位置:$CATALINA_BASE/bin/setenv.sh # JDK 7 环境:堆固定 2G,PermGen 固定 512M,OOM 时自动 dump export CATALINA_OPTS="-server -Xms2048m -Xmx2048m \ -XX:PermSize=512m -XX:MaxPermSize=512m \ -XX:+UseConcMarkSweepGC \ -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tomcat7/logs \ -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai"

-Xms和-Xmx设成一样,是为了避免堆在运行期反复伸缩带来的停顿,服务器上内存够就固定住。-XX:PermSize与-XX:MaxPermSize是 JDK 7 及以前的永久代参数,JSP 编译产物、类元数据都放这里,Tomcat 7 上热部署几次就OutOfMemoryError: PermGen space,多半是这块太小。

用CATALINA_OPTS而不是JAVA_OPTS有个实际好处:JAVA_OPTS会被shutdown.sh、version这些辅助命令一起继承,堆参数在只需要发个关闭指令的进程上是浪费;CATALINA_OPTS只作用于主进程。

如果换成 JDK 8,永久代没了,对应改成-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m,其余不变。-XX:+UseConcMarkSweepGC在 JDK 8 上仍然可用,到 JDK 11 之后就别再写了。

Windows 上的写法要小心引号位置:

@echo off rem 文件位置:%CATALINA_HOME%\bin\setenv.bat rem 注意 set "VAR=value" 的引号包住整个赋值,不能写成 set VAR="value" set "JAVA_HOME=C:\Java\jdk1.7.0_80" set "CATALINA_OPTS=-Xms1024m -Xmx1024m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8"

set CATALINA_OPTS="..."这种写法会把引号一起算进变量值,最终传给 JVM 的参数变成带引号的字符串,报错信息是Unrecognized option: ",很难第一眼看出问题。

3.3 部署方式:WAR 扔 webapps 与外部 docBase 两条路

部署有三条路,选哪条取决于你要不要保留历史版本。

第一种,war 包直接扔进webapps,访问路径就是 war 文件名(api.war→/api),ROOT.war覆盖根路径。这种方式最省事,适合测试环境。前提是Host节点上autoDeploy="true"、unpackWARs="true",默认就是开的。

第二种,目录直接放webapps下,跳过解压过程。适合本地调试,改 class 文件不用重新打包。

第三种,外部目录 + context 描述文件,生产环境推荐这条:

<!-- 文件位置:conf/Catalina/localhost/api.xml --> <!-- 文件名 api 就是访问路径,不要把 path 属性写进来 --> <Context docBase="/data/apps/api-1.4.2" reloadable="false" crossContext="false" />

这里有个必须记住的规则:conf/Catalina/localhost/下的文件名决定了 context path,在 xml 里再写path="/api"会被忽略——Tomcat 7 上会出现Setting property 'path' to '/api' did not find a matching property的警告,然后应用挂在你没想到的路径上。想换路径就重命名文件。

reloadable="false"是生产环境的关键。设成true时,容器会定期扫描WEB-INF/classes和WEB-INF/lib的时间戳,一有变化就重载整个应用。重载会创建新的类加载器,旧的如果回收不掉,永久代就被一点点吃掉,这就是很多人“热部署十几次之后必然 OOM”的来源。开发环境开着方便,生产环境一律关掉,改用停机发布。

docBase指向绝对路径时,应用目录不在webapps下,备份和回滚就是换个软链接或改个文件名的事,比反复覆盖 war 包安全得多。

4. 编码、JSP 编译与 JNDI 数据源:三个最容易返工的配置面

前两章解决“跑起来”,这一章解决“跑对”。这三个配置面有个共同点:默认值在开发机上勉强能用,一到真实数据、真实 JDK 版本、真实数据库连接数上就出问题,而且报错信息通常指向错误的位置,容易查偏。

4.1 GET 中文乱码:URIEncoding 与 useBodyEncodingForURI 的分工

Tomcat 7 的Connector默认用 ISO-8859-1 解 URL 里的查询串,这是 Servlet 规范里比较早期的约定,Tomcat 8 才改成 UTF-8 默认。所以同样的代码在 7.0.108 上,?keyword=测试到后端就变成了问号或者乱码字符,而 POST 表单提交的中文可能完全正常——因为请求体的编码是另一条路径。

两种解法,区别要弄清楚:

URIEncoding="UTF-8"写在Connector上,全局生效,直接决定 URL 查询串的解码字符集。这是首选,配上之后所有应用都受益。

useBodyEncodingForURI="true"的意思是“查询串用请求体声明的编码来解”,只有在应用里已经调用了request.setCharacterEncoding("UTF-8")时才有意义。如果代码里没有这行调用,这个属性等于没配。

两个都配时,useBodyEncodingForURI优先。我一般只配URIEncoding,避免两套逻辑打架。

POST 请求体的编码要靠过滤器解决。Tomcat 自带一个,配在conf/web.xml里全局生效,比每个应用各写一个CharacterEncodingFilter更省事:

<!-- 位置:conf/web.xml,放在所有 servlet 定义之前 --> <filter> <filter-name>SetCharacterEncoding</filter-name> <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>ignore</param-name> <param-value>false</param-value> </init-param> </filter> <filter-mapping> <filter-name>SetCharacterEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

ignore=false表示如果应用自己已经设过编码,就尊重应用的选择,不再覆盖。这一点很重要:有些老框架在DispatcherServlet里会自己设一次,全局过滤器强覆盖会把它们的设置冲掉,反而搞出新的乱码。

顺序上还有一个坑:setCharacterEncoding必须在第一次getParameter之前调用才有效,一旦参数被解析过,再设编码就没用了。所以过滤器要映射到/*,并且排在业务过滤器前面。

4.2 JSP 编译:compilerSourceVM 与 work 目录的清理时机

7.0.108 的conf/web.xml里,Jasper 的compilerSourceVM默认是 1.6。在 JDK 7、JDK 8 上这没问题,因为这两个 JDK 的 javac 还支持-source 6。但如果你把这份 zip 拿到 JDK 11 以上的机器上跑,第一次访问 JSP 就会 500,日志里是:

org.apache.jasper.JasperException: Unable to compile class for JSP Source option 6 is no longer supported. Use 7 or later.

原因很直接:JDK 12 之后彻底移除了对 source/target 6 的支持。解决办法是改conf/web.xml里 JSP Servlet 的初始化参数:

<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>compilerSourceVM</param-name> <param-value>1.7</param-value> </init-param> <init-param> <param-name>compilerTargetVM</param-name> <param-value>1.7</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>

1.7这个值要跟你实际运行的 JDK 匹配。跑在 JDK 8 上写1.8也行,跑在 JDK 11 上就写11。写成比 JDK 高的版本,报错会变成invalid target release,方向反过来而已。

JSP 编译产物落在work/Catalina/localhost/<应用名>/org/apache/jsp/下,.java和.class都在里面。判断一个 500 是不是编译问题,最快的办法是直接去这个目录看有没有对应的.java文件:有文件说明编译过了,问题在运行期;没文件说明编译就没过。

改 JSP 不生效、或者改了web.xml里的 JSP 配置不生效,第一反应应该是删work目录。这个目录可以整个删掉,下次访问时按需重建,没有数据风险。清理时机选在停机后,运行中删可能出现“类文件正在被读取”的异常。

4.3 JNDI 数据源:context.xml 里那五个必填参数

Tomcat 7 自带 DBCP(lib/tomcat-dbcp.jar),不需要额外引commons-dbcp,这一点很多人不知道,结果往lib里塞了一份不兼容的版本,启动报NoSuchMethodError。配数据源的标准位置是应用自己的META-INF/context.xml,或者conf/Catalina/localhost/<应用名>.xml。

<Context> <Resource name="jdbc/orderDS" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://10.0.0.12:3306/order?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true" username="app_rw" password="change_me" maxActive="50" maxIdle="20" minIdle="5" maxWait="10000" validationQuery="SELECT 1" testOnBorrow="true" testWhileIdle="true" timeBetweenEvictionRunsMillis="60000" /> </Context>

几个参数值得单独说。maxActive="50"是连接池上限,一定要小于数据库端的max_connections减去其他应用的占用,不然并发高峰时会出现“应用在等连接、数据库在拒绝连接”的死锁式等待。maxWait="10000"是拿不到连接时的最长等待,10 秒后抛异常,比无限等待好排查得多。

validationQuery必须是对应数据库能执行的轻量语句。MySQL 用SELECT 1,Oracle 要用SELECT 1 FROM DUAL,写错了启动不报错,第一次取连接时才炸。testOnBorrow="true"每次借出都校验,会多一点开销但能挡住被数据库端断掉的空闲连接;testWhileIdle配合timeBetweenEvictionRunsMillis是更轻量的做法。

代码侧取值只有一行:

// 前缀 java:comp/env 不能省,省了会报 NameNotFoundException Context initCtx = new InitialContext(); DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/orderDS"); try (Connection conn = ds.getConnection()) { // 业务代码 }

资源名jdbc/orderDS和 lookup 里的路径必须完全一致,大小写敏感。应用里的web.xml建议加一段<resource-ref>声明,虽然不是强制,但能让容器在启动阶段就做一次校验,比运行到一半才发现配错强。

5. 避坑排查:五条从日志里抠出来的血泪记录

这个版本的坑比较集中,而且大多不是 Tomcat 本身的问题,是环境、路径、版本搭配的问题。下面五条按“现象 → 原因 → 解决”写,都是实际排查过的路径,照着顺序走能省不少时间。

5.1 启动阶段:进程根本起不来

现象一:双击startup.bat,黑窗口一闪就没了。

原因:JAVA_HOME没设,或者指向了jre子目录而不是 JDK 根目录,或者路径末尾多了个反斜杠,catalina.bat拼出来的 java 命令路径非法,脚本执行完直接退出,窗口跟着关掉。

解决:用cmd进到bin目录手动跑startup.bat,报错会留在窗口里;再不行跑catalina.bat run看完整输出。确认JAVA_HOME的写法是C:\Java\jdk1.7.0_80这种形式,echo %JAVA_HOME%打出来末尾不能有\。最稳的做法是把它写进bin\setenv.bat,不依赖系统环境变量。

现象二:日志里java.net.BindException: Address already in use: JVM_Bind,进程起来又退出。

原因:端口被占。除了 8080,server.xml里还有Server port="8005"(关停端口)和 AJP 的 8009,三个都可能冲突。同一个机器上跑两个 Tomcat 实例但没改端口,是最常见的场景。

解决:netstat -ano | findstr :8080找到 PID,tasklist | findstr <PID>确认是哪个进程。如果是自己之前启动的实例没关干净,用shutdown.bat或直接 kill;确认要共存就改端口,注意三个端口都要改,只改 8080 会出现“启动成功但关不掉”的怪现象。

现象三:解压到含中文或空格的路径,启动报The system cannot find the file specified,或者脚本里刷一片“命令语法不正确”。

原因:catalina.bat里有若干处变量展开没加引号,路径带空格时被拆成多个参数。

解决:换到D:\tomcat7这种纯英文无空格路径。这个不是配置能绕过去的,只能换路径。

5.2 运行阶段:起来了但不对

现象四:应用明明放进了webapps,访问却 404,日志里干干净净。

原因:三种可能。war 文件名和你想的 context path 不一致;Host节点的autoDeploy被关掉了;用了外部docBase但 xml 文件名和访问路径对不上。

解决:先看webapps下有没有生成同名目录(unpackWARs="true"时应该有),没有就是没解压;再看logs/catalina.<日期>.log里有没有Deploying web application archive这一行,没有说明自动部署没触发;最后检查conf/Catalina/localhost/下的文件名,那个名字就是路径,改内容没用。

现象五:热部署几次之后OutOfMemoryError: PermGen space,重启就好,再热部署又炸。

原因:每次重载都会创建新的 Webapp 类加载器,旧的要等它加载的所有类、线程、ThreadLocal、JDBC 驱动注册都被释放才能回收。老应用里几乎必然有一两处没注销的定时器或者DriverManager注册,旧类加载器被引用住,永久代只增不减。调大MaxPermSize只是把爆炸时间往后推。

解决:生产环境把reloadable设成false,改成停机发布;开发环境也要控制重载次数,一天重启一次容器。真要定位是谁泄漏,用jmap -permstat <pid>看类加载器数量和加载的类,再配合jmap -histo看哪个类的实例数在涨。这个排查比较费时间,多数情况下直接关掉热部署更划算。

另外还有一类不算异常但很烦的情况:JSP 页面第一次访问特别慢。这是正常的编译过程,work目录里生成.java和.class之后就快了。如果希望启动阶段就编译完,可以在应用里配<load-on-startup>,或者发布后主动跑一遍页面预热。

6. 多实例与生产收尾:用 CATALINA_BASE 把一份 zip 变成四套环境

一份程序目录跑多套环境,是这套资源最实用的一个用法。Tomcat 从 6 开始就把“程序”和“配置”拆成了CATALINA_HOME和CATALINA_BASE两个变量:前者指向解压出来的那份程序(bin、lib),后者指向实例自己的conf、logs、webapps、work、temp。共享一份bin和lib,四个环境各自独立,升级时只换CATALINA_HOME,配置不用动。

# 建一套独立实例:只复制 conf,其余目录建空 BASE=/data/instances/order-test mkdir -p $BASE/{conf,logs,temp,webapps,work} cp -r /opt/tomcat7/conf/* $BASE/conf/ # 改这套实例的 server.xml:8080 / 8005 / 8009 三个端口都要错开 sed -i 's/port="8080"/port="18080"/; s/port="8005"/port="18005"/' $BASE/conf/server.xml # 启动时指定 BASE,HOME 保持不变 export CATALINA_HOME=/opt/tomcat7 export CATALINA_BASE=$BASE $CATALINA_HOME/bin/catalina.sh start

四套环境就是四份CATALINA_BASE,端口按 18080、28080、38080 递增,日志各写各的。setenv.sh也放在各自的bin下——注意是$CATALINA_BASE/bin/setenv.sh,不是$CATALINA_HOME/bin/,启动脚本会优先读 BASE 下的那份,这样不同实例可以配不同的堆大小,测试环境给 1G,生产给 4G,互不影响。

收尾有三件事,做完这套环境才算能交出去。第一,删掉webapps下的manager、host-manager、docs、examples四个自带应用,管理端在tomcat-users.xml没配角色的情况下虽然访问不了,但留着就是多余的攻击面。第二,在Host节点里打开访问日志,不然出了问题只能靠猜:

<!-- 位置:conf/server.xml 的 <Host> 内部 --> <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="access." suffix=".log" pattern="%h %l %u %t &quot;%r&quot; %s %b %D" />

%D是处理耗时(毫秒),排查慢接口时比什么都直接;%b是响应字节数,配合%s状态码能快速定位是哪个接口在返回 500。

第三,验证停机。catalina.sh stop发出的是往 8005 端口写一个明文SHUTDOWN字符串,这意味着一件事:这个端口绝不能暴露在不可信网络上。停完之后用jps确认进程真的没了,有时候应用里有非守护线程卡住,stop会超时,进程残留但端口已经释放,下次启动就撞端口。养成停完看一眼jps的习惯,比事后排查省事得多。

最后说个我自己的教训。早期部署这套东西时,我觉得“解压、启动、能访问”就算完事,setenv不配,日志不配,访问日志更是从来不看。结果一次线上接口超时,翻了两个小时的应用日志才想起来容器层根本没记录,连请求进没进来都不知道。从那以后我每次解压完一份新的 Tomcat,都会强制走一遍:catalina.sh version确认 JDK、setenv定死堆参数、打开 AccessLogValve、删掉自带管理应用,四步走完再动业务。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询