Nginx和Tomcat和Apache的区别

文章导读
搜「Nginx 和 Tomcat 和 Apache 的区别」,多数人卡在同一处:分不清三者在请求链路里各管哪一段,于是选型时按“谁更快”来挑,排查时又不知道该看谁的日志。先给一个能落地的判断口径——Tomcat 是 Servlet/JSP 容器,负责跑 Java Web 应用;Apache httpd 和 Nginx 是通用 Web 服务器与反向代理,负责接连接、发静态文件、转发请求。所以问题通常
📋 目录
  1. A Tomcat和Apache和Nginx:各自站在什么位置
  2. B Tomcat和Apache和Nginx:性能、成本、运维、上手四个口径分开比
  3. C Tomcat和Apache和Nginx:什么情况选哪一边
  4. D Tomcat和Apache和Nginx:一次能复现的对比做法
A A

搜「Nginx 和 Tomcat 和 Apache 的区别」,多数人卡在同一处:分不清三者在请求链路里各管哪一段,于是选型时按“谁更快”来挑,排查时又不知道该看谁的日志。先给一个能落地的判断口径——Tomcat 是 Servlet/JSP 容器,负责跑 Java Web 应用;Apache httpd 和 Nginx 是通用 Web 服务器与反向代理,负责接连接、发静态文件、转发请求。所以问题通常不是“三选一”,而是“前置用谁、后端用谁”。

需要执行 Java 代码(Servlet、JSP、Spring 的 war 包),后端必须是 Tomcat 这类容器;只是静态资源、TLS 终止和反向代理,用 Nginx 或 Apache httpd 更直接。判断顺序:先看请求是否要进 JVM,再看站点是否依赖 .htaccess 或某个 Apache 专有模块,最后看并发主要耗在传输文件还是等后端。常见组合是 Nginx 在前、Tomcat 在后。边界:连接数规模、会话保持、证书卸载要结合现有环境确认,别只按名字选。

Tomcat和Apache和Nginx:各自站在什么位置

Tomcat 的官方身份是 Servlet 容器,自带 Coyote HTTP 连接器,所以它能直接对外提供 HTTP 服务,但它的强项在于把请求交给 Java 应用处理。核对口径:看 conf/server.xml 里的 <Connector> 元素,重点参数是 port、maxThreads、acceptCount、connectionTimeout,这些在 Tomcat 官网的 HTTP Connector 配置页有逐项说明;再看 lib/ 下是否有 jakarta.servlet-api 或 servlet-api 的 jar,有就说明它是容器而不是纯 Web 服务器。

Apache httpd 是模块化的通用 Web 服务器,能力来自加载了哪些模块。核对口径:执行 httpd -M(Debian/Ubuntu 上是 apache2ctl -M)列出已启用模块,看有没有 mod_proxy、mod_proxy_http、mod_rewrite、mod_php,指令含义查 httpd.apache.org 的 Directives 索引页。Nginx 是事件驱动的 Web 服务器与反向代理,核对口径:nginx -V 看编译参数和内置模块,nginx -T 打印实际生效的完整配置,指令查 nginx.org 的 directive 索引。三者定位不同,先确认“请求要不要进 JVM”,后面的比较才有意义。

Tomcat和Apache和Nginx:性能、成本、运维、上手四个口径分开比

性能:不要看别人给的单个数,自己在目标机器上量。静态文件用 ab -n 2000 -c 50 http://127.0.0.1:PORT/index.html 或 wrk -t2 -c50 -d30s http://127.0.0.1:PORT/index.html,动态接口用一个走业务逻辑的 URL,比较的是相同并发下的延迟分布和失败请求数,而不是某个峰值。单请求耗时可以用 curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total} size=%{size_download}\n' URL 逐次看。

成本:主要差在常驻内存。Tomcat 要带一个 JVM,堆用 -Xmx 控制,实际占用用 ps -o pid,rss,cmd -C java 或 systemd-cgtop 看;Nginx 和 httpd 的进程内存用 ps -o pid,rss,cmd -C nginx / -C httpd 看。换算成钱要落到具体机型上,各家云控制台的实例规格与计价页面是唯一可核对的位置,同一 vCPU 内存规格下再比单价,别用“某产品更省”这种笼统说法。

运维:Nginx 改完配置先 nginx -t 再 nginx -s reload,不断连接;httpd 用 httpd -t 检查后 apachectl graceful;Tomcat 改 server.xml 的 Connector 通常要重启进程,改应用配置则看是否支持热加载。日志位置也要提前记住:Nginx 在 access.log/error.log,httpd 在 access_log/error_log,Tomcat 在 logs/catalina.out 与 localhost.*.log,排查时找不到日志往往就是找错了层。

