ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

apache-tomcat-7.0.108-windows-x64 部署实战:启动、服务化与应用发布

apache-tomcat-7.0.108-windows-x64 部署实战:启动、服务化与应用发布

简介:Apache Tomcat 7.0.108 的 64 位 Windows 发行包,定位于 Java Web 开发、学习与测试场景,适合需要本地运行 Servlet/JSP 应用、验证 Java EE 6 兼容性或搭建轻量 Web 容器的开发者与运维人员。压缩包为 rar 格式,共 640 个文件、约 10.1MB,包含 228 个 html 页面、106 个 class、78 个 java 源文件、58 个 jsp、30 个 jar,以及 xml、properties、bat、sh 等配置与启动脚本,覆盖文档、示例、依赖库和跨平台管理脚本。目录结构保留 conf、webapps、logs、temp、work、bin 等标准模块,便于按配置、部署、日志和编译产物分类查阅;对希望研究容器内部组成、类加载与 JSP 编译产物、管理控制台和 WAR 部署机制的读者,也提供了较完整的目录样本与依赖文件。已有 227 人学习下载,可作为理解 Tomcat 7.0 系列容器组成、Servlet 3.0 与 JSP 2.2 运行机制及 Windows 64 位环境部署实践的完整样本。

1. 一个三十兆的 zip 为什么还有人在下载:apache-tomcat-7.0.108-windows-x64 到底是什么

上周又碰到一次:某公司内网那台跑着老 ERP 的 Windows 服务器要重装,翻遍共享盘只找到一个压缩包,名字是 apache-tomcat-7.0.108-windows-x64。这个文件名拆成三段看就清楚了——apache-tomcat 是容器本体,7.0.108 是 7.0.x 这条分支上最后一个补丁版本,windows-x64 说明它不是通用包,而是额外带了 Windows 专用的服务宿主程序和 64 位本地库,能直接把 Tomcat 注册成系统服务,也能挂上 APR 那一套本地连接器。它解决的问题很具体:一批只能在 Servlet 3.0 时代行为下运行的老系统,必须在 Windows 机器上交付,运维又只有 Windows 经验。新项目别往这儿凑,7.0.108 之后这条线不再有补丁,这一点要先认下来。

2. 从解压到首页能打开:apache-tomcat-7.0.108-windows-x64 的最小启动链路

拿到这个包,最容易犯的错不是配置写错,而是解压的位置和 JDK 选错了。我见过太多人把包解到「桌面\新建文件夹 (2)」,然后双击 startup.bat 一闪而过,回头就说 Tomcat 有问题。这一章把从解压到浏览器出猫页面的最短路径走一遍,每一步都给出可对照的结果。

2.1 解压位置与目录结构:为什么不要放在 Program Files

先把包解到一个没有空格、没有中文、路径尽量短的目录,比如D:\apps\tomcat7。原因不是玄学:Tomcat 的catalina.bat、service.bat里有大量未加引号的路径拼接,遇到空格会被拆成两个参数;放在Program Files下还会撞上 UAC 虚拟化,服务账户和当前登录用户看到的文件根本不是同一份,改完配置重启发现没生效,排查起来堪比黑匣子。

解压后先认目录,这个包和通用 zip 的差别主要在 bin 下:

路径内容需要你动手的地方
bin\startup.bat、catalina.bat、service.bat、tomcat7.exe、tomcat7w.exe、tomcat-juli.jar、64 位本地库 dll新建setenv.bat;服务相关全靠这里
conf\server.xml、web.xml、context.xml、tomcat-users.xml、logging.properties改端口、编码、日志级别
lib\servlet-api.jar、jsp-api.jar、jasper.jar、catalina.jar、tomcat-coyote.jar等放数据库驱动等公共依赖
webapps\ROOT、docs、examples、manager、host-manager生产环境删掉一半
logs\启动后生成catalina.YYYY-MM-DD.log、localhost.YYYY-MM-DD.log排查入口
temp\、work\编译后的 JSP、临时文件可清空,不要手改

