这个问题本身有个误解:把三个东西都叫「web server」,会让人以为它们是同一种东西的三种实现。其实不是。Nginx 和 Apache 是 HTTP 服务器,Tomcat 是 Servlet 容器,它们干的活不在一个层次上。
Nginx 和 Apache(一般说的 Apache 指的是 Apache HTTP Server,也就是 httpd)是 HTTP 服务器 :收 HTTP 请求、按配置干活、回 HTTP 响应。它们擅长扛静态文件、做反向代理、做负载均衡,但它们不执行 Java 代码 。你打一个 .war 包丢给 Nginx,Nginx 不知道怎么处理------它只会返回 404 或者把请求转给别人。
Tomcat 是 Servlet 容器,不是完整的 Java EE 应用服务器(那是 WildFly、WebLogic、WebSphere 干的事,它们支持 EJB、JMS、JTA 等完整规范)。Tomcat 只实现 Servlet 和 JSP 规范,核心能力是:把 HTTP 请求交给你的 Java 代码去处理,再把结果变成 HTTP 响应发回去。
所以「Java 后台程序能不能用 Apache 和 Nginx」------能,而且生产环境里经常是「Nginx/Apache 在前,Tomcat 在后」:前面负责扛流量、静态资源、HTTPS 终结、负载均衡,后面专门跑 Java。
Nginx 和 Apache:都是 HTTP 服务器,架构不一样
两者都能做静态文件服务、反向代理、负载均衡,但内部设计完全不同。
Apache 有三种工作模式(MPM):prefork 是一个连接一个进程,worker 是多进程+多线程,event 是在 worker 基础上优化了 keep-alive 连接的处理。现代 Apache(2.4+)默认用 event MPM,处理 keep-alive 的方式已经接近事件驱动了,不完全是老式的「一个连接占一个线程」。但不管哪种模式,Apache 的并发上限都受限于进程/线程数------每个线程有自己的栈空间(Linux 默认 8MB),1000 个线程光栈就要 8GB 内存,还没算堆上的数据。所以 Apache 的并发连接数一般在几百到几千这个量级。
Nginx 是另一种思路:少量 worker 进程 + 事件循环。每个 worker 用 epoll(Linux)/ kqueue(macOS)做 I/O 多路复用,一个 worker 可以同时挂着几万条连接。大部分连接在等 I/O,不需要单独的线程,也就不需要那 8MB 的栈空间。所以同样一台机器,Nginx 能撑的并发连接数比 Apache 高一个数量级。
这也是很多人说「Nginx 比 Apache 性能好」的原因------不是 Nginx 处理单个请求更快,而是它用更少的资源就能维持大量连接。 如果你的场景是几十个并发、主要跑 PHP,Apache + mod_php 用着挺好,没必要换。但如果要扛几万并发、做反向代理或者负载均衡,Nginx 的模型更合适。
Tomcat:能直接对外,但不擅长
这里要纠正一个常见的说法:「Tomcat 必须放在 Nginx 后面」。
Tomcat 自带 HTTP 连接器(Coyote),可以直接监听 80 或 443 端口对外服务。开发的时候大家天天直接访问 localhost:8080,没什么问题。Spring Boot 更进一步------内嵌 Tomcat 打成一个 jar 包,java -jar 直接跑,连单独部署 Tomcat 都省了。
很多微服务架构里,每个服务就是一个内嵌 Tomcat 的 Spring Boot 应用,前面挂一个 API 网关(Spring Cloud Gateway、Kong 之类的)做路由和鉴权,根本没有单独部署 Nginx 的环节。
但 Tomcat 直接对外有几个短板:
静态文件性能。 Nginx 处理静态文件用的是 sendfile 系统调用(之前零拷贝那篇讲过),数据不经过用户空间,直接从磁盘到网卡。Tomcat 处理静态文件要经过 Java 的 IO 层,多了一次拷贝和 JVM 的开销。量小的时候感知不到,量大了差距就出来了。
SSL 终结。 Nginx 的 SSL 实现基于 OpenSSL,经过大量优化,支持 session 复用、OCSP stapling 这些。Tomcat 也能做 SSL,但性能和配置灵活性都不如 Nginx。把 SSL 卸载到 Nginx,Tomcat 和 Nginx 之间走 HTTP 明文,Tomcat 的负担更轻。
限流、缓存、负载均衡。 这些 Nginx 用几行配置就能搞定,Tomcat 要么不支持,要么需要写 Java 代码或者引入额外组件。
所以典型的生产部署是这样的:
nginx
upstream tomcat_backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081; # 多实例负载均衡
}
server {
listen 443 ssl;
ssl_certificate /etc/nginx/cert.pem;
ssl_certificate_key /etc/nginx/key.pem;
location /static/ {
alias /var/www/static/;
}
location / {
proxy_pass http://tomcat_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Nginx 扛 SSL、吐静态文件、做负载均衡,动态请求转给后面的 Tomcat。但这不是唯一的架构------小项目、内部系统、微服务里 Tomcat 直接对外也很常见,取决于你的流量规模和运维需求。
为啥早年都是 Apache + Tomcat
早期 Nginx 还没普及的时候,Apache 是 Linux 上默认的 HTTP 服务器。Java 项目的标准搭配是 Apache + mod_jk(或 mod_proxy)+ Tomcat:Apache 在前面接请求,通过 AJP 协议或 HTTP 代理转给 Tomcat。
mod_jk 用的是 AJP 协议(Apache JServ Protocol),比 HTTP 更紧凑,省了 HTTP 头的解析开销。但 AJP 协议在 2020 年爆出过 Ghostcat 漏洞(CVE-2020-1938),之后很多团队开始关闭 AJP 端口,改用 HTTP 代理。
现在新项目基本都用 Nginx 替代 Apache 当入口了。Apache 在需要 .htaccess(目录级配置覆盖)或者跑 mod_php 的场景还有优势,但纯做反向代理和负载均衡,Nginx 的资源占用和并发能力都更好。
怎么判断你的项目该用哪种组合
Spring Boot 微服务、内部系统、流量不大: 内嵌 Tomcat 直接对外,前面挂个网关或者云厂商的负载均衡器就行,不需要单独部署 Nginx。
对外的 Web 应用、有静态资源、需要 HTTPS: Nginx 在前做 SSL 终结和静态资源,动态请求 proxy_pass 到 Tomcat。
PHP + Java 混合部署(老项目): Apache 跑 mod_php 处理 PHP,同时 mod_proxy 把 Java 请求转给 Tomcat。不过这种架构越来越少了。
纯静态站点、CDN 回源、API 网关: 只需要 Nginx,不需要 Tomcat。
Nginx 和 Tomcat 不是竞争关系,Apache 和 Nginx 才是。而即便是 Apache 和 Nginx,在大部分场景下也不是「谁好谁差」的问题------Nginx 在高并发反向代理上更强,Apache 在需要 .htaccess 和动态模块加载的场景更方便。