TOMCAT与NGINX的组合可以说是Java Web开发中最经典的架构搭配之一,但在实际接触之前,很多人并不清楚这两个东西各自到底负责什么,甚至有人认为它们是可以互相替代的产品。事实上,Tomcat是一个Servlet容器,负责运行Java应用,而Nginx是一个高性能的HTTP服务器和反向代理,两者搭配起来才能发挥出最大的价值。这篇文章会从架构原理讲到具体配置,再聊聊长期使用中的一些真实体会。

为什么Tomcat前面要加一层Nginx
先说清楚一个核心问题:为什么不直接把Tomcat暴露给外网?Tomcat本身是可以直接处理HTTP请求的,它内置了HTTP连接器,理论上完全可以独立对外服务。但Tomcat处理静态资源的效率并不高,它的强项在于执行Servlet和JSP这类动态逻辑。如果让Tomcat去扛图片、CSS、JS这些静态文件的请求,线程池很快就会被占满,导致动态请求排队等待,整体响应变慢。
Nginx刚好相反,它采用事件驱动的异步非阻塞模型,用少量的进程就能处理数以万计的并发连接,处理静态文件的性能远超Tomcat。有测试数据表明,在纯静态文件场景下,Nginx的吞吐量可以达到Tomcat的数倍。所以把静态资源交给Nginx,动态请求通过反向代理转发给后端的Tomcat,这种动静分离的方式能显著提升整体处理能力。
除了性能,Nginx还带来了几个额外的好处。第一是安全性,Tomcat不直接暴露在公网,攻击面大大缩小;第二是负载均衡能力,当一台Tomcat撑不住时,可以在Nginx后面挂多台Tomcat分摊压力;第三是Nginx可以作为SSL终端,统一处理HTTPS加密解密,让Tomcat只处理明文请求,减轻后端负担。
动静分离与反向代理的配置实践
下面通过一个实际可用的配置来演示这套架构怎么搭。假设Tomcat运行在8080端口,应用部署在webapps目录下,我们希望Nginx监听80端口,静态文件直接由Nginx返回,其他请求全部转发给Tomcat。
server {
listen 80;
server_name www.ipipp.com;
# 静态资源由Nginx直接返回
location ~* \.(html|css|js|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
root /data/www/static;
expires 7d;
add_header Cache-Control "public";
}
# 动态请求转发给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_connect_timeout 5s;
proxy_read_timeout 60s;
}
}这个配置里有几个细节值得注意。proxy_set_header那几行非常关键,如果不设置Host和X-Real-IP,Tomcat收到的请求里客户端IP会变成127.0.0.1,应用日志记录和权限校验都会出问题。Tomcat那边需要在server.xml中配置RemoteIpValve,配合X-Forwarded-For头还原真实客户端地址。
静态资源路径要与Tomcat应用里的资源引用路径对应好。一种常见做法是把静态文件从应用的war包里抽出来,单独放到Nginx的目录下,应用里引用时使用统一的路径前缀。expires 7d设置了七天缓存,配合CDN使用效果更好。上传文件的大小限制也要留意,Nginx默认限制请求体1MB,上传大文件时需要在http块中调整client_max_body_size。
负载均衡与会话保持的实现
当单台Tomcat无法满足并发需求时,横向扩展就提上日程了。Nginx的负载均衡配置非常简洁,下面是一个包含健康检查和会话粘性的完整示例。
upstream tomcat_cluster {
# ip_hash实现会话粘性,同一客户端固定打到同一台
ip_hash;
server 192.168.0.11:8080 weight=2 max_fails=3 fail_timeout=30s;
server 192.168.0.12:8080 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.0.13:8080 backup;
}
server {
listen 80;
location / {
proxy_pass http://tomcat_cluster;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}weight参数控制权重,配置高的机器分到更多请求;max_fails和fail_timeout组合实现了被动的健康检查,某台Tomcat在30秒内失败3次后会被暂时摘除。ip_hash根据客户端IP做哈希,保证同一用户始终访问同一台Tomcat,解决了普通Session不能跨服务器共享的问题。
不过ip_hash并不是完美方案。如果用户切换了网络环境,IP变了,会话就会丢失,而且当后端某台机器下线时,哈希分布需要重新调整。更稳妥的做法是应用层面做Session共享,比如使用Spring Session把Session存到Redis,这样负载均衡策略就可以随意选择,不再受粘性限制。短连接、轮询的策略配合共享Session,是目前比较主流的方案。
长期使用的体会与调优建议
这套架构用了几年之后,有一些经验值得分享。首先是Tomcat参数调优,默认的连接器配置偏保守,高并发场景下建议调整maxThreads(工作线程数,一般200到500之间)、maxConnections(最大连接数,NIO模式下可以设到10000)以及acceptCount(等待队列长度)。连接数不够时,日志里会出现拒绝连接的错误,这是最直接的信号。
其次是gzip压缩的分工问题。建议在Nginx层统一开启gzip,而不是Tomcat,因为Nginx压缩效率更高,配置也更灵活。文本类资源开启压缩后体积通常能减少百分之七十左右,对页面加载速度的提升非常明显。但要注意图片这类已经压缩过的格式不要再压缩,白白消耗CPU。
最后聊聊故障排查。这套架构出现问题时,第一步是分清问题出在哪一层:curl直接请求Tomcat端口看应用是否正常,再curl Nginx看代理是否通畅,用nginx -t检查配置语法,看error.log定位具体错误。常见的坑包括Tomcat启动慢导致Nginx健康检查摘除节点、proxy_read_timeout过短导致长接口被截断、以及后端返回大响应体时没有开启缓冲区等。总体来说,这套组合的学习成本不高,稳定性和性能表现都经过了大量生产环境验证,是中小型Java Web应用非常值得选择的架构方案。