tomcat7.exe和tomcat7w.exe是同一套服务宿主程序的两个入口:前者供命令行和脚本调用,后者是带界面的配置器。这个 x64 包里的这两个 exe 是 64 位的,配套的 dll 也是 64 位的,所以后面选 JDK 时必须选 64 位版本,32 位 JVM 加载 64 位宿主程序会直接失败,报错信息还很不友好。

提示:CATALINA_HOME是安装目录,CATALINA_BASE是运行实例目录。单实例可以两个指向同一个路径;将来要在一台机器上跑多个实例,把conf、logs、temp、work、webapps复制一份,CATALINA_BASE指过去即可,bin 和 lib 共用。

2.2 JAVA_HOME 与第一次启动:三行验证法

Tomcat 7 的编译目标是 Java 6 起步,但 2024 年之后还在维护的老系统基本都跑在 JDK 8 上,我这里按 JDK 8 的 64 位版本举例。先确认JAVA_HOME指向 JDK 根目录而不是bin,并且路径里没有空格:

:: 在 cmd 里执行,确认输出的是 JDK 根目录,且末尾不要带反斜杠和分号 echo %JAVA_HOME% "%JAVA_HOME%\bin\java" -version

-version的输出里要能看到64-Bit Server VM字样。如果显示的是Client VM或者根本没这行,说明JAVA_HOME指到了 32 位 JRE 上,先改环境变量再往下走。

接下来不要急着双击startup.bat,先自己在bin目录下建一个setenv.bat。这个文件官方包不带,但catalina.bat在启动时会主动去调它,是唯一不用改官方脚本就能注入 JVM 参数的口子:

:: D:\apps\tomcat7\bin\setenv.bat set "JAVA_HOME=C:\Java\jdk1.8.0_202" set "CATALINA_HOME=D:\apps\tomcat7" set "CATALINA_BASE=D:\apps\tomcat7" :: 堆大小按机器内存给,老系统 1G 起步够用;编码和时区必须显式指定 set "JAVA_OPTS=-server -Xms1024m -Xmx1024m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -Duser.timezone=GMT+08:00"

几个参数说明:-Xms与-Xmx设成一样大是为了避免运行期堆反复扩容带来的抖动;MetaspaceSize这两个参数在 JDK 8 上才有意义,如果这台机器只能用 JDK 7,换成-XX:PermSize与-XX:MaxPermSize,写错了 JVM 会直接拒绝启动;-Dfile.encoding=UTF-8影响FileWriter、OutputStreamWriter这类默认编码,中文日志乱码十有八九栽在这儿。

然后前台启动,看控制台输出而不是看窗口闪没闪:

cd /d D:\apps\tomcat7\bin catalina.bat run

用run而不是startup.bat,是因为run把日志打在同一个窗口里,异常能直接看到;startup.bat会另开一个窗口,出错后窗口一闪而过,什么也留不下。正常时最后几行会出现类似Server startup in 3000 ms的字样,中间夹着Deploying web application directory若干条。此时另开一个 cmd 验证端口:

netstat -ano | findstr ":8080" curl -I http://127.0.0.1:8080/

netstat输出里应该有LISTENING状态的一行,最后一列是 PID;curl返回HTTP/1.1 200或302就说明连接器通了。这一步跑通再去做服务注册,否则服务装好了起不来,还得回头验一遍。

2.3 server.xml 里必须动手的三个端口和编码参数

conf\server.xml里开头那个Server元素管的是关停端口,往下是Connector。默认配置有三个坑:关停端口对外开放、8080 容易被同机其他程序占用、HTTP 连接器默认用 ISO-8859-1 解 URL。

<!-- conf\server.xml 片段,只改这几个属性,其余保持原样 --> <Server port="8005" shutdown="SHUTDOWN"> <!-- 生产机上把 port 改成 -1 更省心,等于彻底关掉远程关停通道 --> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" maxThreads="400" acceptCount="200" URIEncoding="UTF-8" useBodyEncodingForURI="false" compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/css,application/json,application/javascript" redirectPort="8443" /> <!-- 不用 IIS 或 Apache 前置就把这段整段注释掉,少一个对外端口 --> <!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> --> </Service> </Server>

URIEncoding="UTF-8"是最常被忽略的一项。Tomcat 7 的 HTTP 连接器如果不写这个属性,查询串按 ISO-8859-1 解码,GET /search?q=中文到后端就成了乱码;而 POST 体的编码由request.setCharacterEncoding或者useBodyEncodingForURI决定,两者默认都不管,所以中文项目里这两个属性基本是必配。

maxThreads和acceptCount是配套的:前者是同时处理请求的线程上限,后者是排不上队时进内核等待队列的最大长度,超过acceptCount的请求直接被拒。老系统里数据库连接池通常只有 20 到 50 个连接,把maxThreads调到 800 只会让线程全堵在拿连接上,反而拖长响应时间,400 是这类系统比较安全的起点,压测后再上下调。

connectionTimeout单位是毫秒,20000 意味着一个空闲连接最多挂 20 秒。文件上传或报表导出这类长请求如果超过这个值,页面会先收到超时,而服务端还在跑,用户点第二次就变成两个任务并发——这种问题在老 ERP 里非常典型,遇到大文件上传的模块,单独给那个 Connector 或者给那个应用加长超时,比全局调大更稳妥。

改完server.xml必须重启才生效,reloadable和热部署都管不到它。重启前先备份一份server.xml.bak,这是我们给自己留的后悔药;改动一行、重启一次、看一眼日志,比一次改十行然后对着SEVERE猜哪行错了要快得多。

3. 注册成 Windows 服务:service.bat 与 tomcat7.exe 的参数怎么落到注册表

把 Tomcat 挂成服务,是为了解决三件事:开机自启、脱离登录会话运行、能被监控系统按服务名拉起。apache-tomcat-7.0.108-windows-x64这个包之所以存在,很大程度就是因为它带了服务宿主程序——通用 zip 里没有这些 exe,只能靠第三方包装或者计划任务凑合。

3.1 service.bat install 到底做了什么

bin\service.bat本身不干活,它只是把参数拼成命令行,去调同目录的tomcat7.exe。核心动作是往注册表里写一个Procrun 2.0节点,记录 JVM 路径、启动类、堆大小、日志重定向路径,然后在系统服务表里注册一个服务项。

:: 用管理员身份打开 cmd,必须管理员,否则写注册表会失败 cd /d D:\apps\tomcat7\bin service.bat install Tomcat7_ERP

第一个参数是服务名(系统里唯一,建议带端口或应用名后缀,避免同机多个实例打架),第二个可选参数是显示名称,不写就默认跟服务名一致。执行成功的标志是输出里出现The service 'Tomcat7_ERP' has been installed。紧接着验证:

sc query Tomcat7_ERP :: STATE 应该是 STOPPED,START_TYPE 应该是 DEMAND_START sc start Tomcat7_ERP

sc start之后如果立刻报错误 1067: 进程意外终止,别在服务面板里反复点启动,那时候什么信息都拿不到。正确做法是先sc stop,回到bin目录用catalina.bat run前台跑一遍,绝大多数 1067 都是 JVM 参数写错、JAVA_HOME指错、或者端口被占用,前台一跑异常立刻就现形了。

还有一个隐藏问题:setenv.bat里那些JAVA_OPTS,服务方式启动时不一定生效。service.bat在安装那一刻会读取环境变量并把它固化进注册表,之后再改setenv.bat,服务读到的还是老值。所以要么在安装前就把setenv.bat定稿,要么改用下一节的方式直接改服务参数。

3.2 内存、编码、时区:用 tomcat7.exe 直接写服务参数

比起反复卸载重装,我更倾向用tomcat7.exe的//US(update service)直接改注册表里的参数,改完重启服务即可,不用重装。

