性能调优与高并发

性能调优与高并发

写在前面

前几篇讲了反向代理、缓存、安全。这一篇是性能调优——怎么让单机 Nginx 处理更多并发、更短延迟。本篇会从 Nginx 自身配置到 Linux 内核参数,逐层调优,最后实战把单机 Nginx 从 1w 并发提到 10w 并发的真实过程。

一、性能调优的基本思路

性能问题分两类:

  1. 延迟高:单次请求慢——查后端、磁盘 IO、计算
  2. 吞吐低:并发上不去——资源瓶颈、配置瓶颈

本篇主要讲**吞吐(并发能力)**的调优。

1.1 性能基准

调优前先测基线——不调优时能跑多少并发?

# 用 wrk
wrk -t8 -c1000 -d30s https://api.company.com/health
# Running 30s test @ https://api.company.com/health
#   8 threads and 1000 connections
#   Thread Stats   Avg      Stdev     Max   +/- Stdev
#     Latency    45.21ms   12.34ms 200.32ms   89.21%
#     Req/Sec     2.78k   345.21    3.45k    78.32%
#   224567 requests in 30.10s, 156.34MB read
# Requests/sec:   7462.32
# Transfer/sec:      5.19MB

基线:~7500 QPS,P99 延迟 80ms。

二、Nginx 自身配置调优

2.1 worker 数量

worker_processes auto;  # = CPU 核心数

默认 auto,已经是 CPU 核心数——一般不用动。

但不是 worker 越多越好——worker 多了 CPU 调度开销也大。通常 worker 数 = CPU 核心数,或最多 2 倍。

# 看 CPU 核心数
nproc
# 8

# worker 数应该 = 8
worker_processes 8;

2.2 worker 连接数

events {
    worker_connections 4096;  # 每个 worker 的最大连接数
}

最大并发 = worker_processes × worker_connections。

8 worker × 4096 = 32768 并发连接。

但理论不等于实际——每个反向代理连接消耗 2 个 fd(client + upstream),所以实际可服务的客户端连接 ≈ 16000。

2.3 多进程 accept

events {
    accept_mutex off;       # 多进程同时 accept,提高吞吐
    multi_accept on;        # 一次 accept 所有等待连接
    use epoll;              # Linux 必用 epoll
}

accept_mutex off 适合高并发场景,on 适合连接数少但每个连接负载重的场景。

2.4 worker_cpu_affinity

绑定 worker 到固定 CPU,避免 CPU 缓存失效:

worker_cpu_affinity auto;  # 自动绑定

# 或者手动(8 核机器)
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;

⚠️ 不是所有场景都需要——某些 CPU 架构(比如 ARM big.LITTLE)手动绑定反而更差。

2.5 worker_priority

worker_priority -5;  # nice 值,默认 0

降低 worker 优先级,保证系统响应(SSH、其他进程不被饿死)。

三、零拷贝与文件传输

3.1 sendfile

sendfile on;

Linux 内核的 sendfile(2) 系统调用,内核态直接传输文件——跳过用户态的中转。

对静态文件(图片、CSS、JS)性能提升巨大。

3.2 tcp_nopush

tcp_nopush on;

等数据包累积到 MSS 再发送,减少网络包数量。必须和 sendfile 配合。

3.3 aio(异步 IO)

location /video/ {
    aio on;
    directio 512k;       # 大于 512K 用 O_DIRECT,绕过 page cache
    output_buffers 2 1m;
}

异步 IO 读取大文件——适合视频下载、文件下载服务。

3.4 thread pools

# 编译时带 --with-threads
thread_pool default threads=32 max_queue=65536;

location /download/ {
    aio threads;
    # 异步 IO 用 thread pool 处理,worker 不阻塞
}

磁盘 IO 阻塞 worker 是高并发杀手。thread pool 把磁盘 IO 异步化,worker 不会卡住。

四、长连接优化

4.1 客户端 keep-alive

http {
    keepalive_timeout 65;
    keepalive_requests 1000;
}

客户端到 Nginx 的长连接。生产推荐 65s——足够复用,但又不浪费 fd。

4.2 upstream keep-alive

upstream backend {
    server 10.0.0.1:8080;
    server 10.0.0.2:8080;
    keepalive 32;            # 每个 worker 保持 32 个连接到 upstream
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;       # 必须 HTTP/1.1
        proxy_set_header Connection ""; # 清除 Connection: close
    }
}

Nginx 到 upstream 的长连接——避免每次请求都新建 TCP 连接。

坑:默认 Connection: close,即使你写了 proxy_http_version 1.1。必须显式 proxy_set_header Connection ""。

实测:启用 upstream keep-alive,QPS 提升 30%+。

4.3 HTTP/2

listen 443 ssl http2;

HTTP/2 多路复用,单连接并行多个请求。必须 HTTPS。

五、buffer 调优

5.1 客户端请求

http {
    client_max_body_size 20m;          # 上传文件大小限制
    client_body_buffer_size 128k;      # 第一个 buffer 大小
    client_header_buffer_size 4k;      # header buffer
    large_client_header_buffers 4 8k;  # 大 header buffer
}

5.2 代理缓冲

location / {
    proxy_pass http://backend;

    proxy_buffering on;
    proxy_buffer_size 16k;
    proxy_buffers 8 32k;
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k;
    proxy_max_temp_file_size 1024m;
}

