1.2 Tomcat 的定位:与 Nginx、Apache、Jetty 的分工 本节摘要:Tomcat 的准确定位是"Servlet 容器兼静态 Web 服务器",不是全能应用服务器。本节用一个常见误解切入,通过与 Nginx、Apache HTTP Server、Jetty 的逐项对比划清能力边界,再给出"Nginx 在前、Tomcat 在后"的典型生产拓扑与配置实证,最后按场景给出选型建议。学完你能回答"这个请求该由谁处理"。 承接 1.1 的旅程图:请求已经能走完 Tomcat 内部的管道,但真实生产中请求往往先经过别的服务器。本节解决"Tomcat 在整个 Web 服务生态里站在哪一层"——想清楚这一点,第 3 章的端口与协议配置才有方向。
本节摘要:Tomcat 的准确定位是"Servlet 容器兼静态 Web 服务器",不是全能应用服务器。本节用一个常见误解切入,通过与 Nginx、Apache HTTP Server、Jetty 的逐项对比划清能力边界,再给出"Nginx 在前、Tomcat 在后"的典型生产拓扑与配置实证,最后按场景给出选型建议。学完你能回答"这个请求该由谁处理"。
承接 1.1 的旅程图:请求已经能走完 Tomcat 内部的管道,但真实生产中请求往往先经过别的服务器。本节解决"Tomcat 在整个 Web 服务生态里站在哪一层"——想清楚这一点,第 3 章的端口与协议配置才有方向。
"Tomcat 是应用服务器吗?"——面试里十有八九会碰到,而答错的人往往不是没用过 Tomcat,而是没分清三样东西:Web 服务器、Servlet 容器、应用服务器。
Web 服务器(Nginx、Apache HTTP Server 的本职)擅长把静态文件高速发给浏览器,处理并发连接,做反向代理与负载均衡,但它不认识 Servlet。Servlet 容器(Tomcat 的本职)提供 Servlet 规范的运行环境:管理 Servlet 生命周期、编译 JSP、提供会话与安全机制。应用服务器(WildFly、WebLogic、WebSphere 这类)则在容器之上再叠加事务、消息、EJB 等完整 Jakarta EE 能力。Tomcat 只实现了 Web 规范那一小块——Servlet、JSP、EL、WebSocket——所以它轻、快、稳定,也所以在纯 Web 场景下几乎是默认选择。
这个定位直接决定了一个事实:Tomcat 可以当静态 Web 服务器用,但没人这么用。发静态文件它比不过 Nginx,跑分布式事务它又不够格。它的价值在中间那层:让 Java 写的动态应用有一个符合规范的家。
| 对比维度 | Tomcat | Nginx | Apache HTTP Server | Jetty |
|---|---|---|---|---|
| 本职角色 | Servlet 容器 | 反向代理与静态服务 | 静态服务与模块化代理 | Servlet 容器 |
| Servlet 与 JSP | 原生支持 | 不支持 | 需插件且已边缘化 | 原生支持 |
| 静态文件吞吐 | 中等 | 极强 | 强 | 中等 |
| 内存与启动 | 中等 | 极轻 | 中等 | 极轻 |
| 嵌入式使用 | 可内嵌 | 少见 | 少见 | 最常见 |
| 典型场景 | Java Web 应用部署 | 前置代理与静态分流 | 传统站点与 CGI 时代存量 | 异步与云原生场景 |
两个容易忽略的点。其一,Tomcat 与 Jetty 是同类,选择更多看团队习惯:Tomcat 生态文档更全、默认配置更稳,Jetty 架构更灵活、嵌进自定义产品更顺手。其二,Nginx 与 Tomcat 不是竞争关系而是上下游——前者站在公网边上挡流量、发静态,后者在局域网里跑业务。
直接把 Tomcat 暴露在公网行不行?技术上可以,工程上不明智,理由有三。第一,静态资源分流:图片、脚本、样式表交给 Nginx 直接返回,Tomcat 的宝贵线程只服务动态请求。第二,安全缓冲:TLS 终止、请求大小限制、恶意连接拦截都在 Nginx 完成,Tomcat 只听内网。第三,运维弹性:Nginx 上做负载均衡,后端 Tomcat 可以随时扩容、重启、灰度。
下面是一套最小可用的配合配置。先是 Nginx 侧的反向代理,动态请求转给 Tomcat,静态文件自己消化。
server { listen 80; server_name shop.example.com; # 静态资源:Nginx 自己处理,不进 Tomcat location /assets/ { root /data/web; expires 7d; } # 其余全部转给后端 Tomcat location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
再看 Tomcat 侧的配合动作。既然流量都来自本机的 Nginx,连接器就没必要对外多开口,而且要正确识别代理传来的真实客户端地址。
<!-- server.xml 中让 Engine 感知前置代理 --> <Host name="localhost" appBase="webapps"> <!-- RemoteIpValve 把代理头里的真实地址还原为请求来源 --> <Valve className="org.apache.catalina.valves.RemoteIpValve" internalProxies="127\.0\.0\.1" remoteIpHeader="X-Forwarded-For" protocolHeader="X-Forwarded-Proto"/> </Host>
这套组合拳的验证方式很直接:从外网访问站点,静态资源命中 Nginx 缓存,动态请求在 Tomcat 访问日志里看到的来源 IP 是真实客户端而非 127.0.0.1——后者正是 RemoteIpValve 在起作用。如果忘了配这个阀门,你会得到一整页"来源全是本机"的访问日志,排障时两眼一抹黑。
⚠️ 常见坑:internalProxies 的值是正则。抄配置时如果 Nginx 不在 127.0.0.1 而在别的内网段,这个阀门会静默失效——不报错,只是不还原地址。验证方法就看访问日志第一列。
位置讲清楚了,时间坐标还没交代。下一节把版本史摊开——特别是 javax 与 jakarta 这道分水岭,它直接决定你的依赖能不能跑。