cd /d D:\apps\tomcat7\bin :: 先停服务 net stop Tomcat7_ERP :: 更新堆大小(单位 MB)、线程栈、启动类型、描述 tomcat7.exe //US//Tomcat7_ERP --JvmMs=1024 --JvmMx=1024 --JvmSS=1024 ^ --Startup=auto --Description="ERP 应用容器 8080" ^ --JvmOptions="-Dfile.encoding=UTF-8;-Duser.timezone=GMT+08:00;-XX:MetaspaceSize=256m;-XX:MaxMetaspaceSize=512m" net start Tomcat7_ERP

参数含义逐个说清:--JvmMs对应初始堆,--JvmMx对应最大堆,两个都以 MB 为单位,写成1024就是 1G,写成1024m反而可能被当成非法值;--JvmSS是线程栈大小,单位 KB,默认 1024 在 Windows x64 上通常够用,如果应用里有深递归或者大量本地方法调用,可以考虑调到 2048,但每个线程都要占这么多虚拟内存,几百个线程时不是小数目;--Startup=auto让服务随系统启动,demand是手动;--JvmOptions里的多个参数用分号分隔,这是 Procrun 的语法,用空格会被截断——这一条是血泪经验,我第一次写的时候用了空格,结果只有第一个参数生效,后面全丢。

图形化的做法是运行tomcat7w.exe //ES//Tomcat7_ERP,弹出的窗口里有 General、Logging、Java、Startup、Shutdown 五个页签,Java 页里能直接填 Initial memory pool 和 Maximum memory pool,效果和上面的命令完全一样,只是写进注册表的方式不同。运维交接给不熟命令行的人时,用图形界面更省事;做自动化部署脚本时,用命令行。

3.3 服务账户、日志重定向与开机自启验证

默认情况下服务以本地系统账户运行,这在纯本机部署里没问题,但两种场景会翻车:应用要读写网络共享目录(UNC 路径),或者要访问映射到某个登录用户的盘符。系统账户看到的网络身份是机器身份,映射盘根本不存在,日志里表现成「找不到路径」。解决办法是在服务属性里指定一个有权限的域账户或本地账户:

:: 指定服务登录账户,password 部分按实际填写 sc config Tomcat7_ERP obj= ".\svc_tomcat" password= "********"

注意obj=和password=后面那个空格是必须的,这是sc命令的语法怪癖,少了空格会报参数错误。

日志这块,服务方式运行时控制台输出没有窗口可去,默认会被丢弃,只留下logs\catalina.YYYY-MM-DD.log。如果排查启动期问题需要完整的标准输出和标准错误,把它们重定向到文件:

tomcat7.exe //US//Tomcat7_ERP --StdOutput="D:\apps\tomcat7\logs\stdout.log" ^ --StdError="D:\apps\tomcat7\logs\stderr.log" ^ --LogPath="D:\apps\tomcat7\logs" --LogPrefix="tomcat7-service" ^ --LogLevel="Info"

--LogPath是服务宿主程序自己的日志目录,和logs\catalina.*.log不是一回事:前者记录服务启停过程、JVM 拉起失败的原因,后者是应用容器日志。启动失败时先看前者,因为那时候 JVM 可能根本没起来,catalina日志里什么都不会有。

最后做一次完整验收,别只看服务是不是绿的:重启机器,等系统起来后执行sc query Tomcat7_ERP看是否RUNNING,再curl一次首页,再type logs\catalina.%date:~0,10%.log之类的看一眼当天日志有没有SEVERE。三步都过,服务化才算完成。

4. 应用怎么放:appBase、docBase 与 Context 的优先级

老系统交付时最常吵的一个问题是:WAR 到底该扔进webapps还是放外面?两种都能跑,差别在发布流程和回滚成本。理解这三层配置谁覆盖谁,比记住某个具体写法有用。

4.1 appBase、docBase、Context 谁覆盖谁

server.xml里的Host元素有一个appBase属性,默认值webapps,它定义了「自动部署扫描的目录」。Tomcat 启动时会扫这个目录下的 WAR 包和已解压目录,为每个生成一个 Context。而conf\Catalina\localhost\xxx.xml里的 Context 是「显式声明」,优先级高于 appBase 扫描——一旦你在localhost目录下给某个路径写了 xml,同名 appBase 目录就会被忽略。

