从 Server.xml 到字节码:Tomcat 内核全景与请求旅程 10 000 字深剖

(零)为什么要重读 Tomcat

· 面试最爱问:HTTP 请求在 Tomcat 里到底拐了多少弯?

· 生产最怕出:CPU 100 %、线程池打满、类加载泄漏、Session 爆炸。

· 云原生最尴尬:镜像个头 200 MB,启动 30 s,HPA 还来不及扩容。

本文不贴源码,用伪代码、示意图与运维日志,带你走完一次请求的"一生"。


(一)俯瞰:Tomcat 的 4 大版图

  1. Server ‑> Service ‑> Engine ‑> Host ‑> Context ‑> Wrapper

    一条 Server.xml 就是一颗树,节点各司其职,请勿随意增删。

  2. Connector:把"字节流"封装成"Request/Response 对象"。

    BIO/NIO/NIO2/APR 四种实现,分别对应 Java 同步、Java NIO、Java AIO、OpenSSL。

  3. Container:Pipeline + Valve 责任链,像极了 Servlet Filter。

  4. 顶层组件:Jasper(JSP 编译器)、Catalina(Servlet 容器)、Coyote(HTTP 协议处理器)。

(二)一次 HTTP 请求的 12 站地

  1. Acceptor 线程:监听 8080,accept() 得到 SocketChannel。

  2. Poller 线程:NIO 模式下,把 Channel 注册到 Selector,等待 READ 事件。

  3. Processor:解析 HTTP 报文,生成 Request Coyote → Request Catalina → HttpServletRequest。

  4. Mapper:根据 URI 映射到 Host、Context、Wrapper,匹配原则"最长前缀"。

  5. Pipeline.invoke():StandardEngineValve → StandardHostValve → StandardContextValve → StandardWrapperValve。

  6. FilterChain:执行 web.xml 中配置的 Filters(顺序 = 声明顺序)。

  7. Servlet.service():业务代码开始跑。

  8. JSP 编译:如果请求 *.jsp,Jasper 把 JSP 转译成 .java,再编译成 .class,默认放在 $CATALINA_BASE/work。

  9. 响应阶段:WrapperValve → FilterChain → Connector → Poller → Socket 写回。

  10. AsyncContext:若业务开启异步,Processor 归还线程给 Poller,等待业务线程回调。

  11. keep-alive:Tomcat 10 默认 maxKeepAliveRequests=100,超出即关闭连接,防止"空轮询"。

  12. 日志:AccessLogValve 按 %h %l %u %t "%r" %s %b 格式刷盘。

(三)线程模型:Acceptor / Poller / Worker

· Acceptor:数量由 acceptorThreadCount 决定,默认 1。

· Poller:selectorPool.size = min(2, CPU 核数)。

· Worker:Executor 线程池,maxThreads 200 是"总阀门",minSpareThreads 25 保证低峰期不反复创建。

CPU 100 % 排查模板:

  1. top -H 找 nid 十六进制 → jstack 转线程名 → 定位哪个 Servlet 卡在 SQL。

  2. jstat ‑gc 观察是否频繁 Full GC,导致线程长时间阻塞。

(四)类加载:打破双亲委派的三次场景

  1. WebappClassLoader:每个 Context 一个实例,先自己加载 /WEB-INF/classes 与 /WEB-INF/lib,再向上委派。

  2. Common ClassLoader:$CATALINA_BASE/lib 下的 JAR,被所有 Webapp 共享。

  3. Shared ClassLoader:可选配置,解决多 Webapp 共用大体积 SDK(如 Hadoop)。

    内存泄漏根因:ThreadLocal 引用 WebappClassLoader → 线程未销毁 → 元空间 OOM。

    解决方案:

  • 在 Context 的 stop() 中执行 ThreadLocal.remove()。

  • 使用 Guava 的 FinalizableReferenceQueue 清理。

(五)Session 管理:内存、Redis、JDBC、JWT

· StandardManager:内存 Map,重启即失效,仅适合单体。

· PersistentManager + FileStore:序列化到 SESSIONS.ser,优雅停机时 dump。

· RedisSessionManager:通过 Valve 拦截 request,把 Session 序列化为 JSON,存在 Redis TTL=1800 s。

坑:Tomcat 10 以后包名从 org.apache.catalina.session.RedisSessionManager 换成非官方维护,注意兼容性。

· JWT:无状态会话,Tomcat 侧只负责 Filter 解析,不存储。

(六)JSP & Jasper:编译、热替换、预编译

· JspC:Maven 插件把 JSP 预编译为 Servlet,启动提速 50 %。

· development=true 时,Jasper 监听 .jsp 文件变更,重新生成源码。

· mappedfile=false 可关闭静态文本合并,调试时行号对应准确。

(七)HTTPS & APR:OpenSSL 的零拷贝

· NIO + JSSE:Java 层握手,堆内内存拷贝两次。

· APR + OpenSSL:Tomcat Native 库,使用 DirectBuffer,CPU 降低 30 %。

· HTTP/2:通过 upgrade 或 h2c direct,依赖 ALPN 与 openssl 1.0.2+。

(八)监控与诊断

  1. JMX:Catalina MBean 暴露 Connector、ThreadPool、Session 指标。

  2. psi-probe:老牌 Web UI,支持在线查看线程栈、数据源连接。

  3. AccessLogValve:%{X-Forwarded-For}i 记录真实 IP。

  4. GC Log:-Xlog:gc*:file=/tmp/gc.log:time,uptime,pid 观察 GC 与线程阻塞关联。

(九)云原生改造 6 步曲

  1. 镜像:alpine-jre + tomcat:10-jre17,剔除 examples、docs,瘦身至 80 MB。

  2. 启动脚本:catalina.sh 里 JAVA_OPTS 外置到 ConfigMap,热更新无需重建镜像。

  3. 优雅停机:

    • server.xml 开启 <Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener"/>

    • k8s preStop exec sleep 15 让正在处理的请求完成。

  4. 探针:

    • livenessProbe 200 /healthz

    • readinessProbe 200 /ready

  5. 水平扩缩:

    • HPA 用 CPU 50 % + Tomcat 线程池利用率自定义指标。
  6. Sidecar:

    • Envoy 做 mTLS、熔断、灰度。

(十)彩蛋:Tomcat 的"隐藏功能"

· JreMemoryLeakPreventionListener:GC 触发后显式调用 sun.misc.GC.requestLatency(),减少 RMI 带来的 full GC。

· ParallelWebappClassLoader:JDK9+ 模块化后,可并行加载类,启动提速 15 %。

· StuckThreadDetectionValve:监控超过 threshold 秒的线程,发邮件报警。

相关推荐
胖大和尚10 小时前
删除 XML 格式中双引号内的空格
xml
中游鱼10 小时前
使用C#对象将WinRiver项目文件进行复杂的XML序列化和反序列化实例详解
xml·c#·序列化和反序列化·属性的序列化和反序列化·完整序列化·adcp和winriver
WSSWWWSSW10 小时前
JSX(JavaScript XML)‌简介
xml·开发语言·javascript
MediaTea10 小时前
Python 库手册:xml.etree.ElementTree 处理 XML 数据模块
xml·java·前端·数据库·python
阿华的代码王国10 小时前
【Android】xml和Java两种方式实现发送邮件页面
android·xml·java
小周学学学12 小时前
Tomcat及Nginx部署使用
服务器·nginx·tomcat
Sapphire~12 小时前
odoo-059 xml中字段上写 domain 和 filter_domain 什么区别
xml·odoo
云游13 小时前
k8s:离线部署tomcatV11.0.9,报Cannot find /opt/bitnami/tomcat/bin/setclasspath.sh
容器·kubernetes·tomcat
过期动态18 小时前
MySQL中的多表查询和笛卡尔积问题
java·数据库·mysql·spring·tomcat·mybatis
web守墓人19 小时前
【前端】ikun-pptx编辑器前瞻问题二: pptx的压缩包结构,以及xml正文树及对应元素介绍
xml·前端