上手:一个静态站点,Nginx 写个 server { listen 80; root /var/www; } 就能跑;httpd 要配 DocumentRoot 和 <Directory> 权限,Debian 系还要分清 sites-available 和 sites-enabled;Tomcat 则需要把 war 放进 webapps/ 或直接改 ROOT 应用。上手成本从低到高一般是 Nginx、httpd、Tomcat,但真正决定工作量的是你有没有现成的 Java 应用要部署。

Tomcat和Apache和Nginx:什么情况选哪一边

场景一:纯静态站点 + 反向代理 + HTTPS 终止。选 Nginx。前置条件是不依赖 .htaccess,也没有必须用的 Apache 专有模块;如果原站点用了 mod_rewrite 规则,迁到 Nginx 需要逐条改写成 rewrite 或 location,这部分工作量要提前算进去。

场景二:已有大量 .htaccess、依赖 mod_php 或某个 Apache 模块的老站点。继续用 Apache httpd。前置条件是能接受它的进程/线程模型选型(event、worker、prefork 三选一),并且清楚关掉 .htaccess 解析(AllowOverride None)通常能减少每次请求的磁盘检查,但这属于配置优化,不是性能承诺。

Nginx和Tomcat和Apache的区别

场景三:请求要进 Java 代码。后端用 Tomcat,前面加不加 Nginx 取决于是否需要统一入口。如果用了 Spring Boot 内嵌 Tomcat,一般不必再单独装一个 Tomcat;如果是传统 war 部署,注意前置代理要传真实客户端 IP:

location /app/ {
    proxy_pass http://127.0.0.1:8080/app/;
    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;
}

改完先 nginx -t 验证,再 reload。

判断顺序清单,从上往下问,命中即停:

  1. 请求最终要不要 Servlet/JSP 执行?要 → 后端 Tomcat。
  2. 站点是否依赖 .htaccess 或某个 Apache 专有模块?是 → 保留 httpd。
  3. 是否只做静态文件、反向代理、TLS 终止?是 → Nginx。
  4. 并发主要耗在传输文件还是等后端?传文件多 → 前置 Web 服务器;等后端 → 优化 Tomcat 线程与后端逻辑。
  5. 是否需要统一入口、统一证书、统一日志?需要 → 前面再套一层 Nginx。

Tomcat和Apache和Nginx:一次能复现的对比做法

十分钟能跑完的版本用容器,避免动本机环境。先造一个几十 KB 的静态文件,再起三个服务:

mkdir -p /tmp/webcmp
head -c 100000 /dev/urandom | base64 > /tmp/webcmp/index.html

docker run -d --name cmp-nginx -p 8081:80 \
  -v /tmp/webcmp:/usr/share/nginx/html:ro nginx:stable
docker run -d --name cmp-httpd -p 8082:80 \
  -v /tmp/webcmp:/usr/local/apache2/htdocs:ro httpd:2.4
docker run -d --name cmp-tomcat -p 8083:8080 tomcat:10-jdk17

等容器起来后,用同一条命令量三个端口,注意 Tomcat 的静态文件还没放进去:

for p in 8081 8082 8083; do
echo "== port $p"
curl -s -o /dev/null -w 'code=%{http_code} total=%{time_total} size=%{size_download}\n' \
  http://127.0.0.1:$p/index.html
done

预期差异要看两处:8081 和 8082 应当返回 code=200 且 size_download 接近文件大小;8083 通常返回 code=404,因为较新的 Tomcat 镜像默认不部署示例应用,静态文件得自己放进 webapps/ROOT/ 或用卷挂载进去。把同一个文件再挂到 Tomcat 的 /usr/local/tomcat/webapps/ROOT/ 后重跑,三个端口的 time_total 会有差别,但具体数值取决于机器,只能在自己的环境下对同一文件、同一并发做横向比较,别拿一次结果当通用结论。

容易踩的坑:用 Nginx 反代 Tomcat 后,Tomcat 的日志里客户端 IP 全是 127.0.0.1,应用生成的绝对链接或重定向也可能指回内部地址。原因是少传了转发头,或者后端没认这些头。除了上面的 proxy_set_header,还要确认 Tomcat 侧开启了对转发头的信任(例如在 <Host> 内配置 RemoteIpValve,相关属性在 Tomcat 官方 Valve 文档里可查),否则应用里取到的 getRemoteAddr() 依然是代理地址。这一步没验证过就先别急着改业务里的 IP 白名单或风控逻辑。