配置位置路径来源是否支持热部署适合场景
webapps\erp.war文件名是(autoDeploy开启时)开发、测试环境
webapps\erp\已解压目录目录名是需要改配置文件、静态资源
conf\Catalina\localhost\erp.xml+ 外部docBasexml 文件名 +docBase受reloadable控制生产环境,发布不动容器目录

还有一个常被问到的点:写在localhost\erp.xml里的<Context path="/erp">中,path属性是无效的,实际访问路径由文件名决定。erp.xml就是/erp,ROOT.xml就是根路径。写path不会报错,但会误导后来接手的人,我一般直接不写。

4.2 外部目录部署:一个完整的 erp.xml 长什么样

把应用放在D:\apps\erp-1.4.2,容器放在D:\apps\tomcat7,两者物理隔离,回滚时只换软链接或改docBase指向,容器一行不动。

<!-- D:\apps\tomcat7\conf\Catalina\localhost\erp.xml --> <Context docBase="D:/apps/erp-1.4.2" reloadable="false" crossContext="false"> <!-- 数据源交给容器管理,应用里用 java:comp/env/jdbc/erp 取,改连接串不用重新打包 --> <Resource name="jdbc/erp" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://10.0.0.21:3306/erp?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true" username="erp_rw" password="******" maxActive="50" maxIdle="10" minIdle="5" maxWait="10000" validationQuery="SELECT 1" testOnBorrow="true" removeAbandoned="true" removeAbandonedTimeout="300" /> <!-- 环境标识,代码里通过 JNDI 或 System.getProperty 读,避免打多套包 --> <Environment name="app.env" value="prod" type="java.lang.String" override="false"/> <Parameter name="upload.root" value="D:/data/erp-upload" override="false"/> </Context>

几个关键点:docBase用正斜杠,Windows 上 Tomcat 对反斜杠的转义处理不一致,正斜杠永远安全;reloadable="false"是生产环境的铁律,设成true后容器会轮询WEB-INF/classes和lib下所有 class 文件的时间戳,几百个类就是持续的后台 IO,而且一旦触发重载,正在处理的请求会被打断;removeAbandoned那一组参数是防止代码里连接没关导致连接池耗尽,300 秒回收一次,属于兜底而不是解决方案。

数据库驱动 jar 要放在容器的lib目录而不是应用的WEB-INF\lib,否则容器启动时创建数据源会找不到类,报ClassNotFoundException,而且这个错发生在应用部署阶段,日志里只有一行SEVERE,很容易被忽略。放容器lib的好处是多个应用共用一份,坏处是升级驱动要重启整个容器,权衡一下老系统的规模,一般选前者。

4.3 关掉自动扫描:把启动时间从三分钟压到四十秒

老系统的WEB-INF\lib里动辄上百个 jar,Tomcat 7 启动时默认会对每个 jar 做 TLD 扫描和可插拔组件扫描,这一步在机械盘或者杀毒软件实时扫描开着的情况下慢得离谱。7.0.108支持JarScanFilter,可以按文件名跳过。

<Context docBase="D:/apps/erp-1.4.2" reloadable="false"> <JarScanner scanClassPath="false"> <!-- 按实际 jar 名写,不要用 *.jar 一把梭 --> <JarScanFilter tldSkip="commons-*.jar,spring-*.jar,mybatis-*.jar,log4j-*.jar,poi-*.jar" pluggabilitySkip="commons-*.jar,spring-*.jar,mybatis-*.jar"/> </JarScanner> </Context>

scanClassPath="false"表示不去扫CLASSPATH环境变量里的路径,这在 Windows 上尤其值得关,因为很多机器上CLASSPATH是历史遗留的一长串。tldSkip跳过的是 JSP 标签库扫描——如果你的 JSP 页面用了某个 jar 里的自定义标签,把那个 jar 从 skip 列表里去掉,否则页面会报标签未定义;pluggabilitySkip跳过的是 ServletContainerInitializer 扫描,Spring 的SpringServletContainerInitializer就靠这个机制注册DispatcherServlet,把spring-web*.jar跳过会导致 Spring MVC 完全不通。

