news 2026/9/29 17:48:44

nginx+tomcat组合实战:高并发部署与性能调优指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
nginx+tomcat组合实战:高并发部署与性能调优指南

接手过很多带tomcat的环境,几乎都能看到nginx站在前面顶着流量。我直接说结论:nginx和tomcat这对组合,是当前Java Web服务里最常见、最实用的一套骨架,nginx负责接住百万级连接、处理静态资源和转发请求,tomcat专注跑业务逻辑。这篇文章从架构原理讲到安装部署、参数调优、前后端分离落地、日志排障,新手照着做能少走弯路,老手看细节也有可参考的地方。

1. 先想明白:nginx和tomcat到底各自负责什么

1.1 nginx擅长的是“接客”

nginx本质是一个高性能HTTP和反向代理服务器,核心模型是事件驱动异步非阻塞。说人话就是:一个nginx worker进程能同时维持成千上万个连接,不会因为某个请求慢就卡住整条线程。静态文件比如html、css、js、图片的响应速度极快,因为它省去了动态解析环节,直接从磁盘或内存缓存里把文件吐给客户端。

nginx还能干很多“接客”的活:HTTPS证书卸载、gzip压缩、请求限流、IP封禁、负载均衡。这些都发生在业务代码之外,处理起来几乎不消耗应用服务器资源。

1.2 tomcat是“干活的”

tomcat是一个Servlet容器,也是标准Java Web应用的运行环境。Servlet、JSP、Spring Boot打出的war包,最终都是运行在tomcat这类容器里。它内部基于线程池处理请求,每个请求占用一个线程,触达业务代码后由你的Spring、Dubbo或者其他框架去完成真正的数据计算和逻辑返回。

tomcat的并发能力受JVM堆内存、线程池大小、数据库连接池多重限制,它的强项是业务处理,弱项恰恰是静态资源:每次静态请求都要占一个tomcat线程,流量一多,真正需要计算的业务线程反而被饿死。

1.3 为什么必须把nginx摆在tomcat前面

我画过很多次这两个角色的对比,核心差异可以用一张表说清楚:

维度nginxtomcat
静态资源异步IO,性能强悍线程池模式,浪费线程
并发模型单进程可维持数万连接受JVM和线程池限制
HTTPS终结配置简单,性能稳定也能配,但维护成本高
负载均衡原生upstream支持需要额外组件
故障隔离上游挂了可快速切换单点崩溃直接影响业务

所以标准架构是:外部请求先进nginx,nginx能自己处理的静态资源直接返回,处理不了的动态请求转发给后端的tomcat集群。tomcat的线程不至于被静态文件拖垮,nginx的并发能力又能扛住公网流量。这一层设计解决的核心问题是资源隔离和分层解耦。

有人问单台tomcat直接暴露公网行不行,小流量没问题,但一旦并发上百,CPU先飙起来的就是tomcat的线程调度和静态文件IO。nginx挡在前面最大的价值就是让tomcat只处理该处理的事。

2. nginx 安装实战:编译安装并没有那么神秘

2.1 编译前先把依赖备齐

我推荐编译安装而不是直接用发行版自带的nginx包,原因是编译安装能自己控制目录、模块,后续平滑升级和排查问题都方便。版本我建议选1.24.x以上稳定版,老版本有几个已知缓冲区漏洞,比如CVE-2022-41742在低版本里就存在,生产环境尽量避开。

编译安装前需要准备的依赖:

  • gcc、make:C编译工具链
  • pcre-devel:rewrite模块正则依赖
  • zlib-devel:gzip压缩模块依赖
  • openssl-devel:HTTPS支撑

CentOS系一条命令装齐:

yum install -y gcc make pcre-devel zlib-devel openssl-devel

如果机器是aarch64并且纯内网环境,没法直接拉在线包,就提前在有外网的同样架构机器上把依赖rpm下载好,打包拷进去用yum localinstall *.rpm装。注意架构必须一致,x86_64的rpm在aarch64上装不了。

