🚀 第04章 · 负载均衡 upstream
轮询/hash/权重/健康检查
负载均衡 upstream¶
当一个站点流量大到单台后端扛不住,就要把请求分发给多台"长得一模一样"的后端。upstream 块用来定义这样一组后端,Nginx 按策略把流量拆分过去。本章讲 upstream 的写法、常用调度策略、健康检查,以及本机"单后端 + upstream"这类极简形态。
为什么需要负载均衡¶
反向代理是"一个后端";负载均衡是"一组后端"。目的:
- 提升容量 —— 多台机器分摊请求,QPS 上限接近线性叠加;
- 高可用 —— 某台后端挂掉,Nginx 自动把流量交给活着的那台;
- 平滑升级 —— 滚动更新时先把某台摘下来,再切换。
本机目前每个站点都只有一台后端(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 不只转发动态请求,还能直接服务静态文件并缓存响应。