注意:这两个属性只影响启动扫描,不影响类加载。改完一定要重启后访问一遍核心页面,尤其是带自定义标签的 JSP 和 Spring MVC 的入口,跳过扫描导致的故障是「启动很快但页面 500」,比慢更难查。

热部署的边界也要说清:autoDeploy="true"只对appBase下的 WAR 和目录生效,外部docBase的 Context 不在其列。也就是说,4.2 那种部署方式改代码后必须重启容器,这是用它换来的稳定性代价。如果开发阶段确实需要改完就生效,在测试环境用webapps部署并开reloadable,生产环境关掉,两套配置分开维护。

5. 避坑与排查:Tomcat 7.0.108 在 Windows 上最容易翻车的 5 个点

下面这五条都是我在真实交付里反复遇到的,按「现象 → 原因 → 解决」写,遇到问题时可以直接对照。

第一条:双击 startup.bat 窗口一闪就没了,什么日志都没有。现象是黑窗口出现不到一秒消失,logs目录里也没新文件。原因是JAVA_HOME没配、配到了 JRE 而不是 JDK、或者配的路径里带空格被拆成两段,catalina.bat在解析阶段就退出了。解决方法是先在 cmd 里echo %JAVA_HOME%并执行"%JAVA_HOME%\bin\java" -version确认,然后改用catalina.bat run启动——只要 JVM 能被拉起,异常就会打在屏幕上。如果连run都一闪而过,在bin目录下开个 cmd 手动执行catalina.bat run,别双击。

第二条:服务启动报「错误 1067: 进程意外终止」。现象是服务面板状态从「正在启动」跳到失败,事件查看器里只有一句进程终止。原因通常是三类:堆参数超出物理内存(-Xmx2048m配在 2G 内存的机器上)、java是 32 位(寻址上限摆在那儿,堆设到 2G 直接起不来)、或者端口被别的进程占了。解决顺序是:先netstat -ano | findstr ":8080"和:8005看占用,再sc qc确认服务的可执行路径对不对,然后用catalina.bat run前台复现。服务宿主程序自己的日志(--LogPath指定那个目录)里往往有 JVM 拉起的失败原因,比容器日志更靠前。

第三条:页面能打开,但搜索框输中文查不出结果,日志里 SQL 参数是乱码。现象是 GET 请求的参数解码错误,POST 有时正常有时不正常。原因有三层叠加:Connector没写URIEncoding="UTF-8",Tomcat 7 的 HTTP 连接器默认按 ISO-8859-1 解 URL;应用没调request.setCharacterEncoding("UTF-8")处理请求体;JVM 没指定-Dfile.encoding,写日志和读配置文件都按系统默认代码页。解决方法是三处一起改:server.xml加URIEncoding、在字符编码过滤器里统一设请求体编码、服务参数里加-Dfile.encoding=UTF-8。另外在 Windows 控制台直接看日志时,先chcp 65001切到 UTF-8 代码页,否则日志文件是对的、屏幕上还是花屏。

第四条:跑一段时间后报 OutOfMemoryError,或者「unable to create new native thread」。前者多是MetaspaceSize不够导致类元数据撑爆,常见于频繁热部署或者大量动态代理的框架;后者是线程数超过系统限制,Windows 上每个线程默认占 1MB 栈,maxThreads=800加-Xss=2048k,光栈就要 1.6G 虚拟内存,再加上堆,很容易撞顶。解决方法是先用jstack导出线程快照看线程都在干什么,确认是线程泄漏还是配置过大,然后把maxThreads调到和数据库连接池匹配的量级,-Xss保持 1024k 不要随手加大。堆外内存的问题(Direct buffer memory)则要看是否用了 NIO 连接器和大文件传输,那属于另一条线。