2.2 configure、make、make install 全流程

源码解压后进入目录,先执行configure生成Makefile:

./configure \ --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_gzip_static_module

这几个参数逐个解释一下:

  • --prefix指定安装目录,统一管理,卸载也干净
  • --with-http_ssl_module是HTTPS必须的,漏掉之后想补就要重新编译
  • --with-http_stub_status_module提供/nginx_status监测页面,线上排查并发很有用
  • --with-http_gzip_static_module支持预压缩静态资源

然后编译安装:

make -j4 && make install

-j4表示并行编译,如果你的机器是4核就用-j4,8核可以-j8,能明显缩短编译时间。编译过程不要着急,看到最后生成/usr/local/nginx/sbin/nginx就算成了。

验证安装:

/usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx

nginx -t只测试配置语法不会真正启动,新手务必养成先-t再启动的习惯。启动后浏览器访问机器IP,能看到Welcome to nginx页面就说明基础环境OK了。

2.3 基础配置里的高并发调参

打开/usr/local/nginx/conf/nginx.conf,核心配置我建议这样起步:

worker_processes auto; events { worker_connections 10240; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; client_max_body_size 20m; gzip on; gzip_types text/plain text/css application/json application/javascript; }

worker_processes auto会自动探测CPU核数并启动相同数量的worker进程,每个worker能维持worker_connections个连接,整体并发上限就是两者相乘。注意这个上限是理论值,还要考虑文件描述符限制,必要时在/etc/security/limits.conf里把nofile调大。

client_max_body_size 20m不说清楚很多人会踩:nginx默认限制客户端上传体积1M,不加这个配置,上传文件超过1M直接返回413。调成多少取决于业务,一般20M够用,视频类业务再往上加。

gzip on是白给的性能优化,开启后nginx在返回静态资源时自动压缩,前端传输体积能省一半以上。但我建议同时开启gzip_static配合预压缩文件,能省掉请求时的实时压缩CPU开销。

2.4 平滑升级的正确姿势

我在升级nginx时踩过坑,直接kill老进程再启动新的,结果线上瞬间502。nginx的平滑升级是利用旧进程处理完存量连接后自动退出的机制:

# 备份老二进制 mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old # 拷贝新编译好的二进制 cp /path/to/new/nginx /usr/local/nginx/sbin/nginx # 发送USR2信号,启动新worker进程 kill -USR2 `cat /usr/local/nginx/logs/nginx.pid`

新进程起来后会接管新连接,旧进程把存量请求处理完再退出。整个升级过程理论上不中断服务,升级完务必观察几分钟接口返回情况,有问题再回滚。

3. tomcat 部署与运行优化:配置细节决定服务质量

3.1 下载、安装、目录结构一次说清

tomcat下载认准Apache官网,但国内从官网拉文件经常很慢,我一般用国内镜像,速度快多了。版本选择看项目需要,Spring Boot内嵌tomcat无需额外安装,传统war包部署用tomcat 9或10都行。

解压到/opt/tomcat后,目录结构必须烂熟于心:

  • bin:startup.sh、shutdown.sh、catalina.sh都在这里
  • conf:server.xml、web.xml、logging.properties等核心配置
  • lib:运行所需的jar包
  • logs:catalina.out、localhost.log、access.log等日志
  • webapps:war包解压目录
  • work:JSP编译临时目录

启动tomcat直接执行bin/startup.sh,关闭用bin/shutdown.sh。千万别用kill -9直接杀tomcat进程,可能会导致文件缓存和数据不一致。

3.2 乱码问题的根源与根治方案

tomcat乱码是出现频率最高的怪现象,归纳下来其实是两个层面的问题:

第一层是控制台和日志乱码,根因是JVM默认编码和系统编码不一致。需要改两个地方:

在conf/logging.properties里确认:

java.util.logging.ConsoleHandler.encoding = UTF-8

在bin/catalina.sh的JAVA_OPTS里加:

