upstream 负载均衡

五种调度算法、server 参数全解、被动健康检查的真实语义与 keepalive 的默认值变更。

作者 David更新于 第 10 篇(共 21 篇)

upstream 块

upstream backend {
    server backend1.example.com weight=5;
    server backend2.example.com:8080;
    server unix:/tmp/backend3;

    server backup1.example.com backup;
}

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

upstream 的上下文是 http——与 server 平级,不能写在 server 里面。

server 参数

参数默认含义
weight=n1权重
max_conns=n0(不限)最大并发连接
max_fails=n1判定不可用前的失败次数
fail_timeout=t10s双重含义,见下
backup—仅当主服务器全不可用时启用
down—永久标记不可用
resolve—跟踪 DNS 变化自动更新(需 resolver)
drain—排空模式,只接受已绑定的请求
slow_start=t—恢复时权重渐增(商业版)

被动健康检查

nginx 开源版没有主动健康检查(那是商业版功能)。开源版靠 max_fails + fail_timeout 做被动检测:

server backend1.example.com max_fails=3 fail_timeout=30s;

fail_timeout 有两个含义,这是最容易误解的地方:

  1. 统计窗口——在这段时间内失败达到 max_fails 次才判定不可用。
  2. 隔离时长——判定不可用后,隔离这么久再重试。

上面的配置读作:「30 秒内失败 3 次 → 标记不可用 → 隔离 30 秒」。两个时长共用同一个值,无法分开设置。

「失败」的定义由 proxy_next_upstream 决定——默认只有 error 和 timeout 算。上游返回 500 默认不算失败,所以一台持续返回 500 的机器不会被摘掉。要摘就得:

proxy_next_upstream error timeout http_500 http_502 http_503;

注意 http_403/http_404 永远不计入失败次数,即使列出来也不影响 max_fails。

backup 与 down

upstream backend {
    server primary1.example.com;
    server primary2.example.com;
    server standby.example.com backup;   # 主的全挂了才用
    server retired.example.com down;     # 永久下线
}

down 的一个重要用途是配合 ip_hash——见下。

五种调度算法

1. round-robin(默认)

不写任何算法指令时的默认。按权重轮询:

upstream backend {
    server backend1.example.com weight=5;
    server backend2.example.com;   # weight=1
    server backend3.example.com;   # weight=1
}

每 7 个请求:backend1 拿 5 个,另两台各 1 个。

2. least_conn

upstream backend {
    least_conn;
    server backend1.example.com;
    server backend2.example.com;
}

发给当前活跃连接数最少的机器(同样考虑权重)。

适用于请求处理时长差异大的场景——轮询会把慢请求和快请求平均分配,导致某些机器堆积;least_conn 能自动避开繁忙的机器。

3. ip_hash

upstream backend {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com down;   # ← 注意这里
}

按客户端 IP 哈希:IPv4 取前 3 段,IPv6 取完整地址。同一客户端总落到同一台——用于会话保持。

要临时移除机器时用 down 而不是删掉那一行。 官方示例里特意写了 down,原因是:删除一行会改变服务器列表长度,导致所有客户端的哈希结果重新分配(会话全丢);标 down 则保留了位置,只有原本落在该机器上的客户端被重新分配。

IPv4 只取前 3 段(/24)意味着同一个 C 段的客户端会落到同一台——对 NAT 后的大量用户会造成不均衡。

4. hash

upstream backend {
    hash $request_uri consistent;
    server backend1.example.com;
    server backend2.example.com;
}

按任意 key 哈希,key 可以是任何变量组合:

hash $cookie_sessionid;          # 按会话
hash $arg_userid consistent;     # 按用户
hash $request_uri consistent;    # 按 URI(缓存友好)

consistent 参数启用 ketama 一致性哈希——增删机器时只有少部分 key 需要重映射,而不是全部。做缓存分片时必须加,否则加一台机器会导致缓存全部失效。

5. random

upstream backend {
    random two least_conn;
    server backend1.example.com;
    server backend2.example.com;
}

随机按权重选。two 参数是关键:随机挑 2 台,再用指定方法(默认 least_conn)从中选一台。

这是「二选一的力量」(power of two choices)——只需两次随机采样就能获得接近全局最优的负载均衡效果,且无需维护全局状态。在多 nginx 实例的场景下比 least_conn 更优:least_conn 只知道自己这台 nginx 的连接数,多实例时会集体涌向同一台后端。

keepalive

upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;
}

保持到上游的长连接池,避免每个请求都 TCP+TLS 握手。

1.29.7 起 keepalive 默认启用,每 worker 32 条连接。相关默认值:

项默认
连接数(每 worker)32(1.29.7 起默认开启)
keepalive_timeout60 秒
keepalive_requests1000(1.15.3+)
keepalive_time1 小时(1.19.10+)

这是个重要变更:以前不显式配 keepalive 就完全没有长连接,每个请求都要重新握手。现在默认就有了。

历史上还需要这两行配合:

location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;      # 1.29.7 起已是默认
    proxy_set_header Connection ""; # 清掉默认的 Connection: close
}

proxy_http_version 默认已改为 1.1,但 Connection "" 在旧版本仍需显式写。

各协议的 keepalive 支持

协议支持额外要求
HTTP✅proxy_http_version 1.1 + Connection ""
FastCGI✅fastcgi_keep_conn on;
SCGI❌不支持
uwsgi❌不支持

FastCGI 的 fastcgi_keep_conn on 容易漏——只配 upstream 的 keepalive 而不开这个,连接池不会生效。

resolve:动态 DNS

resolver 10.0.0.2 valid=30s;

upstream backend {
    zone backend 64k;
    server api.internal.example.com resolve;
}

默认情况下 nginx 只在启动时解析一次 upstream 里的域名——后端 IP 变了(K8s 滚动更新、云负载均衡漂移)不会感知,必须 reload。

resolve 参数让它跟踪 DNS 变化。需要 resolver 指令,且通常需要 zone(共享内存)。

这是容器/云环境的常见故障:后端 Pod 重建换了 IP,nginx 还在往旧 IP 发请求,表现为持续 502 直到 reload。开源版可用 resolve 解决,但注意它对某些配置组合有限制(如 proxy_pass 里直接写域名而非 upstream 名时行为不同)。

一份生产 upstream

upstream api_backend {
    least_conn;

    server 10.0.1.10:8080 weight=2 max_fails=3 fail_timeout=15s;
    server 10.0.1.11:8080 weight=2 max_fails=3 fail_timeout=15s;
    server 10.0.1.12:8080          max_fails=3 fail_timeout=15s;
    server 10.0.1.99:8080 backup;

    keepalive     64;
    keepalive_requests 10000;
    keepalive_timeout  60s;
}

server {
    location / {
        proxy_pass http://api_backend;

        proxy_http_version 1.1;
        proxy_set_header   Connection "";
        proxy_set_header   Host $host;

        proxy_next_upstream       error timeout http_502 http_503;
        proxy_next_upstream_tries 2;
    }
}

下一篇:FastCGI、uWSGI 与 gRPC。

本页目录