proxy_buffering on 把响应全部收下来再发给客户端——适合客户端网络慢的场景。

5.3 流式响应

location /sse/ {
    proxy_pass http://backend;
    proxy_buffering off;            # 实时传输
    proxy_cache off;
    chunked_transfer_encoding on;
}

适合 SSE、长轮询、WebSocket。

六、Linux 内核参数调优

Nginx 的连接数上限受系统 fd 限制和内核参数影响。

6.1 文件描述符限制

# 系统级
fs.file-max = 1000000

# 用户级(/etc/security/limits.conf)
* soft nofile 1000000
* hard nofile 1000000

# Nginx 配置
worker_rlimit_nofile 1000000;

6.2 TCP 参数

# /etc/sysctl.conf

# TCP 连接队列
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# TIME_WAIT 复用
net.ipv4.tcp_tw_reuse = 1

# TIME_WAIT 数量上限
net.ipv4.tcp_max_tw_buckets = 5000

# TCP 缓冲区
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

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

应用:

sudo sysctl -p

6.3 网络设备参数

# 队列长度
net.core.netdev_max_backlog = 65535

# 巨型帧
net.ipv4.tcp_mtu_probing = 1

6.4 连接跟踪

如果用 iptables:

net.netfilter.nf_conntrack_max = 1000000
net.nf_conntrack_max = 1000000

七、磁盘 IO 优化

7.1 IO 调度器

# 看当前调度器
cat /sys/block/vda/queue/scheduler
# [none] mq-deadline kyber bfq

# SSD 用 none,机械盘用 deadline
echo none > /sys/block/vda/queue/scheduler

7.2 文件系统挂载选项

# /etc/fstab
/dev/vda1 /var/log ext4 defaults,noatime,nodiratime 0 0

减少磁盘写入。

7.3 缓存目录用 tmpfs

# /etc/fstab
tmpfs /var/cache/nginx tmpfs defaults,size=2G 0 0

proxy_cache 目录用 tmpfs——重启会丢,但 Nginx 重建缓存很快。

八、实战:单机从 1w 到 10w 并发

8.1 测试环境

8.2 调优前

QPS: 7500
P99: 80ms
CPU: 60% (4 核满载)

8.3 第一轮:Nginx 基础调优

worker_processes 8;
worker_rlimit_nofile 1000000;
events {
    worker_connections 4096;
    multi_accept on;
    accept_mutex off;
    use epoll;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    keepalive_timeout 65;
    keepalive_requests 1000;

    upstream backend {
        server 10.0.0.1:3000;
        keepalive 64;
    }

    server {
        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
}
QPS: 12000 (+60%)
P99: 65ms (-19%)
CPU: 75%

8.4 第二轮:内核参数调优

sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_max_syn_backlog=65535
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216"
sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216"
QPS: 18000 (+50%)
P99: 55ms
CPU: 85%

8.5 第三轮:fd 限制 + worker 连接数

# /etc/security/limits.conf
* soft nofile 1000000
* hard nofile 1000000
worker_rlimit_nofile 1000000;
events {
    worker_connections 65535;  # 从 4096 提高
}
QPS: 28000 (+56%)
P99: 50ms

8.6 第四轮:开启 HTTP/2

listen 443 ssl http2;

浏览器并发连接数从 6 提到 100+。

QPS: 35000
P99: 45ms

8.7 第五轮:thread pool 处理磁盘 IO

thread_pool disk_pool threads=16 max_queue=1024;

location /static/ {
    sendfile on;
    aio threads=disk_pool;
    directio 512k;
}

把磁盘 IO 异步化,避免 worker 阻塞。

QPS: 42000 (+20%)

8.8 第六轮:多 Nginx 实例 + CPU 绑定

如果还是不够,可以跑多个 Nginx 实例绑不同 CPU:

# 实例 1 - CPU 0-3
taskset -c 0-3 nginx -c /etc/nginx/nginx-1.conf

# 实例 2 - CPU 4-7
taskset -c 4-7 nginx -c /etc/nginx/nginx-2.conf

或者用 HAProxy 在前面分发。

8.9 最终结果

QPS: 100000+
P99: 50ms
CPU: 95% (8 核)
内存: 4GB

从 7500 QPS 到 10w QPS,提升 13 倍——全是配置和内核调优,没有一行代码优化。

九、压测工具

9.1 wrk

# 基础压测
wrk -t8 -c1000 -d30s https://api.company.com/health

# 复杂场景用 Lua 脚本
wrk -t8 -c1000 -d30s -s post.lua https://api.company.com/api/orders

9.2 ab(Apache Bench)

ab -n 10000 -c 1000 https://api.company.com/health

简单但功能有限。

9.3 vegeta

echo "GET https://api.company.com/health" | vegeta attack -rate=1000 -duration=30s | vegeta report

支持更复杂的报告格式。

十、本篇小结

调优点 关键指令
Worker 数量 worker_processes auto
Worker 连接 worker_connections 65535
文件传输 sendfile on + tcp_nopush on
异步 IO aio threads + thread_pool
长连接 upstream keepalive + Connection ""
缓冲 proxy_buffering + buffer 调优
内核 somaxconn + tcp_tw_reuse
fd 限制 worker_rlimit_nofile + ulimit

下一篇讲 常见 502/504 故障排查,把生产中遇到的所有 5xx 故障做一份速查表。

参考资料