linux

Nginx性能调优18条黄金法则:支撑10万并发的配置模板

发布时间:12天前热度: 93 ℃评论数:

一、概述

1.1 背景介绍

说实话,Nginx调优这事儿我踩过无数坑。记得2019年双11,我们电商平台流量暴涨,Nginx直接扛不住了,QPS从平时的2万飙升到8万,响应时间从50ms飙到了2秒,最后还是靠临时加机器扛过去的。那次事故之后,我花了大半年时间专门研究Nginx的性能极限,总结出了这20条黄金法则。

Nginx作为目前最流行的Web服务器和反向代理,官方数据显示单机可以轻松处理10万+的并发连接。但实际生产环境中,很多同学拿到默认配置就直接上了,结果发现连1万并发都扛不住。问题不在Nginx本身,而在于配置。

1.2 技术特点

Nginx采用事件驱动的异步非阻塞架构,这跟传统的Apache每个连接一个进程/线程的模式完全不同。打个比方:Apache像是银行柜台,每个客户都需要一个柜员全程服务;Nginx则像是叫号系统,一个柜员可以同时处理多个客户的不同阶段任务。

核心优势:

  • 内存占用低:处理10000个非活跃HTTP keep-alive连接仅需2.5MB内存
  • 事件驱动:基于epoll/kqueue,不会因为连接数增加而线性增长CPU消耗
  • 模块化设计:只加载需要的模块,减少资源浪费
  • 热部署:配置修改无需重启,平滑reload

1.3 适用场景

  • 高并发静态资源服务(图片、CSS、JS、视频)
  • 反向代理和负载均衡
  • API网关
  • SSL/TLS终结点
  • 缓存服务器
  • WebSocket代理

1.4 环境要求

组件
版本
说明
操作系统
Rocky Linux 9.4 / Ubuntu 24.04 LTS
内核版本建议5.15+
Nginx
1.26.2 / 1.27.0
mainline版本功能更新,stable版本更稳定
CPU
8核+
Nginx worker数量与CPU核心数相关
内存
16GB+
主要用于连接缓冲和缓存
网络
万兆网卡
千兆网卡在高并发下会成为瓶颈
磁盘
NVMe SSD
日志写入和缓存需要高IOPS

二、详细步骤

2.1 准备工作

2.1.1 系统内核参数调优

在动Nginx配置之前,得先把操作系统底子打好。很多人忽略了这一步,结果Nginx配得再好也白搭。

# /etc/sysctl.conf 追加以下内容

# 文件描述符限制
fs.file-max = 2097152
fs.nr_open = 2097152

# TCP连接相关
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# TIME_WAIT优化(这个参数救过我无数次)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_max_tw_buckets = 262144

# TCP keepalive
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3

# 网络缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# 本地端口范围
net.ipv4.ip_local_port_range = 1024 65535

应用配置:

sysctl -p

2.1.2 文件描述符限制

这是个老生常谈的问题了,但每次接手新项目还是会遇到。Nginx报"Too many open files"错误,十有八九就是这个没配好。

# /etc/security/limits.conf
* soft nofile 1048576
* hard nofile 1048576
nginx soft nofile 1048576
nginx hard nofile 1048576

# /etc/systemd/system/nginx.service.d/override.conf
[Service]
LimitNOFILE=1048576

2.2 核心配置

法则1:worker进程数量配置

# 自动检测CPU核心数,省心省力
worker_processes auto;

# 或者手动指定,建议等于CPU核心数
# worker_processes 8;

# CPU亲和性绑定,减少CPU缓存失效
worker_cpu_affinity auto;

踩坑记录:早期我喜欢把worker_processes设成CPU核心数的2倍,觉得这样能处理更多请求。结果发现这是典型的过度优化,多出来的worker进程反而增加了上下文切换开销,QPS不升反降。

法则2:worker连接数配置

events {
    # 每个worker的最大连接数
    # 理论最大并发 = worker_processes * worker_connections
    worker_connections 65535;

    # 使用epoll事件模型(Linux 2.6+必选)
    use epoll;

    # 允许一个worker进程同时接受多个新连接
    multi_accept on;

    # 互斥锁,高并发场景建议关闭
    accept_mutex off;
}

法则3:文件描述符缓存

# main context
worker_rlimit_nofile 1048576;

法则4:sendfile零拷贝

http {
    # 开启sendfile,避免用户态和内核态之间的数据拷贝
    sendfile on;

    # 配合sendfile使用,减少网络报文段数量
    tcp_nopush on;

    # 禁用Nagle算法,减少延迟
    tcp_nodelay on;
}

原理解释:传统文件发送需要经历"磁盘->内核缓冲区->用户缓冲区->socket缓冲区"四次拷贝。sendfile直接在内核态完成"磁盘->socket缓冲区"的数据传输,理论上可以提升30%-40%的吞吐量。

法则5:超时配置

http {
    # 客户端请求头超时
    client_header_timeout 15s;

    # 客户端请求体超时
    client_body_timeout 15s;

    # 响应超时
    send_timeout 15s;

    # keepalive超时
    keepalive_timeout 65s;

    # keepalive请求数量限制
    keepalive_requests 10000;
}

