Tomcat和Apache和Nginx的区别

文章导读
搜「Tomcat 和 Apache 和 Nginx 的区别」的人,通常正卡在「服务要上线了,前面该放哪个」这一步。可以直接给的判断是:Tomcat 是 Servlet 容器,负责运行 .war、JSP、Servlet 这类 Java 应用;Nginx 和 Apache httpd 是 HTTP 服务器兼反向代理。Nginx 和 Apache httpd 彼此是同类替换关系,Tomcat 无法被它们
📋 目录
  1. A Nginx和Tomcat和Apache:各自站在什么位置
  2. B Nginx和Tomcat和Apache:性能、成本、运维、上手四个口径分开比
  3. C Nginx和Tomcat和Apache:什么情况选哪一边
  4. D Nginx和Tomcat和Apache:一次能复现的对比做法
A A

搜「Tomcat 和 Apache 和 Nginx 的区别」的人,通常正卡在「服务要上线了,前面该放哪个」这一步。可以直接给的判断是:Tomcat 是 Servlet 容器,负责运行 .war、JSP、Servlet 这类 Java 应用;Nginx 和 Apache httpd 是 HTTP 服务器兼反向代理。Nginx 和 Apache httpd 彼此是同类替换关系,Tomcat 无法被它们替代。先确认应用有没有 Java 运行时需求,再谈性能、成本和运维差异;顺序反了,容易多折腾一轮。

判断口径:要跑 war、JSP、Servlet,就绕不开 Tomcat 这类 Servlet 容器,Nginx 和 Apache httpd 都顶不上;只做静态文件、TLS 终止和反向代理,Nginx 与 Apache httpd 都能胜任,选哪个看现有配置和模块依赖。三者同框时,常见组合是 Nginx 在前、Tomcat 在后。先确认应用语言与运行方式,再看性能与运维,别反过来按性能排名选型。

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

Nginx 的定位是事件驱动的 HTTP 服务器和反向代理,擅长静态文件、TLS 终止、按路径或域名分流。核对口径:装好后执行 nginx -V,输出里是编译参数和模块列表;官方文档在 nginx.org 的 Documentation 下,看「Beginner's Guide」和 ngx_http_proxy_module 两页,就能确认某个能力是内置还是要加模块。

Apache httpd 同样也提供 HTTP 服务,但设计取向更偏模块化:同一个进程可以通过加载模块提供反向代理、认证、重写等能力,很多老项目依赖 .htaccess 在目录里写规则。核对口径:执行 httpd -M(Debian/Ubuntu 上可能是 apache2ctl -M)看已加载模块,比如 mod_proxy_ajp、mod_rewrite 在不在;文档看 httpd.apache.org 的 Directive Index,逐条查指令属于哪个模块。

Tomcat 是 Java 应用服务器,主要提供 Servlet 容器、连接器,以及 webapps 目录下的部署机制。核对口径:看 $CATALINA_BASE/conf/server.xml 里的 <Connector port="8080" protocol="HTTP/1.1"> 这一段,端口和协议都在这里改;部署规则看 tomcat.apache.org 的 Configuration Reference 和 Class Loader How-To,后者解释 WEB-INF/lib 与公共类加载器的先后顺序,排查依赖冲突时用得上。

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

性能:不要凭印象判断。同一台机器上把静态文件分别放到 Nginx 和 Tomcat 的根目录,用 ab -n 1000 -c 50 http://127.0.0.1:8081/index.html 和指向 Tomcat 端口的同一条命令各跑一遍,观察 Requests per second 与 Time per request 两项;也可以只测单次响应,用 curl -s -o /dev/null -w 'start=%{time_starttransfer} total=%{time_total}\n' http://127.0.0.1:8080/。差异主要来自静态资源是否绕过 Servlet 处理,以及 JVM 是否已预热;只测一次不预热,结论会偏。

成本:分两部分。机器成本看你选的云厂商「实例规格 / 云服务器」计价页面,按 vCPU 与内存的单价估算,价格随可用区和计费方式变化,必须在你下单的那个页面上确认。常驻内存成本用 ps -o pid,rss,comm -C nginx,httpd 看 HTTP 服务器,Tomcat 用 ps -o rss= -p <pid> 配合 jcmd <pid> VM.flags 看 -Xmx 设了多少;JVM 堆是 Tomcat 独有的固定开销,小规格机器上这一项往往比 HTTP 服务器本身大。

Tomcat和Apache和Nginx的区别

