跳转至

nginx upstream 配置速查:负载均衡算法、server 指令参数与 keepalive 三件套

第一次配置 nginx upstream 的时候,我对着官方文档里那一堆参数看了 20 分钟,还是不知道该配几个。轮询就够了,还是得考虑权重?max_fails 到底配几秒?keepalive 明明加了,为什么 ss -tanp 里还是每次新建连接?

这三个问题问得越多的人越多。写这篇速查的时候我也翻了 nginx101、CSDN 上各种总结——但都是讲一个点,缺一份"从零到生产"的完整模板。

先想清楚:upstream 要解决哪三个问题

upstream 块不是简单的"列几个后端 IP"。它同时解决三件事:

  1. 怎么分流量(负载均衡算法)
  2. 每个后端配多少"预算"(server 指令参数)
  3. 怎么跟后端复用 TCP 连接(keepalive 三件套)

三件事任何一件没配好,都会在生产环境里出怪事。

负载均衡算法:6 种,先选一种再谈优化

nginx 支持 6 种算法,但生产环境 90% 用前两种。先选对算法,比调参数重要 10 倍。

算法 语法 适用场景 不适用场景
轮询(默认) 无 后端无状态,性能差不多 有 session 的场景
加权轮询 weight=N 后端性能差异大 后端性能差异 <20% 时意义不大
IP 哈希 hash $remote_addr consistent 会话保持,同一用户固定后端 后端节点少会不均
最少连接 least_conn 后端处理时间差异大(长任务) 短请求、突发流量
一致性哈希 hash $key consistent 缓存命中率高优先 无缓存场景
URL 哈希 hash $request_uri 静态资源缓存到固定后端 用户会话保持

决策树:

  • 后端是无状态的短请求(大部分 API)→ 加权轮询(默认)
  • 有 session,用户登录态不能丢 → IP 哈希 加 consistent 关键字
  • 后端是长短任务混合(比如既有查询又有导出)→ least_conn
  • 后端有本地缓存 → 一致性哈希 加 hash $request_uri consistent
  • 其他 → 别折腾,用默认轮询

server 指令:10 个参数逐个说

server 指令是 upstream 里最复杂的一行。10 个参数按"日常必配"和"进阶"分两档。

日常必配(4 个)

server 10.0.0.11:8080 weight=3 max_conns=100;
server 10.0.0.12:8080 weight=1 max_conns=100 backup;
  • weight=N(默认 1):流量权重,weight=3 表示这台机器分 3/4 的流量
  • max_conns=N(默认 1024):这台机器最大并发连接数,超过就不接新连接。生产建议显式配,防单台被打爆
  • backup:这台机器当备份,只有前面所有 server 都 down 时才接流量
  • down:这台机器不参与流量分配,但配置保留(用于灰度下线)

进阶参数(6 个)

server 10.0.0.11:8080
  weight=3
  max_conns=200
  max_fails=3          # 连续失败 3 次就判定 down
  fail_timeout=30s     # 判定 down 后 30 秒才重新接流量
  resolve              # 域名支持动态解析(配合 upstream 变量用)
  proxy_protocol       # 透传客户端真实 IP(云 LB 前面常用)

max_fails + fail_timeout 是最重要的健康检查。nginx 不做主动健康检查(要用 nginx plus 才有),全靠被动的连接失败次数判断。默认值 max_fails=1 fail_timeout=10s 太激进,一次连接失败就屏蔽 10 秒。生产建议:

  • 敏感接口:max_fails=3 fail_timeout=30s
  • 后台任务:max_fails=5 fail_timeout=60s
  • 短生命周期服务:max_fails=1 fail_timeout=5s(快速失败快速恢复)

keepalive 三件套:最容易漏配的一件事

这是本文最核心的一段。90% 的 "nginx upstream keepalive 不生效" 问题都出在这里。

keepalive 需要三行配置同时出现,缺一行就白配:

# ① upstream 块里声明连接池大小
upstream backend {
    server 10.0.0.11:8080;
    server 10.0.0.12:8080;
    keepalive 32;              # 每 worker 保留 32 条空闲连接
    keepalive_timeout 65s;     # 空闲连接保持 65 秒
    keepalive_requests 1000;   # 单条连接最多处理 1000 请求
}

# ② location 里强制 HTTP/1.1
location / {
    proxy_http_version 1.1;    # 必须!HTTP/1.0 不支持长连接
    proxy_set_header Connection "";   # 必须!清空 Connection 头
    proxy_pass http://backend;
}

三行少任何一行都会导致 keepalive 无效:

  • 少 proxy_http_version 1.1 → Nginx 用 HTTP/1.0,长连接直接不支持
  • 少 proxy_set_header Connection "" → 客户端的 Connection: close 头会透传到后端,后端主动关连接
  • 少 keepalive 32 → 没有连接池,每次请求都新建 TCP

keepalive 数值怎么定?

keepalive 32 这个 32 不是随手写的。每 worker 每后端进程保留 N 条空闲连接,实际总连接数 = worker_processes × upstream server 数 × keepalive N。

估算公式:

keepalive N ≈ 每秒请求数 × 后端平均响应时间(秒)/ 后端进程数

例:1000 QPS、后端 200ms、4 个 worker: - keepalive N ≈ 1000 × 0.2 / 4 = 50 - 保守一点配 keepalive 64

别贪心配 512:keepalive 越大越可能耗尽 upstream 连接数,导致真正需要新建连接时反而被拒。

怎么验证 keepalive 真的生效了?

配置后 3 秒验证命令:

# 1. 用 ab 打 100 个请求,看请求耗时是不是明显下降
ab -c 10 -n 100 http://nginx.example.com/

# 2. 看后端连接池,ESTAB 数应该是几十条而不是每次都是新连接
ss -tanp | grep ':8080' | wc -l

# 3. 打开 nginx access_log 看 upstream_response_time 是不是变短
tail -f /var/log/nginx/access.log

你应该看到什么: - ab 里 Time per request 从几十毫秒降到几毫秒(keepalive 命中) - ss 里同一后端 IP 只有几十条 ESTAB 连接(复用成功) - 如果不生效,ab 前后耗时差不多,ss 里连接数会短暂飙升到几百再降下来

3 个真实踩坑案例

踩坑 1:keepalive 加了但 ab 前后没差别

症状:upstream 里配了 keepalive 32,location 里也配了 proxy_http_version 1.1,但 ab -c 20 -n 1000 前后请求耗时没差别,后端 ss 里连接数依然飙升。

排查:nginx -T | grep -E 'keepalive|proxy_http_version|proxy_set_header' 看合并后的配置。

根因:少配了 proxy_set_header Connection ""。客户端发的 Connection: close 头被透传到后端,后端直接 close(fd)。

修复:加第三行 proxy_set_header Connection "";。

注意:这行是清空,不是删除。写成 proxy_set_header Connection close; 会强制所有请求都是短连接。

踩坑 2:max_fails=1 导致后端被误判 down

症状:后端机器偶尔网络抖动一次,nginx 把它屏蔽了 10 秒,用户 10 秒内所有请求都打到剩余机器,触发雪崩。

根因:默认 max_fails=1 太激进。

修复:max_fails=3 fail_timeout=30s。要求连续 3 次失败才判定 down,给网络抖动的缓冲。

延伸:nginx 免费版没有主动健康检查(health_check 是 nginx plus 专属)。真需要主动健康检查的话,要么上 nginx plus,要么用 external health check(比如 Prometheus blackbox 探测 + nginx reload)。

踩坑 3:一致性哈希配了但流量还是不均

症状:用了 hash $request_uri consistent,但看 access_log 里流量分布还是接近平均,缓存命中率没提升。

排查:$request_uri 是整个 URI 路径。如果后端缓存是按 API 前缀分区的(比如 /api/v1/xxx 全部缓存到同一台),URI 差异让 hash 分散了。

修复:改 hash 键:

upstream backend {
    hash $request_uri consistent;
    server ...;
}
# 或按 API 版本分区
location /api/v1/ {
    proxy_pass http://backend_v1;
}
upstream backend_v1 {
    hash $host consistent;   # 同一域名走同一台
}

教训:一致性哈希的 hash 键选什么比算法本身更重要。想清楚"什么粒度的请求应该落到同一台"。

一份可直接抄的生产模板

upstream backend {
    # 6 台后端,4 主 2 备
    server 10.0.0.11:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
    server 10.0.0.12:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
    server 10.0.0.13:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
    server 10.0.0.14:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
    server 10.0.0.21:8080 backup max_conns=100;
    server 10.0.0.22:8080 backup max_conns=100;

    # keepalive 三件套的第 ① 部分
    keepalive 64;
    keepalive_timeout 65s;
    keepalive_requests 1000;
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_http_version 1.1;                    # keepalive 三件套第 ② 部分
        proxy_set_header Connection "";             # keepalive 三件套第 ③ 部分
        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;

        proxy_pass http://backend;
    }

    # 排障时看一眼 upstream 状态
    location /nginx_status {
        stub_status;
        allow 127.0.0.1;
        deny all;
    }
}

Cheat Sheet:12 条速查命令

# 命令 用途
1 nginx -T 2>&1 \| grep -A 20 'upstream' 看合并后的 upstream 完整配置
2 nginx -T 2>&1 \| grep -E 'keepalive|proxy_http_version' 验证 keepalive 三件套是否齐全
3 curl http://localhost/nginx_status 看 active connections / handled requests
4 ss -tanp \| grep :8080 看 Nginx 与后端的连接状态
5 ss -tan \| grep ESTAB \| wc -l 看当前 ESTAB 连接总数
6 ab -c 20 -n 1000 http://nginx/ 压测 keepalive 是否生效
7 tail -f /var/log/nginx/error.log \| grep upstream 实时看 upstream 报错
8 awk '$(NF-1)==502 {print}' access.log \| wc -l 统计最近 502 数量
9 tail access.log \| awk '{print $7, $9}' \| sort \| uniq -c 看不同 URI 的响应码分布
10 curl -o /dev/null -w "%{time_connect} %{time_starttransfer}" -I http://nginx/ 看连接建立和首字节耗时
11 nginx -s reload 热加载 upstream 配置
12 lsmod \| grep nginx 看 nginx 内核模块(少见,用来看 kprobe 类扩展)

收尾:什么情况下不要调 upstream

写 upstream 配置最容易陷入的坑是过度优化。这些情况下别碰 upstream:

  1. QPS 低于 100:nginx 默认轮询完全够用,调权重调 keepalive 都是白费力气
  2. 后端就 1 台:upstream 只有一行 server,配什么算法都一样
  3. 有外部 LB(阿里云 SLB / AWS ALB):LB 前面已经做过一次负载均衡,nginx upstream 里再配一遍是重复劳动

upstream 优化真正的价值在中大规模(QPS 500+ 或后端 4 台以上)。小场景把时间花在后端代码和数据库上更有回报。