-Dfile.encoding=UTF-8

第二层是请求参数乱码,尤其是GET请求的查询参数,在conf/server.xml中给Connector节点加:

URIEncoding="UTF-8"

三层都改一遍,乱码基本能根治。需要说明的是,如果你用的恰好是tomcat 8.0.5之前的版本,默认编码可能不是UTF-8,一定要显式配置。

3.3 JVM参数这样调才不迷路

打开bin/catalina.sh,找到JAVA_OPTS区域,我生产环境常用的模板:

CATALINA_OPTS="-Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m -Djava.awt.headless=true -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/tomcat/logs"

几个参数的设定逻辑:

-Xms和-Xmx建议设成一样,防止JVM运行期频繁扩容堆内存导致停顿。大小取决于机器物理内存和预期并发,我习惯堆内存不超过物理内存60%,剩余留给Metaspace、线程栈和操作系统page cache。比如8G机器给tomcat 4G堆,留4G给系统。

-XX:MaxMetaspaceSize控制类元数据上限,Spring Boot时代的类加载很多,不给上限容易碰到“Metaspace OutOfMemory”。

-Djava.awt.headless=true是服务器环境必加项,防止某些图表生成、图片处理业务在无图形界面环境报错。

-XX:+HeapDumpOnOutOfMemoryError是保命配置,OOM时自动生成dump文件,排障没有这个dump会非常被动。

调好后重启tomcat,用jmap -heap pid确认生效。

3.4 systemd 管理 tomcat 开机自启

linux上设置tomcat自启动,最省事的做法是用systemd。我见过有人在/etc/rc.local里塞startup.sh,也能跑,但服务异常退出不会自动拉起,rc.local本身也不被新版系统默认加载了。

创建/etc/systemd/system/tomcat.service:

[Unit] Description=Apache Tomcat Web Application Container After=network.target [Service] Type=simple Environment="JAVA_HOME=/usr/local/java" ExecStart=/opt/tomcat/bin/catalina.sh run ExecStop=/opt/tomcat/bin/shutdown.sh Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target

注意一个关键细节:ExecStart直接写startup.sh会以子进程方式启动tomcat,systemd会丢失对主进程的跟踪,服务挂了没法自动拉起。必须用catalina.sh run前台模式,让tomcat进程成为systemd直接管理的进程。

启动并设置开机自启:

systemctl daemon-reload systemctl enable tomcat systemctl start tomcat

看到systemctl status tomcat显示active (running)就可以放心了。

4. 前后端分离项目部署:nginx 配置才是重头戏

4.1 静态资源直接让nginx吐给用户

前后端分离项目,前端构建产物通常是一整个dist目录,index.html和按路由拆分的js、css、图片都在里面。nginx配置只需要:

server { listen 80; server_name app.example.com; root /data/www/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }

这段配置的重点是try_files。前端用vue-router或react-router的history模式时,访问比如/user/1这个路径,后端没有任何真实文件对应,不加try_files直接404。加上try_files $uri $uri/ /index.html,nginx会把不存在的路径统一引导到index.html,由前端路由自己接管页面渲染。

另外给静态资源加缓存策略,性能提升非常明显:

location ~* \.(js|css|png|jpg|gif|svg)$ { expires 30d; add_header Cache-Control "public, immutable"; }

4.2 API反向代理的路径陷阱

前端和后端分离后,动态接口需要通过nginx转发到tomcat。基础配置:

location /api/ { 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; proxy_read_timeout 60s; }

proxy_set_header这几行一定要保留完整,否则后端拿不到真实的客户端IP,统一显示成nginx的内网地址,日志分析和限流都会失真。X-Forwarded-Proto用于后端判断原始请求是HTTP还是HTTPS。

最容易踩坑的是proxy_pass末尾的斜杠:proxy_pass http://127.0.0.1:8080;不带斜杠时,转发到后端的是完整URI/api/user/list;proxy_pass http://127.0.0.1:8080/;带斜杠时,/api/这个前缀会被吞掉,后端收到的路径变成/user/list。具体用哪种取决于后端接口有没有加/api前缀,配置完一定要用curl实测一遍,这是个只看配置很难察觉的细节。

