1. 优化前必须想清楚的事:你的Nginx到底慢在哪
接手OpenEuler系统上的Nginx性能调优,很多人第一反应就是去改配置、加参数,我在实际项目里见过太多这种操作。某次线上压测,一位同事直接把worker_processes改成128,结果负载不降反升,CPU在中断处理上烧掉一大半——这事儿说明一个道理:调优前提是定位瓶颈,不是堆配置。
我需要先明确一个概念:Nginx性能优化不是单点问题。它涉及系统内核参数、Nginx自身配置、后端服务处理能力、网络链路质量四个层面。你在OpenEuler上装好Nginx、能跑通业务,这只是起点,离“高性能”还差着十万八千里。
1.1 先做压测,画出性能基线
动任何配置之前,先花半小时测出当前系统的基准数据。我推荐的压测工具是wrk或者ab,注意压测机和Nginx服务器要分开部署,否则压测请求本身会消耗被测试机的CPU和网络资源,数据完全失真。
我常用的压测命令是:
wrk -t8 -c100 -d60s --latency http://your-server-ip/test.html重点看三个指标:QPS(每秒请求数)、平均延迟、P99延迟。压测完成后记录一份基线数据,后续每改一个参数就重测一次,对比数据变化。没有基线的调优,等于闭着眼睛开车。
1.2 看清硬件底子再定策略
OpenEuler系统跑的机器配置千差万别。先执行lscpu看核数、free -h看内存、df -h看磁盘类型,再用iostat确认磁盘IO能力。我遇到过一台机器磁盘IO能力弱,静态文件读取直接成为瓶颈,这时候怎么调Nginx参数都白搭,只能上缓存或者换存储。
CPU核数是Nginx worker进程数配置的核心依据,不是随便填的数字。内存大小决定你能开多大的文件缓存、多长的keepalive连接池。磁盘类型决定静态文件的高性能策略——SSD和机械盘的优化方向完全不同。把这些信息记在纸上,心里有数再动手。
2. OpenEuler系统底层调优:内核参数决定天花板
很多人调Nginx只看nginx.conf,忽略了操作系统层面。实际上,大量连接卡住、TIME_WAIT堆积、文件句柄耗尽,这些问题根源都在内核参数。OpenEuler基于Linux内核,参数体系和其他主流发行版基本一致,但有几个默认值需要特别留意。
2.1 文件句柄限制:连接数上限的隐形阀门
Nginx处理每个连接都要占用一个文件句柄(file descriptor),系统默认的1024上限意味着你的Nginx最多同时处理几百个连接。我在生产环境踩过这个坑——压测一上量,日志里全是“too many open files”,Nginx直接拒绝服务。
调整分两步。第一步改系统级限制,编辑/etc/security/limits.conf,追加:
* soft nofile 65535 * hard nofile 65535第二步在Nginx配置里同步提升worker进程的句柄上限:
worker_rlimit_nofile 65535;这里多说一句:worker_rlimit_nofile的值要略小于系统nofile限制,否则Nginx启动时会报错。改完必须重启Nginx进程才生效,不是reload。
2.2 TCP栈参数:突破连接和端口的限制
高并发场景下,服务器要处理大量短连接。默认的TCP参数往往不够用,我常用的内核参数调整如下,写入/etc/sysctl.conf:
net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 0 net.ipv4.tcp_fin_timeout = 30 net.ipv4.ip_local_port_range = 1024 65535 net.core.somaxconn = 65535 net.core.netdev_max_backlog = 65535逐条解释背后的逻辑:
tcp_tw_reuse=1:允许客户端复用TIME_WAIT状态的连接。压测时大量短连接会导致TIME_WAIT堆积,连接数一多,端口就被占满了。但注意tcp_tw_recycle在较新的内核里已经废弃,开启会导致NAT环境连接异常,务必设置为0。ip_local_port_range扩大本地端口范围,避免端口耗尽。默认值经常从32768开始,压测时成千上万的外部连接打进来,本地端口很快被耗光。somaxconn和netdev_max_backlog分别控制TCP连接队列长度和网卡接收队列长度,高并发下默认值128的somaxconn会让accept队列溢出,表现是客户端连接超时或重置。
改完之后执行sysctl -p生效。这个环节我在多个项目上验证过,效果立竿见影,压测时TIME_WAIT堆积导致连接失败的问题基本消失。
2.3 CPU亲和性和网卡多队列:让中断分摊出去
Nginx默认情况下,内核会把网卡中断集中丢给某一个CPU核心处理,导致单个核心满载、其他核心闲着。这种现象在单队列网卡上尤其明显。
解决方法有两个方向。一个是设置worker_cpu_affinity,让每个Nginx worker进程绑定到固定的CPU核心,减少进程在核心间切换的开销。配置方式是在nginx.conf中指定:
worker_processes 4; worker_cpu_affinity 0001 0010 0100 1000;这里四个掩码分别对应前四个CPU核心。我的经验是,多核机器上绑定CPU能减少约10%的开销,性能损耗主要来自上下文切换。
另一个方向是网卡多队列。检查网卡是否支持RSS(Receive Side Scaling):
ethtool -l eth0如果Combined队列数大于等于CPU核数,就用ethtool -L eth0 combined N把队列数设置为CPU核数,让各队列的中断分散到不同核心。开启RPS(Receive Packet Steering)也能在软件层面分摊软中断处理,但配置相对复杂,生产环境看需求选择。
3. Nginx配置层性能调优:参数背后是取舍
系统层改完,真正的重头戏在Nginx配置。这个环节我不建议直接抄网上的“最佳配置”,因为每个业务场景的模型不同——静态文件服务和反向代理的优化方向差异很大,必须先搞清楚自己属于哪一种。
3.1 worker进程数和连接数:不是越大越好
worker_processes最稳妥的设置是等于CPU核心数。早些年有观点说设为核数的两倍更好,实际压测表明,Nginx是事件驱动模型,worker进程数超过核数反而增加上下文切换开销,收益为负。
我常用的设置方案:
worker_processes auto; worker_cpu_affinity auto;OpenEuler上Nginx支持auto参数自动检测核数,配置更省心。如果机器还跑着其他核心服务,建议手动设置为核数-1,给系统调度留出余地。
worker_connections指的是每个worker进程能同时打开的最大连接数,Nginx官方建议公式是:
events { worker_connections 10240; use epoll; multi_accept on; }理论上最大并发连接数 = worker_processes × worker_connections,前提是系统文件句柄上限足够。use epoll是Linux高并发场景下最有效的事件驱动模型,OpenEuler默认支持。multi_accept on让worker进程一次性接受所有新连接,而不是一个一个来,压测下吞吐能提升不少。
这里有个细节:worker_connections不是随便拉高就行的。每个连接背后都有内存开销,连接数拉满而业务处理跟不上,内存直接告急。我用压测的方式逐步调整,比如先设10240、压测观察内存和延迟,再决定要不要上调。
3.2 静态文件处理:sendfile和open_file_cache是主角
如果业务以静态文件为主,Nginx的静态文件处理能力通常直接决定性能上限。核心配置如下:
sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65;sendfile开启后,Nginx直接从磁盘到网卡传输文件,省去用户态和内核态之间的数据拷贝。这个过程可以类比成快递直发——不经过中转站,时间自然缩短。
open_file_cache是很多人忽略的参数,它缓存文件元数据(路径、大小、修改时间),减少重复打开文件的开销:
open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on;这几个参数的含义分别是:缓存最多10000个文件信息,20秒内未被访问的条目淘汰;每30秒检查一次缓存条目是否过期;文件被访问2次以上才缓存。这部分对高流量静态资源站点效果特别明显,我在一个图片服务上实测,配置后CPU占用率下降了约30%。
3.3 gzip压缩:带宽换CPU,还能提升首屏速度
带宽小的场景,开启gzip能让传输体积缩小60%-80%,尤其对文本类资源效果显著,但压缩消耗CPU,需要在两者之间平衡。
gzip on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss image/svg+xml;gzip_comp_level是最值得注意的参数,5是性能和压缩率的平衡点,6以上压缩率提升不明显,CPU消耗却成倍增加。gzip_min_length 1k是防止压缩小文件——小于1KB的文件压缩后体积反而更大,得不偿失。
另外,如果客户端通过代理访问,还需要在gzip_http_version 1.1和Vary头方面测试确认,否则可能出现压缩不生效或重复压缩的问题。
3.4 连接超时和keepalive:连接复用大幅提升吞吐
Nginx处理HTTP短连接时,每次请求都要经历TCP三次握手和四次挥手,连接建立和释放的耗时占比很重。开启keepalive让客户端复用TCP连接,是提升吞吐性价比最高的操作之一。
keepalive_timeout 65; keepalive_requests 1000;keepalive_timeout 65是服务端关闭空闲连接前的等待时间,单位是秒。这个值太小,连接被频繁关闭,复用率低;太大则占用文件句柄资源,timeout单位是秒,65是一个常用平衡点。
keepalive_requests是单条连接上最多能处理的请求数,默认100不够用,高并发场景建议提升到1000左右。连接被复用越充分,握手次数越少,CPU开销越小。这个参数在压测中的提升效果,经常会让第一次接触的人惊讶。
4. 反向代理场景优化:upstream配置不能照抄默认值
如果Nginx承担的是反向代理角色,性能瓶颈往往不在Nginx本身,而在Nginx和后端服务之间的连接管理、超时控制和负载均衡策略。这块的优化空间非常大,我踩过的坑也最多。
4.1 upstream keepalive:连接池让后端喘口气
很多人在upstream配置里只写几个server地址,然后就不管了。默认情况下,Nginx每个请求都会向上游重新建立TCP连接,压测时后端服务器被握手请求淹没,表现为CPU飙升但业务处理量却不高。
正确的做法是配置upstream连接池。首先在http块内设置:
upstream backend { server 10.0.0.1:8080 weight=3 max_fails=2 fail_timeout=10s; server 10.0.0.2:8080 weight=2 max_fails=2 fail_timeout=10s; keepalive 32; }然后在location里协调好请求头:
location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; }第proxy_http_version 1.1是关键,keepalive需要HTTP/1.1协议支持。proxy_set_header Connection ""是清空Connection头,让上游连接保持复用。
keepalive 32表示每个worker进程缓存32个上游连接。这个值不用太大,连接池太大占内存、太小缓存命中率低,32在绝大多数场景都够用。配置完成后,后端服务的CPU占用会明显下降,因为不再频繁创建和销毁TCP连接。
4.2 超时重试和负载均衡策略:区分业务场景
反向代理场景下,超时设置直接影响用户体验和服务器压力。四个关键超时参数,含义容易混淆但实际分工明确:
proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s;proxy_connect_timeout是Nginx和后端建立连接的超时时间,太短容易在高峰时报504;proxy_read_timeout是两次读操作之间的间隔超时,不是总耗时上限,这个很多人理解错;proxy_send_timeout类似是写操作间隔超时。
负载均衡策略方面,默认是轮询(round-robin),后端服务能力不一致时,需要按权重调整(weight参数)。least_conn策略适合上游服务处理能力不均的场景,会把请求分发给当前连接数最少的服务器。
我处理高并发接口时,还会加一层主动健康检查。Nginx自带的max_fails和fail_timeout是被动检查——请求失败次数超阈值后,标记该服务器为不可用。这块建议配合upstream块的zone指令,在主备切换和故障隔离时体验会好很多。
4.3 SSL性能优化:如果走HTTPS,这步是最大的性能杠杆
越来越多的业务全站HTTPS,但SSL握手开销远超很多人的预期。压测时经常看到,HTTP跑10万QPS的机器,加了HTTPS直接掉到2万。优化SSL性能,是所有使用HTTPS业务的重中之重。
ssl_protocols TLSv1.2 TLSv1.3; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_buffer_size 4k; ssl_session_tickets on;ssl_session_cache是SSL会话缓存,分布式环境中推荐共享缓存,10m可以缓存大约几万个会话。会话复用后,客户端在缓存期间内重新连接时跳过完整握手,直接把握手时间省掉。
ssl_session_timeout 10m控制会话有效期。太短则缓存命中率低、加速效果不明显;太长则存在安全风险。10-30分钟是常用区间。
ssl_buffer_size 4k控制SSL发送数据大小。改为4k可以减少每条记录的填充开销,在响应体较小的API场景里提升明显。另外,如果证书链完整、后端能响应OCSP请求,一定要开启ssl_stapling on,这会把证书状态验证从客户端负担转移到服务端,减少一次网络往返。
5. 压测复盘与问题排查实战记
参数改完不算结束,我的习惯是再跑一轮完整压测,对比基线和调优后的数据。有次在OpenEuler系统上做优化,基线QPS 3万,调优后跑到7.5万,延迟还降了一半。但这个数字不是凭空来的——中间经历了几轮问题和调整。
5.1 三个高频问题速查表
| 现象 | 可能原因 | 排查方法 | 解决措施 |
|---|---|---|---|
| 连接数一高就报too many open files | 文件句柄限制未调 | ulimit -n、cat /proc/sys/fs/file-max | 调limits.conf和worker_rlimit_nofile |
| 压测大量TIME_WAIT状态连接 | TCP栈参数不合适 | netstat -an | grep TIME_WAIT | wc -l | 开启tcp_tw_reuse、调小tcp_fin_timeout |
| worker进程CPU占用不均 | CPU亲和性未设 | top按1查看各核使用率 | 配置worker_cpu_affinity |
| QPS提不上去、延迟忽高忽低 | keepalive未开启 | 抓包确认是否有频繁TCP握手 | 配置upstream keepalive |
| 偶发502/504错误 | 后端超时或连接池耗尽 | 查error.log、后端日志 | 调proxy_connect_timeout、加大keepalive连接池 |
这个表是我踩坑的浓缩版,建议收藏。遇到问题先查表,能少走很多弯路。
5.2 压测过程中的两次教训
第一次教训是压测目标接口选择失误。我当时压的是PHP动态页面,结果QPS上不去,调整各种Nginx参数都无效。后来发现瓶颈在PHP-FPM进程数太低,Nginx配置怎么改都白搭。性能调优一定要先确认瓶颈层级,不要只盯着Nginx这一层。
第二次教训是keepalive配置了但没生效。配置后重新压测,发现后端连接的创建和销毁依然频繁。排查发现我写的是proxy_set_header Connection close;,直接把keepalive废掉了。改成proxy_set_header Connection "";才恢复正常。这里特别提醒:空字符串必须双引号包裹,写成不设值或各版本格式不一致,效果完全不同。
5.3 优化效果的复盘逻辑
优化后的压测数据需要多维度对比。除了QPS和延迟,我还会关注以下几个方面:
压测期间用top观察CPU使用率分布(用户态、内核态、软中断占比),用vmstat看上下文切换次数(cs字段),用ss -s看连接状态分布。QPS上去了但上下文切换次数飙升,说明进程调度成为新瓶颈;CPU大部分消耗在软中断上,说明网卡队列或驱动需要调整。
产出对比报告时,我会把基线、优化一版、二版的数据放一起,每一版标明改了哪些参数、数据变化多少。这样做的好处是,后续线上出问题时能通过对比回溯是哪次变更引发的,问题定位效率高很多。
6. 一个完整的可参考配置模板
写到这里,我把前面所有内容整合成一份可落地的Nginx核心配置。直接抄这个模板可以应付大多数业务场景,但生产环境务必根据你的实际业务调整。
user nginx; worker_processes auto; worker_rlimit_nofile 65535; events { use epoll; worker_connections 10240; multi_accept on; } http { include mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; keepalive_requests 1000; open_file_cache max=10000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; gzip on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; upstream backend { server 10.0.0.1:8080 weight=3 max_fails=2 fail_timeout=10s; server 10.0.0.2:8080 weight=2 max_fails=2 fail_timeout=10s; keepalive 32; } server { listen 80; server_name your-domain.com; location /static/ { alias /data/static/; expires 7d; access_log off; } location /api/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; 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 30s; proxy_send_timeout 30s; } } }第expires 7d是静态资源的浏览器缓存策略,适合图片、CSS、JS这类不常变化的资源。access_log off是针对高频静态资源访问关闭访问日志,防止IO成为瓶颈。这两项在生产环境中收益不小,但要根据资源更新频率调整过期时间,否则用户拿到的是过期的旧文件。
根据我个人项目的经验,这套配置在4核8G的OpenEuler服务器上,压测静态文件的QPS大概在6-8万,反向代理场景大约能跑到3-4万,具体数据受网络环境和后端服务性能影响较大。你拿到的数字只要在自己机器上比基线有明显提升,目标就达到了——性能优化从来不是追求一个绝对数字,而是让系统资源得到合理利用、各项指标达到平衡。优化的空间通常还有不少,但先把这份配置跑熟练,遇到流量增长时再逐参数微调,比一上来就追求极限稳妥得多。