第五条:服务方式启动正常,但logs目录不生成文件,或者日志停在昨天。现象是应用在跑,日志文件时间戳不更新。原因是服务账户对logs目录没有写权限——把 Tomcat 放到某个用户目录下、或者用受限账户跑服务时特别常见;另一种情况是conf\logging.properties里FileHandler的directory或prefix被改过,实际写到了别处。解决方法是右键目录看「安全」页里服务账户有没有「写入」,没有就补上;顺便用--LogPath把宿主日志和容器日志分开放,出问题时按时间顺序对一遍就能定位到是哪一层没写出来。

6. 让 7.0.108 多撑两年:加固清单、验证脚本和升级路线的判断点

这个版本已经不再有安全补丁,指望它「安全」不现实,能做的只是把暴露面收到最小、把故障发现时间压短。我一般上线前过一遍三件事。

第一件是删和关。webapps下的docs、examples整目录删掉,manager和host-manager如果不用也删;Server的port改成-1关掉关停通道;tomcat-users.xml里只留必要的角色,如果确实要用 manager 做发布,加一层来源限制:

<!-- conf\Catalina\localhost\manager.xml --> <Context docBase="${catalina.home}/webapps/manager" antiResourceLocking="false"> <Valve className="org.apache.catalina.valves.RemoteAddrValve" allow="127\.0\.0\.1" /> </Context>

RemoteAddrValve的allow是正则,用127\.0\.0\.1而不是127.0.0.1,点号要转义,写成.*就等于对全网开放——这个误配我在两台机器上见过,都是复制粘贴没改。

第二件是开访问日志,出问题时能算出影响面。在Host里加一个AccessLogValve:

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="access." suffix=".log" pattern="%{yyyy-MM-dd HH:mm:ss}t %a &quot;%r&quot; %s %b %D" resolveHosts="false" rotatable="true" fileDateFormat="yyyy-MM-dd" />

%a是客户端地址,%r是请求行,%s是状态码,%b是响应字节数,%D是处理耗时(毫秒)。%D这一项最关键,接口变慢时不用上 APM,直接按耗时排序就能找到最慢的几个 URL。resolveHosts="false"必须显式写,设成true会对每个请求做反向解析,几百并发下延迟直接翻倍。

第三件是留一个能自查的探针。写一个极简的 JSP 放在应用里,返回堆使用和线程数,监控系统定时拉一次,趋势图比报警阈值有用:

<%-- health.jsp:只允许内网访问,放在 WEB-INF 之外的独立目录 --%> <%@ page contentType="text/plain;charset=UTF-8" %> <% Runtime rt = Runtime.getRuntime(); long used = (rt.totalMemory() - rt.freeMemory()) / 1024 / 1024; long max = rt.maxMemory() / 1024 / 1024; int threads = Thread.getAllStackTraces().size(); out.println("heap_used_mb=" + used); out.println("heap_max_mb=" + max); out.println("thread_count=" + threads); out.println("uptime_ms=" + java.lang.management.ManagementFactory.getRuntimeMXBean().getUptime()); %>

这个东西的价值不在页面本身,而在它是「容器还活着」的最直接证据。服务状态是RUNNING但线程池全堵死的时候,探针超时能第一时间告警,比等用户打电话快得多。

至于升级路线,判断点只有一个:这套系统还要不要继续改功能。如果只是维护存量、一年改不了几行代码,把版本压在这儿、把上面的暴露面收干净,是成本最低的选择;如果有新需求要进来,那 7.0.108 就是个过渡态,早做规划比晚做便宜——Servlet 3.0 到 3.1 的变化不大,应用代码基本不用动,难的是那些依赖容器内部行为的旧写法,越晚拆越贵。

我自己的习惯是:任何一台 Windows 上的 Tomcat,装完第一件事不是部署应用,而是把setenv.bat、erp.xml、server.xml这三个文件拷贝出来单独存一份,连同sc qc的输出一起放进版本库。后来几次迁移,都是靠这份三年前的备份在半小时内把环境复原的。踩过的坑记下来,下次才不用再踩一遍,希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进