在设计负载测试方案时,Nginx往往被当成一条透明的数据通道,似乎只要后端能扛住压力,整条链路就不会出问题。实际情况并非如此:Nginx需要维护大量客户端连接,同时将请求转发到上游服务,连接建立、缓冲区拷贝、日志写入和响应回传都会消耗CPU与内存资源。如果压测时绕开Nginx直接打后端,等上线后流量先经过Nginx,性能表现可能会明显下降。本文围绕Nginx与JMeter的组合,说明如何制定一份可执行的负载测试计划,并给出关键配置和分析思路。

一、明确负载测试的目标与流量模型
任何压测之前都要先回答一个问题:你到底想验证什么。是验证Nginx作为静态文件服务器的吞吐上限,还是验证反向代理到后端时的转发能力,亦或是模拟HTTPS卸载后的性能损耗?不同目标对应的JMeter脚本结构和Nginx配置完全不同。举例来说,如果只压测静态资源,请求根本不会到达后端,上游连接池的参数就不会影响结果;如果压测动态接口,Nginx与后端的keepalive连接复用情况就会直接决定响应时间。
流量模型同样重要。生产环境中的请求不会所有都命中同一个URL,而是存在访问比例、请求方法差异和不同大小的响应体。使用JMeter时,可以借助随机控制器或加权开关控制器来模拟不同接口的调用比例,也可以把静态资源和动态接口按比例混合在一起,例如百分之七十的请求访问静态文件,百分之三十的请求访问后端接口。不要只用一个首页或一个登录接口做压测,那样的结果只能代表单接口性能,不能反映Nginx在多类型请求下的调度与缓存表现。
此外还要确定压测时长和并发递增方式。短时间突发流量容易发现连接建立问题,长时间持续负载则能暴露内存泄漏、日志增长和连接不释放等隐患。建议至少包含三个阶段:预热阶段逐步增加线程数,持续阶段保持固定并发观察稳定性,收尾阶段观察系统在流量下降后的恢复情况。JMeter原生线程组支持设置线程数、Ramp-Up时间和循环次数,但如果需要更精细的阶梯加压,可以安装Stepping Thread Group插件。
二、JMeter测试计划与Nginx关键配置
在JMeter中创建测试计划时,先添加线程组,再添加HTTP请求采样器。线程组中的线程数相当于并发用户数,Ramp-Up时间决定这些线程在多长时间内逐步启动完成。比如线程数设为200,Ramp-Up设为120秒,那么JMeter会在大约两分钟内将并发从0提升到200。循环次数可以勾选永远,配合调度器设置持续时间,这样测试时长更可控。
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0" jmeter="5.6.3">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Nginx负载测试计划" enabled="true">
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="用户定义的变量" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="压测线程组" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="循环控制器" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">10</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">200</stringProp>
<stringProp name="ThreadGroup.ramp_time">120</stringProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration"></stringProp>
<stringProp name="ThreadGroup.delay"></stringProp>
</ThreadGroup>
</hashTree>
</hashTree>
</jmeterTestPlan>
上面的XML片段展示了线程组的基础结构,实际生成时可以通过JMeter图形界面操作,保存后得到完整JMX文件。图形界面适合调试,真正执行压测时一定要切换到命令行模式,使用 jmeter -n -t 脚本.jmx -l result.jtl -e -o report 生成HTML报告。GUI模式会额外消耗客户端资源,高并发下甚至可能让测试机先成为瓶颈。
Nginx侧需要特别关注反向代理的连接处理。默认情况下,Nginx与客户端之间可以使用HTTP keepalive,但与上游服务器之间如果不显式配置,通常每次请求都会新建连接。对于高并发动态接口,这会引入大量TCP握手和TIME_WAIT状态。可以在upstream块中设置keepalive和proxy_http_version,并清理Connection头。
upstream backend_pool {
server 192.168.10.11:8080 weight=3 max_fails=2 fail_timeout=30s;
server 192.168.10.12:8080 weight=1 max_fails=2 fail_timeout=30s;
keepalive 64;
}
server {
listen 80;
server_name api.internal.test;
location /api/ {
proxy_pass http://backend_pool;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
}
location /static/ {
root /data/nginx;
expires 7d;
access_log off;
}
}
这里用 keepalive 64 表示每个worker进程与上游服务器之间最多保持64个空闲连接。实际压测时,可以通过调整这个值观察响应时间的变化。如果设置太小,连接复用率低,TIME_WAIT会迅速累积;如果设置太大,上游服务器可能被空闲连接占满。通常以Nginx与后端之间的实际并发量为参考,设置为并发的百分之十到百分之二十起步,再结合监控逐步调整。
三、监控Nginx与后端的关键指标
没有监控的压测只是一堆数字。至少要在测试期间持续采集Nginx所在机器的CPU使用率、内存占用、网络流量、磁盘IO和文件描述符数量。可以使用 top、vmstat、sar、ss -s 等命令快速查看。在Linux下,Nginx的连接数可以通过 ss -ant | grep :80 | wc -l 统计,但更推荐开启Nginx的stub_status模块,获取稳定的状态数据。
server {
listen 127.0.0.1:8080;
server_name status.local;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
访问这个地址会返回活跃连接数、已接受连接总数、已处理连接总数和请求总数。重点关注活跃连接数与已接受连接数的差值,如果已接受连接数远大于已处理连接数,说明连接在队列中积压,worker进程可能来不及处理。还可以借助nginx-module-vts模块获得更丰富的虚拟主机维度数据,方便区分静态请求与动态请求的流量。
JMeter侧则关注聚合报告中的几个核心指标:平均响应时间、中位数、百分之九十和百分之九十五响应时间、错误率、吞吐量。平均响应时间容易掩盖长尾问题,百分位指标更能反映真实用户体验。比如平均响应时间是300毫秒,但百分之九十五响应时间达到2秒,说明有部分请求遇到了排队或超时。错误率如果随着并发上升而突然增加,通常意味着Nginx或后端已经触到某个资源上限,需要立即查看系统日志。
如果需要实时观察压测曲线,可以在JMeter中添加后端监听器,将数据发送到InfluxDB,再用Grafana展示。这样可以看到每秒请求数、响应时间和错误率的动态变化,比翻看聚合报告更直观。如果只是临时排查,命令行模式配合生成的HTML报告已经足够,报告里包含了吞吐量、响应时间百分位和错误统计,不需要额外搭建监控栈。
四、根据压测结果优化Nginx与测试计划
拿到第一轮结果后,最常见的瓶颈往往不在Nginx本身,而在系统资源限制。比如文件描述符上限默认可能是1024,高并发下会出现 too many open files 错误。可以通过 ulimit -n 查看,并在启动脚本或systemd配置中提升到例如65535。Nginx的worker_connections参数也要同步调整,它决定每个worker进程能够同时处理的最大连接数。worker_processes 通常设置为auto,让Nginx根据CPU核心数自动创建worker。
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
gzip on;
gzip_types text/plain text/css application/json application/javascript;
}
如果静态资源占比较高,开启sendfile和tcp_nopush可以让Nginx直接在内核空间完成文件到网络的数据传输,减少用户态与内核态的拷贝。开启gzip虽然会消耗CPU,但能显著降低响应体积,在带宽受限场景下提升吞吐。对于动态接口,重点调整upstream keepalive、proxy_buffering和超时参数。对于响应体较大的接口,启用proxy_buffering可以让Nginx先把上游响应缓冲到本地,再按客户端速度转发,避免慢客户端拖住上游连接。
测试计划本身也需要根据结果迭代。如果发现测试机的CPU或网络先跑满,并非Nginx达到瓶颈,需要增加测试机或采用分布式压测。JMeter支持以主从模式运行多个负载生成器,通过主节点控制从节点同时发起请求。注意从节点之间要保证时钟同步,否则汇总结果时时间戳会偏移。最后,不要只做一次压测就下结论。每次修改Nginx参数或后端配置后,保持相同的测试脚本和并发模型重新执行,对比多轮数据,才能判断优化是否有效。