
简介Tomcat 7.0.108 是一份面向 Java Web 开发者的完整开源 Servlet 容器发行包适合需要在本地运行 JSP/Servlet 应用、搭建测试环境或熟悉 Tomcat 目录结构的初中级开发者。压缩包约 10.38MB共 640 个文件以 html、jsp 页面、java 源码、class 编译文件以及 jar、xml 配置文件为主同时包含启动/停止脚本、war 示例应用及日志、临时目录等解压即可得到 bin、conf、lib、webapps、temp、logs 等标准目录便于快速部署和验证。作为 Tomcat 7 系列的更新版本它内置了运行 Java Web 应用所需的核心库与默认配置支持通过 webapps 自动部署 WAR 包或在 server.xml 中手工配置 Context。包中还附带若干 bat/sh 脚本和 war 文件可帮助 Windows/Linux 用户完成环境变量设置、服务启停与应用发布。目前已有 632 人学习/下载对需要离线部署或入门 Java Web 开发的读者来说是一份可直接使用的完整工具包。1. 为什么tomcat-7.0.108.zip到今天还是刚需某次做系统巡检发现客户的生产环境还在跑Tomcat 7.0.82安全扫描直接报了高危。想升级到9老应用里基于Servlet 3.0规范写的自定义Filter和第三方加密组件在更高版本容器下行为不一致光一个请求头解析差异就折腾了一天。最后统一替换成tomcat-7.0.108.zip——这是Tomcat 7系列的最终版本相当于把七年的安全补丁一次补齐行为特征和老版本一致业务代码一行不用动。搜索这个压缩包的人无非三类场景遗留系统重装、安全整改换版本、测试环境标准化。它支持JDK 6/7/8兼容Spring 3.x/4.x时代的大量老项目。如果你正在维护这类系统它就是这个年代最稳妥的容器基线。2. 下载、解压与启动把tomcat-7.0.108.zip跑起来2.1 从官方归档下载并校验文件Tomcat 7.0.108的二进制包大约8MB从Apache官方归档目录的v7.0.108/bin路径就能拿到。官网同时提供zip和tar.gz两种格式Windows开发机用zip方便Linux生产服务器建议用tar.gz因为它保留了Unix权限位——bin目录下的脚本需要执行权限。我一般直接用wget拉取归档文件mkdir -p /data/software cd /data/software wget https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.108/bin/apache-tomcat-7.0.108.zip wget https://archive.apache.org/dist/tomcat/tomcat-7/v7.0.108/bin/apache-tomcat-7.0.108.zip.sha1 # 对比哈希值防止文件在传输过程中被篡改 echo $(cat apache-tomcat-7.0.108.zip.sha1) apache-tomcat-7.0.108.zip | sha1sum -c -这三条命令分别负责建目录、下载二进制包、下载官方校验文件。sha1sum -c 会输出OK说明文件与官方发布内容一致。企业内网如果策略严格可以把zip先拉回私有镜像再统一分发校验这样能保证所有服务器拿到的都是同一份干净文件。拿到包之后解压并改名cd /data/software unzip apache-tomcat-7.0.108.zip -d /opt/ mv /opt/apache-tomcat-7.0.108 /opt/tomcat7 ls -l /opt/tomcat7这里有个提醒不要用老版本的包直接覆盖升级。跨小版本升级时conf目录里可能残留旧配置容易把新版本的行为带偏。Tomcat 7.0.108这个版本我建议直接解压全新目录配置自己重新维护干净可控。2.2 标准目录结构拆解Tomcat本质上不需要“安装”它就是“一个目录 一个JDK”。解压后的目录结构作为运维人员需要记清楚目录作用需要重点关注bin/启动关闭脚本catalina.sh是核心脚本读取的环境变量conf/全部配置文件server.xml、web.xml、tomcat-users.xmllib/Tomcat自身及公共jar包不要放应用私有jarlogs/运行日志catalina.out、localhost_access_logwebapps/部署war/目录的默认位置生产建议改为外部路径work/JSP编译后的class缓存跨版本升级建议清空temp/临时文件目录无需保留这里有两个经典坑。第一个是lib目录被塞进应用jar导致classloader冲突典型表现是启动后各种NoSuchMethodError。第二个是work目录里的旧编译缓存跨小版本升级或JSP文件改动频繁时会出现“改了代码不生效”的幻觉清空work目录解决。2.3 JAVA_HOME与CATALINA_BASE规划启动之前先确认JDK版本。命令行敲入java -version输出里如果是1.7.x或1.8.x且是64位就满足要求。Tomcat 7.0.108最低要求JDK 6主流生产配套是JDK 7或8。这里有一个JDK 8的隐藏知识点JDK 8u191之后的版本对TLS协议加了默认限制如果老客户端只支持TLSv1.0需要在jdk.tls.disabledAlgorithms中放开对应算法否则会出现诡异的握手失败。这个问题排查起来非常消耗时间先记住这个前提。环境变量的典型设置export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export CATALINA_HOME/opt/tomcat7 export CATALINA_BASE/opt/tomcat7 export PATH$CATALINA_HOME/bin:$PATHJAVA_HOME指定JDK根目录startup.sh靠它找到java命令。CATALINA_HOME是Tomcat安装目录。CATALINA_BASE是实例目录单实例时和CATALINA_HOME相同多实例时必须分开。建议把这几个变量写进/etc/profile或者单独的tomcat.sh文件避免每次登录都要重复声明。2.4 单实例与多实例部署单实例最简单直接用CATALINA_HOME当CATALINA_BASE启动就行。生产上我更推荐多实例结构——同一份二进制、多份独立配置隔离不同业务的端口和日志mkdir -p /data/tomcat/instance1/{conf,logs,temp,webapps,work} cp -r /opt/tomcat7/conf/* /data/tomcat/instance1/conf/然后手动指定实例目录启动export CATALINA_HOME/opt/tomcat7 export CATALINA_BASE/data/tomcat/instance1 /opt/tomcat7/bin/startup.sh多实例的好处很务实升级二进制时只替换/opt/tomcat7各实例的conf和webapps完全不受影响回滚时换回旧目录即可业务数据零损失。对老系统迁移来说这是成本最低的容灾手段。2.5 首次启动与验证注意首次启动务必用前台模式直接把日志打到终端便于看到完整启动过程。/opt/tomcat7/bin/catalina.sh run看到“Server startup in 2345 ms”这行说明容器已经就绪。确认无误后再切换到后台启动/opt/tomcat7/bin/startup.sh tail -f /data/tomcat/instance1/logs/catalina.out curl -I http://localhost:8080/curl返回HTTP/1.1 200说明默认ROOT应用已经可以被访问。如果你走到这一步发现没起来多半是端口被占用或JAVA_HOME没配对这些在第5章会专门展开。3. 别只改端口server.xml的配置与三个必调参数3.1 server.xml的分层模型server.xml是Tomcat所有核心行为的定义来源。从进程角度看它抽象成四层Server、Service、Connector/Engine、Host。Server是整个进程的根包含一个用于关闭的Shutdown端口Service把一组Connector和Engine绑定在一起Connector负责监听外部请求HTTP和AJP各占一个Host和Context决定请求最终落在哪个应用上。Tomcat 7.0.108的默认server.xml里有一个8080的HTTP连接器和一个8009的AJP连接器。如果应用只走HTTP建议把AJP注释或删掉少一条通道就少一分风险。很多老项目上线时只改端口号结果线上频繁出现两类故障一是慢请求拖死线程池二是URI中文参数乱码。这两类都与Connector参数直接相关下面拆开讲。3.2 HTTP Connector参数线程、等待队列与编码生产环境我的基准配置如下Connector port8080 protocolorg.apache.coyote.http11.Http11NioProtocol connectionTimeout20000 redirectPort8443 maxThreads200 minSpareThreads25 maxIdleTime60000 acceptCount100 maxConnections10000 URIEncodingUTF-8 compressionon compressionMinSize2048 compressibleMimeTypetext/html,text/xml,text/plain,text/css,text/javascript,application/json/逐个说明关键参数protocolorg.apache.coyote.http11.Http11NioProtocol指定NIO连接器。Tomcat 7里默认的HTTP/1.1协议是BIO实现也就是每个连接独占一个线程并发一高线程就被耗尽。NIO用多路复用同时可以hold住更多连接这是老Tomcat调优的第一项。maxThreads容器同时能处理请求的线程上限。老项目建议150~300之间太小高峰时CPU没跑满请求已开始排队太大也不会更快反而增加线程切换开销。minSpareThreads与maxIdleTime控制空闲线程的保留和回收避免频繁创建销毁线程。Tomcat 7的时代线程创建销毁成本高这两个参数对突发小流量很有意义。acceptCount线程池满之后操作系统中等待队列的长度。设太小并发稍高就拒绝连接设太大用户会一直排队等待实际取50~100比较均衡。URIEncodingUTF-8GET请求的URI解码方式。不设的话Tomcat 7默认按ISO-8859-1解码中文字符参数全变成乱码。这是我检查老项目时必查的配置项。compressionon开启gzip压缩对HTML、CSS、JS和JSON这些文本响应收益非常明显。压缩级别用默认值即可不需要强行调高CPU开销会不成比例地增长。compressibleMimeType指定哪些响应类型参与压缩。只列文本类不要把图片、PDF等二进制格式放进去。3.3 用Executor统一管理线程池如果同时开了HTTP和HTTPS两个连接器建议把线程池参数抽出来Executor nametomcatThreadPool namePrefixcatalina-exec- maxThreads200 minSpareThreads25 maxIdleTime60000/ Connector port8080 protocolorg.apache.coyote.http11.Http11NioProtocol executortomcatThreadPool acceptCount100 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8/这样多个Connector共享同一个池线程不会被拆成多个池互相争抢。只有单个连接器时没必要配Executor直接在Connector上写参数即可。这个设计本身不复杂但很多人只见过单独配置的写法不知道还有共享线程池的手段。3.4 Host虚拟主机与Context部署老系统常常需要在同一台机器上跑两个域名比如管理端和用户端分离。利用Host就能实现Engine nameCatalina defaultHostapp.example.com Host nameapp.example.com appBase/data/sites/app unpackWARsfalse autoDeployfalse Context path docBase/data/sites/app/ROOT reloadablefalse/ /Host Host nameadmin.example.com appBase/data/sites/admin unpackWARsfalse autoDeployfalse Context path docBase/data/sites/admin/ROOT reloadablefalse/ /Host /Engine第一个Host接收用户端请求第二个Host接收管理端请求互不干扰。关键在autoDeployfalse和reloadablefalse——自动部署在开发环境很好用生产环境却会在你放入war时自动重载轻则抖动重则classloader内存泄漏。docBase指向外部目录后应用代码完全脱离webapps目录发布和回滚都更容易管理。3.5 conf/web.xml中的全局会话超时很多“登录30分钟就失效”的投诉问题不在应用代码而在conf/web.xmlsession-config session-timeout60/session-timeout /session-config单位是分钟默认值30。老项目如果觉得会话失效太快优先检查这里。这个配置对部署在Tomcat下的所有应用全局生效应用自身的web.xml配置会覆盖它排查时要先确认到底哪一层生效。4. JVM参数与性能调优让Tomcat 7在不换硬件的前提下扛住流量4.1 内存参数怎么设才能脱离玄学Tomcat 7的性能瓶颈绝大多数集中在JVM参数上。启动时内存不够、GC停顿过长、PermGen溢出都是老系统最常见的问题。以8G物理机跑3个实例为例JDK 7环境我常用的基准参数如下export CATALINA_OPTS-server -Xms1536m -Xmx1536m -XX:MaxPermGen256m -XX:UseConcMarkSweepGC -XX:UseParNewGC -XX:CMSInitiatingOccupancyFraction70每个参数的作用要清楚-server强制使用Server虚拟机JIT编译更激进适合长期运行的容器进程。-Xms与-Xmx堆初始值和最大值。生产环境两者必须相同避免运行期堆扩容触发Full GC停顿。1536m对中型业务模块够用更精确的值要靠压测观察对象分配速率来定。-XX:MaxPermGenJDK 7时代的独有参数。如果还跑在JDK 7上PermGen默认值只有几十MB老项目加载大量class很容易OOM。先给256m日志里报PermGen space就加到384m或512m。-XX:UseConcMarkSweepGC与-XX:UseParNewGCCMS回收器配ParNew新生代收集器是JDK 7下稳妥的组合目的是降低单次GC停顿时间。CMS自身会有碎片问题监控Full GC频率比堆内存大小更重要。如果你的服务器已经升级到JDK 8参数要跟着变export CATALINA_OPTS-server -Xms2048m -Xmx2048m -XX:MaxMetaspaceSize512m -XX:UseG1GCJDK 8里PermGen被Metaspace取代MaxPermGen参数直接失效继续用会在启动日志里看到警告。JDK 8环境我直接上G1因为8里的G1比CMS稳定尤其在堆内存抖动和碎片化场景省掉了CMS维护滞后带来的各种麻烦。4.2 加GC日志别等问题发生了才后悔内存问题和停顿排查依赖GC日志。没有日志看到现象也只能瞎猜。在CATALINA_OPTS中加入export CATALINA_OPTS$CATALINA_OPTS -Xloggc:/data/logs/gc.log -XX:PrintGCDetails -XX:PrintGCDateStamps -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/逐项讲清楚-Xloggc指定GC日志输出文件。注意它跟-verbose:gc互斥配了loggc就不需要verbose。-XX:PrintGCDetails输出每次GC前后的空间使用和耗时这是分析每次停顿的原始数据。-XX:PrintGCDateStamps在GC行上打时间戳方便和业务报障时间对齐。-XX:HeapDumpOnOutOfMemoryErrorOOM时自动导出堆快照。这是血泪经验换来的参数如果没有它OOM发生后只能靠猜有了hprof文件导入MAT分析就能直接定位是哪个对象撑爆了堆。4.3 jstack与jstat配合排查Tomcat 7的线程池模型比较直观查问题时两步定位就够了# 第一步查看内存使用与GC次数 jstat -gcutil pid 5000 # 第二步导出线程栈 jstack pid /data/logs/jstack_$(date %Y%m%d_%H%M%S).txtjstat输出里S0、S1、E、O、M分别代表幸存区、新生代、老年代、元数据区的百分比。如果O列长期偏高且FGC次数持续增长直接判断老年代不够。jstack导出的文件里grep catalina-exec-能看到所有Tomcat业务线程结合线程状态判断是IO等待还是锁竞争。连接器线程名就是catalina-exec-加编号一眼就能认出来。4.4 压测工具与文件描述符上限压测不需要上复杂平台用Apache自带的ab工具就能验证基础并发能力ab -n 5000 -c 100 -H Accept-Encoding: gzip http://localhost:8080/api/health-n是总请求数-c是并发数建议从50起步逐步加到200。重点看Requests per second和Time per request这两个指标。如果TPS到某个值后不再增长但CPU已经占满说明瓶颈在连接器如果CPU还有余量但TPS上不去就要检查应用线程是否阻塞在远程调用或数据库上。压测前还有一个容易漏的配置ulimit -n 65535Linux默认文件描述符上限是1024高并发下Tomcat连接数超过这个值会直接报Too many open files压测结果完全是假象。把ulimit调大再测数据才有参考价值。5. 避坑手册tomcat-7.0.108使用中的五个典型翻车现场5.1 启动脚本没报错页面却访问不到现象执行startup.sh后终端没报错java进程也在但浏览器和curl都超时。原因多半是环境变量里CATALINA_BASE指向了另一个实例或者8080端口被别的进程占用。startup.sh启动时没有打印完整路径于是你tail错了日志文件。解决先看网络监听状态ss -tlnp | grep 8080 ps -ef | grep catalina确认进程启动参数里实际加载的CATALINA_BASE再打开对应的catalina.out。这个排查顺序能最快定位问题。5.2 GET请求中文参数乱码现象链接里传中文参数后端拿到的值全是问号POST请求同样传中文却正常。原因Tomcat 7的Connector默认URIEncoding是ISO-8859-1GET请求的查询串在协议层就被解码错了应用层再怎么setCharacterEncoding都是无效的。解决在Connector上同时配置URIEncodingUTF-8和useBodyEncodingForURItrue。加上后者的目的是让GET和POST的编码策略统一避免POST按请求体编码、GET按URI编码这种两套规则并存的问题。改完重启服务再用带中文参数的链接验证一遍。5.3 OutOfMemoryError: PermGen space现象老项目运行几天后日志出现java.lang.OutOfMemoryError: PermGen space随后页面500或进程被系统杀掉。原因JDK 7的PermGen存放类元数据。应用反复热加载、JSP编译、反射生成动态代理类时PermGen空间被撑爆。Tomcat 7的reloadabletrue会放大这个压力。解决生产环境把autoDeploy和reloadable全部关闭同时在CATALINA_OPTS里显式加大PermGen。如果在JDK 8环境把这个参数换成MaxMetaspaceSize。修正后观察FGC频率如果PermGen还在涨进一步检查是否有动态生成类的代码。5.4 Address already in use: JVM_Bind现象启动时catalina.out里报java.net.BindException: Address already in use: JVM_Bind。原因端口被占用。常见于上一次进程没杀干净或者Nginx转发到8080但Tomcat起了两个实例都监听8080。解决lsof -i:8080 kill -9 pid先找到占用端口的进程再kill不要盲目重启Tomcat。如果同一台机器需要跑多个实例记得给每个实例分配不同的端口并检查Connector的port、Shutdown的port和AJP端口是否都改了。5.5 部署war后旧代码残留现象替换同名war包后页面还是旧功能有的类直接报ClassNotFoundException。原因Tomcat 7的自动部署机制在解压新war时如果同名目录已存在且包含旧class不会完整清理残留文件与新class混在一起。这和reloadabletrue共同使用时更加明显。解决手动部署四步缺一不可/opt/tomcat7/bin/shutdown.sh rm -rf /data/tomcat/instance1/webapps/myapp* rm -rf /data/tomcat/instance1/work/Catalina/* cp maven打出的ROOT.war /data/tomcat/instance1/webapps/ /opt/tomcat7/bin/startup.sh先停、再删、后放、最后启。生产环境禁止直接把war塞进webapps目录指望它自动部署这一步看着省事后续线上案例几乎都会翻车。6. 给tomcat-7.0.108实例做一轮安全加固6.1 收紧Shutdown端口默认server.xml里的Shutdown端口是8005关闭指令固定为字符串SHUTDOWN。任何能访问到这个端口的人都可以远程关停Tomcat。修改方式Server port8005 address127.0.0.1 shutdown此处改为一段随机字符串address限制为本机回环地址外部网络无法触达shutdown修改为随机字符串即使本机其他用户也不能猜出关闭口令。改完重启生效。6.2 移除默认应用解压后的webapps目录自带ROOT、docs、examples、manager和host-manager。这些默认应用在安全扫描里通常是扣分项rm -rf /opt/tomcat7/webapps/examples rm -rf /opt/tomcat7/webapps/docs rm -rf /opt/tomcat7/webapps/ROOTmanager和host-manager保留的话需要在tomcat-users.xml里配置强密码和最小角色。运维需要mananger-gui角色业务应用不需要没用到就直接把两个目录一并删掉攻击面越小越好。6.3 HTTPS终结在反向代理而不是Tomcat**注意如果Tomcat直接暴露HTTP给外部建议在上游的反向代理层做HTTPS证书终止Tomcat只监听内网HTTP端口。**这样证书管理集中在代理层Tomcat端不需要处理证书文件和TLS握手性能损耗。代理层转发HTTP到8080Tomcat只对代理来源的网段放行。如果必须直接在Tomcat上启用HTTPS才需要配置8443的连接器并管理证书生命周期运维成本高出一截而且Connector混用HTTPS和NIO时参数组合更敏感非必要不建议走这条路。最后的最后说点实在的。新项目我劝你直接上Tomcat 9或10但在遗留系统上tomcat-7.0.108是我这两年老系统收尾时用得最顺的版本。最大的体会是部署动作规范一点后续问题就少一半。不要图省事往webapps里丢war不要依赖热部署不要省那几行JVM参数。认真对待一次部署后面能省下无数个排查的深夜。希望帮到你。本文还有配套的精品资源点击获取