4.3 upstream 实现多实例负载均衡

后端做集群时,nginx的upstream模块是最直接的负载均衡方案:

upstream tomcat_pool { server 127.0.0.1:8081 weight=1; server 127.0.0.1:8082 weight=1; keepalive 32; } server { location /api/ { proxy_pass http://tomcat_pool; } }

默认轮询策略,weight控制流量比例。生产环境我建议加keepalive参数,开启nginx到tomcat的长连接,减少频繁TCP握手开销。

如果业务依赖session共享,可以先在upstream里用hash $request_uri保证同一个URL固定打到同一台tomcat,但我强烈建议把session迁到redis,让tomcat随便扩缩容都无状态,这才是后端集群的正确打开方式。

4.4 HTTPS证书配置与常见证书文件来源

现在线上基本全面HTTPS,证书在godaddy这类服务商买的证书,下载的时候选nginx格式,拿到的是一串.pem/.crt证书文件和.key私钥文件。nginx配置:

server { listen 443 ssl; server_name app.example.com; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.key; ssl_protocols TLSv1.2 TLSv1.3; }

配置完别忘了把80端口跳转到443:

server { listen 80; server_name app.example.com; return 301 https://$host$request_uri; }

证书到期是这个环节最常见的故障源,我的做法是写一个定时任务每个月检查一次证书剩余天数,提前提醒续期。很多公司事故就是证书静默过期,用户访问直接安全告警。

5. 日志分析与高频问题排查实录

5.1 从日志链路快速缩小问题范围

一套nginx+tomcat环境,日志分两段看:nginx的access.log和error.log对应入口层,tomcat的catalina.out和应用日志对应业务层。我排查问题的固定顺序是:

先看nginx error.log,确认有没有上游连接失败;再看tomcat catalina.out,确认应用是否正常启动和报错;最后看应用业务日志,定位具体代码异常。

看access.log状态码分布可以用一条awk统计:

awk '{print $9}' /data/www/logs/access.log | sort | uniq -c | sort -rn

结果里大量502、503、504基本锁定是网关层问题,大概率是tomcat挂了、连接数被打满或响应超时。大量499是客户端提前断开,用户等不及刷新页面。

5.2 高频报错速查表

我把实际运维中遇到最多的几类问题整理成表,基本能覆盖日常80%的排障需求:

现象定位方向常见解法
502 Bad Gatewaytomcat进程崩了或端口不通检查tomcat状态和catalina.out
504 Gateway Timeouttomcat响应超过nginx等待时间调大proxy_read_timeout
404nginx路径和真实文件对不上检查root、alias、location匹配
413 Request Entity Too Large上传体积超nginx限制调大client_max_body_size
请求中文乱码编码链路不一致按第3章三步统一UTF-8
定期503后端连接池被打满jstack看阻塞线程和连接池配置

5.3 一起诡异502的完整复盘

说一个我印象深刻的线上排查过程。某系统白天正常,每到晚高峰就偶发502,nginx error.log里大量connect() failed (111: Connection refused) while connecting to upstream。

我的第一反应是tomcat挂了,但systemctl status tomcat显示active,端口也是LISTEN状态。继续翻catalina.out,发现大量等待数据库连接的异常。再往后看,tomcat线程全部阻塞在获取连接上,数据库连接池被打满了。

根源不在tomcat本身,而是数据库连接池maxActive设太小,晚高峰业务请求一上来,连接耗尽,线程全卡住,最终tomcat无法接收新连接,nginx只能向上游报502。最后方案是调大连接池、给慢SQL加索引、加了一层缓存,问题彻底消失。

这次复盘给我一个很重要的经验:nginx报502时不要只查nginx和tomcat,tomcat默默运行不代表它没被下游拖垮,要顺着调用链往下游继续看。

5.4 开发环境配置tomcat的两个高频问题

很多人在开发环境也被tomcat折腾过,最常见两个问题。

第一个是IDEA或vscode里配置tomcat后启动报源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的,这个看着像后端错误其实多半是访问路径问题:URL路径和后端Servlet的映射对不上,要么项目没部署成功,要么访问的context-path不对。先查看IDEA的deployment配置确认Application context,再核对浏览器访问的URL。

第二个是tomcat闪退。Linux下执行startup.sh闪退基本是环境变量问题,最常见是JAVA_HOME没设置,启动脚本直接退出。用bin/catalina.sh run前台模式跑一遍,错误信息会直接打在屏幕上,比看日志更快。

几点维护心得

最后分享几个长期运维下来的经验。

nginx配置改动后一定先nginx -t再reload,tomcat同样先检查再重启,这条几乎能拦住一半以上的低级事故。把nginx的stub_status监控页面向监控系统开放,采集活跃连接数和accepts/handled计数,流量冲上来之前这些指标会先有动静。日志保留周期至少30天,出问题时要能往回翻历史。

nginx和tomcat这套架构短期内不会过时,把两者的安装、配置、调优、排障吃透,后面不管是容器化部署还是接入服务网格,你都会发现很多底层思路是通的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 17:48:25

大型Java项目为什么无人敢改?从风险识别到渐进式重构的破局之路

凌晨两点四十七分,线上支付成功率掉了三个点。我盯着告警群里连番弹出的消息,打开 IDEA,跳进一个跑了快十年的 Spring 模块里,然后——卡住了。不是卡在编译,也不是卡在环境,而是卡在“到底要不要动它”这个…

作者头像 李华
网站建设 2026/9/29 17:48:17

JMM Happens-Before规则详解:从原理到实战,彻底解决并发可见性

1. 先搞清楚 JMM 到底在解决什么问题 在聊 Happens-Before 之前,得先掰扯清楚 JMM(Java 内存模型)这玩意到底是干嘛的,不然你只记规则,转身就忘,面试被深挖照样懵。 JMM 是一套抽象的内存访问规范&#xf…

作者头像 李华
网站建设 2026/9/29 17:46:56

海康MVS参数配置避坑指南:工业视觉落地关键12参数

1. 为什么MVS参数配置是工业视觉落地的第一道坎海康视觉相机驱动软件MVS,不是个普通工具,它是连接物理世界与数字图像处理系统的“神经突触”。我第一次在汽车焊装车间调试MV-CH200系列面阵相机时,整整两天卡在“曝光时间设为5000μs却始终拍…

作者头像 李华
网站建设 2026/9/29 17:44:48

ComfyUI 从入门到进阶:节点式 AI 画图工作流实战指南

1. 为什么我最终把主力画图工具换成了 ComfyUI第一次接触 AI 画图,很多人都是从那种“输入一句话、点一下按钮、等一张图”的网页工具开始的。方便是真方便,但用久了就会撞到天花板:想固定一个角色的脸,想批量出一组风格统一的图&…

作者头像 李华
网站建设 2026/9/29 17:43:20

给Agent加定时任务:dsh不做Cron,我是这样解决的

给 Agent 加定时任务:dsh 只给了三个工具,而且故意不做 Cron最近在跟一个 Agent 项目较劲,项目本身用了一套叫 dsh 的 Agent 编排框架。上手第一感觉是:干净,真的干净,框架只给了 Agent 三个工具&#xff0…

作者头像 李华
网站建设 2026/9/29 17:41:54

Windows下CMake 3.29.3安装配置与避坑指南

简介:CMake 3.29.3 的 Windows x86_64 预编译版,面向 C/C 开发者,用于跨平台构建管理。它不直接编译,而是为 Visual Studio、Ninja 或 Makefile 生成构建输入,适合维护多平台项目的开发者或构建系统初学者。压缩包内含…

作者头像 李华