踩坑记录:曾经有个项目,上传大文件总是失败。排查半天发现是client_body_timeout设成了10秒,但大文件上传需要更长时间。生产环境这个值建议根据实际业务调整,不要一刀切。

法则6:缓冲区配置

http {
    # 客户端请求体缓冲区
    client_body_buffer_size 128k;
    client_max_body_size 100m;

    # 请求头缓冲区
    client_header_buffer_size 4k;
    large_client_header_buffers 4 32k;

    # 代理缓冲区
    proxy_buffer_size 64k;
    proxy_buffers 8 128k;
    proxy_busy_buffers_size 256k;
}

法则7:Gzip压缩

http {
    gzip on;
    gzip_vary on;
    gzip_proxied any;

    # 压缩级别1-9,建议4-6,太高CPU消耗大
    gzip_comp_level 5;

    # 最小压缩长度,太小的文件压缩反而浪费CPU
    gzip_min_length 1024;

    # 压缩类型
    gzip_types
        text/plain
        text/css
        text/javascript
        application/javascript
        application/json
        application/xml
        application/xml+rss
        image/svg+xml;

    # 预压缩文件支持
    gzip_static on;
}

性能对比

压缩级别
压缩率
CPU消耗
适用场景
1
最低
CPU受限环境
4-5
中等
通用场景(推荐)
9
最高
带宽极其昂贵

法则8:静态文件缓存

http {
    # 打开文件缓存
    open_file_cache max=100000 inactive=60s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

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

法则9:日志优化

http {
    # 使用缓冲写入,减少磁盘IO
    access_log /var/log/nginx/access.log main buffer=64k flush=5s;

    # 或者对于高并发场景,考虑关闭access log
    # access_log off;

    # 错误日志级别
    error_log /var/log/nginx/error.log warn;
}

血泪教训:生产环境日志不要开debug级别!我曾经为了排查问题临时开了debug,结果磁盘一会儿就满了,服务直接挂掉。

法则10:SSL/TLS优化

http {
    # SSL会话缓存
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets on;

    # SSL协议版本
    ssl_protocols TLSv1.2 TLSv1.3;

    # 优先使用服务器端加密套件
    ssl_prefer_server_ciphers off;

    # TLS 1.3加密套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
}

法则11:HTTP/2配置

server {
    listen 443 ssl;
    http2 on;

    # HTTP/2推送(Nginx 1.25.1+)
    # 注意:主流浏览器已逐步废弃Server Push
}

法则12:连接复用优化

upstream backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;

    # 与后端保持的连接数
    keepalive 300;

    # 单个连接最大请求数
    keepalive_requests 10000;

    # 连接超时
    keepalive_timeout 60s;
}

server {
    location /api/ {
        proxy_pass http://backend;

        # 必须使用HTTP/1.1才能使用keepalive
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

这条救命配置:很多人配了upstream的keepalive但不生效,99%是因为没加proxy_set_header Connection ""。默认情况下Nginx会把Connection头设成close,导致每次请求都新建连接。

法则13:负载均衡算法选择

upstream backend {
    # 最少连接数算法,推荐
    least_conn;

    # 或者IP哈希(需要会话保持时使用)
    # ip_hash;

    # 或者一致性哈希
    # hash $request_uri consistent;

    server 10.0.0.1:8080 weight=3;
    server 10.0.0.2:8080 weight=2;
    server 10.0.0.3:8080 weight=1 backup;
}

法则14:健康检查

upstream backend {
    server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
}

开源版Nginx的健康检查比较弱,只能被动检测。如果需要主动健康检查,建议:

  • 使用Nginx Plus(商业版)
  • 使用第三方模块nginx_upstream_check_module
  • 使用OpenResty

法则15:请求限流

http {
    # 定义限流区域
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/s;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}

server {
    location /api/ {
        # 请求速率限制
        limit_req zone=api_limit burst=200 nodelay;

        # 并发连接限制
        limit_conn conn_limit 50;

        # 限流返回码
        limit_req_status 429;
        limit_conn_status 429;
    }
}

法则16:proxy优化

location /api/ {
    proxy_pass http://backend;

    # 连接超时
    proxy_connect_timeout 5s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;

    # 失败重试
    proxy_next_upstream error timeout http_502 http_503;
    proxy_next_upstream_tries 3;
    proxy_next_upstream_timeout 10s;

    # 请求头传递
    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;
}

法则17:缓存配置

http {
    # 定义缓存路径
    proxy_cache_path /var/cache/nginx levels=1:2
        keys_zone=api_cache:100m
        max_size=10g
        inactive=60m
        use_temp_path=off;
}

server {
    location /api/public/ {
        proxy_pass http://backend;
        proxy_cache api_cache;
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout updating;
        proxy_cache_lock on;

        add_header X-Cache-Status $upstream_cache_status;
    }
}

法则18:安全加固

http {
    # 隐藏版本号
    server_tokens off;

    # 安全响应头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

server {
    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

手机扫码访问