Tomcat作为Apache软件基金会旗下的开源项目,自1999年诞生以来已成为Java Web应用部署的事实标准。不同于商用WebLogic等全功能应用服务器,Tomcat专注于Servlet/JSP规范的实现,其轻量级特性使其在中小型系统场景中占据绝对优势。根据2023年JVM生态调查报告显示,全球超过68%的Java Web应用选择Tomcat作为运行时容器,这一数字在互联网初创企业中更是高达82%。
核心架构层面,Tomcat实现了以下关键组件:
实际生产环境中,Tomcat 8.5.x版本因其对Java EE 7的完整支持与稳定性,目前仍是企业级应用的首选。但需要注意,从Tomcat 10开始,Jakarta EE命名空间全面替代了原有的javax.*包,这会导致与旧版本库的兼容性问题。
对于典型的生产环境部署,建议采用以下配置基准:
在Linux环境下需进行内核参数调整(以CentOS 7为例):
bash复制# 增加文件描述符限制
echo "* soft nofile 65535" >> /etc/security/limits.conf
echo "* hard nofile 65535" >> /etc/security/limits.conf
# 优化TCP协议栈
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
echo "net.core.somaxconn = 65535" >> /etc/sysctl.conf
sysctl -p
推荐使用OpenJDK 11 LTS版本,关键优化参数示例:
bash复制export JAVA_OPTS="-server -Xms4g -Xmx4g -XX:+UseG1GC
-XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=4
-XX:ConcGCThreads=2 -XX:InitiatingHeapOccupancyPercent=70"
| 方案类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 粘性会话 | 实现简单 | 负载不均 | 小型集群 |
| 会话复制 | 高可用 | 网络开销大 | 中小规模集群 |
| 集中存储 | 扩展性好 | 单点风险 | 大型分布式系统 |
| 无状态设计 | 完全水平扩展 | 需改造应用 | 云原生应用 |
在conf/context.xml中添加:
xml复制<Manager className="de.javakaffee.web.msm.MemcachedBackupSessionManager"
memcachedNodes="n1:192.168.1.10:11211,n2:192.168.1.20:11211"
failoverNodes="n1"
requestUriIgnorePattern=".*\.(ico|png|gif|jpg|css|js)$"
transcoderFactoryClass="de.javakaffee.web.msm.serializer.kryo.KryoTranscoderFactory"
sessionBackupAsync="false"
backupThreadCount="4"
lockingMode="auto"/>
关键参数说明:
memcachedNodes:定义所有Memcached节点,格式为节点ID:IP:端口failoverNodes:指定主备节点优先级transcoderFactoryClass:使用Kryo序列化比Java原生序列化性能提升3-5倍lockingMode:设置为auto可避免并发访问导致的会话数据不一致修改conf/server.xml中的Connector配置:
xml复制<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
maxThreads="500"
minSpareThreads="30"
acceptCount="1000"
connectionTimeout="20000"
maxConnections="10000"
keepAliveTimeout="30000"
maxKeepAliveRequests="100"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript"/>
使用以下命令检测内存问题:
bash复制# 生成堆转储文件
jmap -dump:format=b,file=heap.hprof <pid>
# 分析GC日志
jstat -gcutil <pid> 1000 10
常见内存泄漏场景:
xml复制<Server port="8005" shutdown="自定义复杂字符串">
xml复制<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/keystore.jks"
type="RSA"
keystorePass="复杂密码"
ciphers="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"/>
</SSLHostConfig>
</Connector>
-Dcom.sun.management.jmxremote.authenticate=truebash复制wget https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.16.1/jmx_prometheus_javaagent-0.16.1.jar
yaml复制rules:
- pattern: 'Catalina<type=GlobalRequestProcessor, name=\"(\w+-\w+)-(\d+)\"><>(\w+):'
name: tomcat_$3_total
labels:
port: "$2"
protocol: "$1"
help: "Tomcat global $3"
type: COUNTER
bash复制-javaagent:./jmx_prometheus_javaagent-0.16.1.jar=9090:tomcat.yml
| 指标类别 | 健康阈值 | 报警阈值 |
|---|---|---|
| 线程池使用率 | <70% | >90%持续5分钟 |
| 堆内存使用 | <60% | >80%持续10分钟 |
| 响应时间 | <500ms(P99) | >1s(P99)持续15分钟 |
| 错误率 | <0.1% | >1%持续5分钟 |
dockerfile复制FROM eclipse-temurin:11-jre
RUN useradd -m -d /tomcat -s /bin/bash tomcat
WORKDIR /tomcat
COPY apache-tomcat-9.0.65.tar.gz .
RUN tar xzf apache-tomcat-9.0.65.tar.gz --strip-components=1 \
&& rm apache-tomcat-9.0.65.tar.gz \
&& chown -R tomcat:tomcat .
USER tomcat
ENV CATALINA_OPTS="-Xms2g -Xmx2g -XX:+UseZGC"
EXPOSE 8080
CMD ["bin/catalina.sh", "run"]
构建注意事项:
bash复制docker run -d --name tomcat \
-p 8080:8080 \
-v ./conf:/tomcat/conf \
-v ./webapps:/tomcat/webapps \
--memory=4g --cpus=2 \
my-tomcat-image
bash复制tail -f logs/catalina.out
grep -i "exception" logs/catalina.out
bash复制netstat -tulnp | grep -E '8005|8080|8443'
bash复制java -verbose:class -jar bin/bootstrap.jar 2>&1 | grep "loaded"
bash复制jstack <pid> > thread.dump
bash复制grep -A 1 "BLOCKED" thread.dump
bash复制jcmd <pid> VM.flags
jcmd <pid> PerfCounter.print
bash复制tar czf tomcat-backup-$(date +%F).tar.gz \
conf/ webapps/ROOT/ logs/ lib/
bash复制# 旧版本继续运行在8080端口
# 新版本部署在8081端口进行测试
java复制public class AccessLogValve extends ValveBase {
@Override
public void invoke(Request request, Response response) {
long start = System.currentTimeMillis();
getNext().invoke(request, response);
long duration = System.currentTimeMillis() - start;
String logMsg = String.format("%s %s %s %d %dms",
request.getRemoteAddr(),
request.getMethod(),
request.getRequestURI(),
response.getStatus(),
duration);
AccessLog.write(logMsg);
}
}
配置到server.xml:
xml复制<Engine>
<Valve className="com.example.AccessLogValve" />
</Engine>
开发注意事项: