🚀 第04章 · 负载均衡 upstream

轮询/hash/权重/健康检查

负载均衡 upstream

当一个站点流量大到单台后端扛不住,就要把请求分发给多台"长得一模一样"的后端。upstream 块用来定义这样一组后端,Nginx 按策略把流量拆分过去。本章讲 upstream 的写法、常用调度策略、健康检查,以及本机"单后端 + upstream"这类极简形态。

为什么需要负载均衡

反向代理是"一个后端";负载均衡是"一组后端"。目的:

  1. 提升容量 —— 多台机器分摊请求,QPS 上限接近线性叠加;
  2. 高可用 —— 某台后端挂掉,Nginx 自动把流量交给活着的那台;
  3. 平滑升级 —— 滚动更新时先把某台摘下来,再切换。

本机目前每个站点都只有一台后端(3000/3010/3081/8090),只有 Nginx 这一层在转发。但只要后端能水平扩展,结构就可以升级为:

客户端 ──> Nginx (upstream) ──> 后端 A
                            ──> 后端 B
                            ──> 后端 C

upstream 块的基本写法

upstream 定义在 http 层(不能在 location 里),然后在 proxy_pass 里用它的名字:

http {
    upstream backend {
        server 127.0.0.1:3000;
        server 192.168.1.11:3000 weight=2;
        server 192.168.1.12:3000;
    }

    server {
        listen 443 ssl;
        server_name bangdream.xyz;

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

易错 upstream 里只能写 IP + 端口,域名也行,但不能写 https:// 这类协议前缀(那是 proxy_pass 的事)。另外 upstream 名称要和 proxy_pass http://名字; 完全一致。

调度策略

upstream 默认按轮询分发,也支持多种算法。整理成表:

策略 指令 特点 适用场景
轮询 (默认,不写) 依次轮流,每台机会均等 后端能力相同
权重 weight=N 按比例分配 机器性能不同
ip_hash ip_hash; 同一 IP 固定到同一台 需要会话粘滞(购物车/登录态)
最少连接 least_conn; 优先分给当前连接少的 请求时长差异大
随机 random; 随机选一台 简单均匀性

权重

upstream backend {
    server 127.0.0.1:3000 weight=3;   # 处理 3 份
    server 127.0.0.1:3001 weight=1;   # 处理 1 份
}

上例总权重 4,约 75% 请求给第一台、25% 给第二台。适合"新机器性能差一点,少分点活"的场景。

ip_hash —— 会话粘滞

后端如果保存了会话(比如用户登录状态在内存里),请求分到别的机器就丢了。ip_hash 保证同一个客户端 IP 永远落到同一台后端:

upstream backend {
    ip_hash;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

理解 匹配粒度是 IP,所以移动端/公司 NAT 下多个用户共享一个出口 IP 时,可能被"粘"到同一台,导致负载不均。更好的做法是把会话放 Redis 这类共享存储里,让任何一台都能处理——ip_hash 只是"临时兜底"。

least_conn —— 最少连接

每台后端当前处理中的连接数不同,least_conn 把新请求交给最闲的那台:

upstream backend {
    least_conn;
    server 127.0.0.1:3000;
    server 127.0.0.1:3001;
}

适合"长请求"(如实时计算、大文件转码)和"短请求"混跑的场景,避免某台被长耗时请求拖住。

后端健康检查

Nginx 会给"连续失败的"后端打标,数到阈值就暂时摘除,过段时间再试,成功者重新启用:

upstream backend {
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:3000;
}
参数 含义
max_fails=3 (默认 1)在 fail_timeout 内最多允许失败几次
fail_timeout=30s (默认 10s)多少秒内的失败计入 max_fails,到时重新探测

易错 开源版 Nginx 只有被动检查:它靠"真的转发过去得到的失败"来判定,不会主动定期去 ping 健康端点。所以它只能发现"正在被使用且出错"的机器,且判定有个滞后期。需要主动健康检查(定时探测 /health),得用商业版 Nginx Plus 或自行用脚本 nginx -s reload 摘除。

backup 备用节点

backup 节点平时不受流量,只有其它机器全挂了才顶上:

upstream backend {
    server 192.168.1.11:3000;
    server 192.168.1.12:3000;
    server 127.0.0.1:3000 backup;   # 平时不用,主干全挂才启用
}

而 server ... down; 表示手动摘除(如升级前先把它下线,Nginx 不再分给它请求):

server 192.168.1.12:3000 down;

安全 运维常规动作:升级某台后端前,先把它标成 down 并 reload,等它处理完存量请求,再操作机器。避免升级到一半恰好有请求进来。

keepalive 提高性能

Nginx 与后端之间默认每次请求都新建 TCP 连接。keepalive 让 Nginx 和维护后端的一批空闲长连接,显著降低建连开销:

upstream backend {
    server 127.0.0.1:3000;
    keepalive 16;   # 每 worker 保留最多 16 条空闲连到后端的连接
}

location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;          # keepalive 要求 HTTP/1.1
    proxy_set_header Connection "";
}

易错 keepalive 要和 proxy_http_version 1.1 一起用,并且要把 Connection 头清空(proxy_set_header Connection "";)。如果漏了,后端看到 Connection: close 会主动断开,keepalive 白搭。

本机场景:单后端也能用 upstream

本机虽然每站一台后端,但依然可以升级成 upstream 写法,好处是便于日后加机器、改端口不动转发处:

upstream bangdream_app {
    server 127.0.0.1:3000;
}

server {
    server_name bangdream.xyz www.bangdream.xyz;
    access_log /var/log/nginx/bangdream.access.log;
    error_log  /var/log/nginx/bangdream.error.log;

    location / {
        proxy_pass http://bangdream_app;
        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;
    }
}

以后要加一台后端,只需在 upstream 里加一行 server 192.168.1.20:3000;,location / 里的 proxy_pass 完全不用动。这就是把"后端是谁"和"怎么转发"解耦。

易错 同一份配置里,location / 和 location ~ ... 如果都出现,正则 location 的优先级高于普通前缀 location,可能导致某些路径绕过 upstream 直接走到别处。分流的规则要小心,别让敏感路径拦截规则和主 location 争抢。

调试负载均衡

# 看是否真的连接到后端
sudo tail -f /var/log/nginx/bangdream.access.log

# 直接测后端是否可达
curl -I http://127.0.0.1:3000/

# 测 Nginx 反代链路(含头)
curl -I https://bangdream.xyz/

理解 负载均衡出错的第一现场通常在日志:确认请求确实到了正确后端、后端返回非 5xx。不要把"访问没反应"立刻归因于 Nginx,先 curl 后端地址 验证后端本身是否活着。

小结

upstream 把多台后端聚合成一个逻辑服务,proxy_pass http://名字 引用它;轮询/weight/ip_hash/least_conn 决定怎么分;max_fails+fail_timeout 提供被动健康检查;backup/down 管理节点参与度;keepalive 复用连接。即便单后端,用 upstream 包裹也是好习惯——为扩容留好口子。下一章,让 Nginx 不只转发动态请求,还能直接服务静态文件并缓存响应。