运维:先看有没有语法自检工具。Nginx 改完配置跑 nginx -t,Apache httpd 跑 apachectl configtest 或 httpd -t,都能在 reload 前拦下拼写错误;Tomcat 改 server.xml 通常需要重启才生效,改动前先备份该文件。日志位置也不同:Nginx 默认在 /var/log/nginx/,Apache httpd 视发行版在 /var/log/httpd/ 或 /var/log/apache2/,Tomcat 在 $CATALINA_BASE/logs/ 下的 catalina.out 与按日期滚动的应用日志。

上手:Nginx 用十几行配置就能把请求转到后端,学习成本集中在 location 匹配优先级;Apache httpd 的优势是 .htaccess 丢进目录就生效(前提是 AllowOverride 已开),不用碰主配置,代价是每次请求多一次目录查找;Tomcat 要额外理解 context path、war 解包和类加载顺序,前端的流量分发概念在 Tomcat 里用不上。

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

按下面的顺序判断,比横向比性能更快得出结果:

  1. 应用是不是 Java 写的、要不要跑 war、JSP 或 Servlet?是,Tomcat 必选,跳过后续的性能对比。
  2. 现有项目是否依赖 .htaccess、mod_php 或某个只在 Apache 上有的模块?是,保留 Apache httpd,迁移成本通常高于收益。
  3. 只是静态站点、前端 SPA 加反向代理?Nginx 或 Apache httpd 都行,优先按团队熟悉度选。
  4. 已在用 Tomcat,又需要 TLS、多域名分流、静态资源加速?在 Tomcat 前面加一层 Nginx,不要让 Tomcat 直接对外。

场景一:前端静态资源加非 Java 的 API 后端。前置条件是后端不要求特定 Web 服务器模块,选 Nginx,用它做 gzip、缓存头和 proxy_pass。验证方式是先 nginx -t 再 reload,然后 curl -I 看响应头里是否出现预期的 Cache-Control。

场景二:单体 Java 应用,一个 war 包部署。前置条件是团队能维护 JVM 参数和连接器配置,选 Tomcat 直接部署,前置一层 Nginx 负责 443 和静态文件。上线前先用 curl -I http://127.0.0.1:8080/应用名/ 确认应用本身能通,再接前置层,否则出问题分不清是哪一层。

场景三:老系统跑在 Apache httpd 上且用了大量重写规则。前置条件是这些规则暂时没有人力重写,就保留 Apache httpd;需要接 Java 时用 mod_proxy_ajp 或 ProxyPass 把特定路径转发到 Tomcat 的 8080 或 8009,不要为了统一技术栈硬迁。

Tomcat和Apache和Nginx的区别

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

用容器在本机起三个实例,十分钟内能看到响应头和默认行为的具体差别。先拉镜像并启动(镜像标签以 Docker Hub 上对应仓库当前页面为准):

docker run -d --name nginx-demo -p 8081:80 nginx:alpine
docker run -d --name httpd-demo -p 8082:80 httpd:alpine
docker run -d --name tomcat-demo -p 8080:8080 tomcat:9-jdk17-temurin

等几秒后逐个看响应头,注意三个端口对应三种服务器:

for p in 8080 8081 8082; do
  echo "== port $p =="
  curl -s -o /dev/null -D - http://127.0.0.1:$p/ | head -n 8
done

预期输出差异:8081 的 Server 头是 nginx 加版本号;8082 是 Apache 加版本号;8080 通常不出现 Server 头,而且响应体是一个 Tomcat 自带的页面,说明请求是交给 Servlet 容器处理的。再补一步静态资源对比:把同一个 index.html 复制进三个容器的默认根目录(Nginx 是 /usr/share/nginx/html,httpd 是 /usr/local/apache2/htdocs,Tomcat 是 /usr/local/tomcat/webapps/ROOT),用 docker cp 拷入后用上面的 curl -w 命令各测一次,看 time_starttransfer 的量级差异;Tomcat 这一项要跑两遍再取值,第一遍包含 JVM 预热。

容易踩的坑:在 Nginx 里只写 proxy_pass 而不传头信息,Java 侧拿到的协议和主机名全是错的,重定向地址会指回内网端口。最小可用的转发写法是:

location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

验证方式是让应用打一行日志输出 request.getRequestURL() 和 request.getHeader("X-Forwarded-Proto"),或者直接 curl -I 看 Location 头的域名和协议对不对。边界在于:如果应用框架自己不读 X-Forwarded-*,加了头也不会自动生效,需要到框架的启动参数或配置里显式开启。