1. 负载均衡概述
Nginx 通过 upstream 模块将客户端请求分发到多台后端服务器,实现高可用和水平扩展。
基本架构:
客户端 → Nginx (反向代理 + 负载均衡) → 后端服务器集群
├── Server A (192.168.1.10:8080)
├── Server B (192.168.1.11:8080)
└── Server C (192.168.1.12:8080)
最小配置结构:
http {
# 定义后端服务器组
upstream my_backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://my_backend;
}
}
}
2. 六大负载均衡策略详解
2.1 轮询(Round Robin)— 默认策略
原理: 按顺序逐一将请求分配给每台后端服务器。
适用场景: 后端服务器性能相近,请求处理耗时均匀。
upstream backend_rr {
# 不需要额外指令,轮询是默认行为
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
请求分配示意:
请求1 → Server A
请求2 → Server B
请求3 → Server C
请求4 → Server A (循环)
请求5 → Server B
...
2.2 加权轮询(Weighted Round Robin)
原理: 在轮询基础上,按 weight 值分配更多请求给高性能服务器。权重越大,分到的请求越多。
适用场景: 后端服务器硬件配置不同(如一台 16 核、一台 4 核)。
upstream backend_weighted {
server 192.168.1.10:8080 weight=5; # 高配机器,分 5 份
server 192.168.1.11:8080 weight=3; # 中配机器,分 3 份
server 192.168.1.12:8080 weight=1; # 低配机器,分 1 份
}
请求分配示意(每 9 个请求):
Server A 接收 5 个请求 (权重5)
Server B 接收 3 个请求 (权重3)
Server C 接收 1 个请求 (权重1)
2.3 IP Hash
原理: 根据客户端 IP 地址计算哈希值,将同一 IP 的请求始终转发到同一台后端服务器。
适用场景: 需要会话保持(Session Sticky),如未使用 Redis 集中存储 Session 的应用。
upstream backend_iphash {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
注意事项:
- 如果某台服务器需要临时下线,使用
down标记而不是删除,否则哈希结果会变,导致所有用户 Session 丢失 - 客户端经过代理(如 CDN)时,IP 可能相同,导致分配不均
upstream backend_iphash_maintain {
ip_hash;
server 192.168.1.10:8080;
server 192.168.1.11:8080 down; # 临时下线,保持哈希表稳定
server 192.168.1.12:8080;
}
2.4 最少连接数(Least Connections)
原理: 将新请求分配给当前活动连接数最少的服务器。
适用场景: 请求处理时间差异大(如有的接口耗时 50ms,有的耗时 5s),避免慢请求堆积在某台服务器。
upstream backend_least_conn {
least_conn;
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
也可以结合权重使用:
upstream backend_least_conn_weighted {
least_conn;
server 192.168.1.10:8080 weight=5;
server 192.168.1.11:8080 weight=3;
server 192.168.1.12:8080 weight=1;
}
2.5 通用 Hash(Generic Hash)
原理: 根据自定义的 key(如 URI、请求参数)计算哈希值来分配请求。
适用场景: 缓存场景——确保相同 URL 始终命中同一台后端,提高缓存命中率。
upstream backend_hash {
hash $request_uri consistent; # consistent 启用一致性哈希
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
consistent 关键字的作用:
| 模式 | 增减服务器时 | 说明 |
|---|---|---|
| 普通 hash | 大量请求重新映射 | 缓存大面积失效 |
| consistent hash | 仅影响相邻节点 | 缓存失效最小化 |
按请求参数哈希:
upstream backend_hash_arg {
hash $arg_user_id consistent; # 按 user_id 参数分配
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
2.6 Random(随机)— Nginx 1.15.1+
原理: 随机选择服务器。可配合 two 参数实现"两次随机选择中取最优"(P2C 算法)。
适用场景: 多级负载均衡架构中,避免多个 Nginx 实例同时选择同一台后端。
upstream backend_random {
random two least_conn; # 随机选 2 台,取连接数最少的那台
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
策略对比总结
| 策略 | 会话保持 | 性能差异适配 | 缓存友好 | 配置复杂度 | 推荐场景 |
|---|---|---|---|---|---|
| 轮询 | ✗ | ✗ | ✗ | 最低 | 无状态 + 同配置服务器 |
| 加权轮询 | ✗ | ✓ | ✗ | 低 | 无状态 + 异构服务器 |
| IP Hash | ✓ | ✗ | ✗ | 低 | 需要 Session 保持 |
| 最少连接 | ✗ | ✓ | ✗ | 低 | 请求耗时差异大 |
| 通用 Hash | 可选 | ✗ | ✓ | 中 | 缓存代理 |
| Random two | ✗ | ✓ | ✗ | 低 | 多级 LB 架构 |
3. 健康检查与故障节点自动剔除
参考:https://blog.csdn.net/jveqi/article/details/151290943
3.1 被动健康检查(Nginx 开源版内置)
Nginx 通过实际请求的响应结果判断后端是否健康。无需额外模块,开箱即用,是 Nginx 开源版默认支持的健康检查方式。
⚠️ 关键理解:被动检查既不是 TCP 探测端口,也不是 ping,它完全不会主动发送任何探测请求。 它依赖真实的客户端流量——当一个真实请求被转发到后端时,Nginx 观察这次请求的成功与否来判断后端是否健康。
3.1.1 核心参数详解
| 参数 | 含义 | 默认值 |
|---|---|---|
max_fails |
在 fail_timeout 时间窗口内,与后端通信的连续失败次数超过此值,Nginx 将该节点标记为不可用 |
1 |
fail_timeout |
双重含义: ①统计 max_fails 的时间窗口;②节点被标记不可用后,持续的不可用时间,超过此时间后 Nginx 会再次尝试向该节点发送请求 |
10s |
proxy_next_upstream |
定义哪些情况视为"失败",并触发将请求转发到下一台后端服务器 | error timeout |
3.1.2 什么情况被判定为"失败"?
失败的判定由 proxy_next_upstream 指令控制。可配置的失败类型如下:
| 失败类型 | 具体情况 |
|---|---|
error |
与后端建立连接、发送请求或读取响应时发生错误(连接被拒绝、网络不可达等) |
timeout |
与后端建立连接、发送请求或读取响应时超时(受 proxy_connect_timeout 等参数控制) |
invalid_header |
后端返回了空的或无效的响应头 |
http_500 |
后端返回 500 Internal Server Error |
http_502 |
后端返回 502 Bad Gateway |
http_503 |
后端返回 503 Service Unavailable |
http_504 |
后端返回 504 Gateway Timeout |
http_403 |
后端返回 403 Forbidden |
http_404 |
后端返回 404 Not Found |
http_429 |
后端返回 429 Too Many Requests |
non_idempotent |
允许对非幂等请求(POST/LOCK/PATCH)也进行重试(默认只重试幂等请求) |
off |
禁用转发到下一台服务器的功能 |
默认值是
error timeout,即只有连接错误和超时才算失败。 如果希望后端返回 502/503/504 也触发重试和失败计数,必须显式配置。
3.1.3 配置示例
http {
upstream backend_passive_check {
# 在30秒内,连续失败3次,则该节点被标记为不可用30秒
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://backend_passive_check;
# 定义哪些情况视为失败,触发重试到下一台服务器
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
# 重试的最大次数(0 = 不限制,尝试所有服务器)
proxy_next_upstream_tries 3;
# 所有重试的总耗时上限
proxy_next_upstream_timeout 10s;
}
}
}
3.1.4 故障剔除与自动恢复流程
┌──────────────────────┐
│ 正常转发用户请求 │
└──────────┬───────────┘
│
▼
┌───────────────────────────────┐
│ 请求转发到 Server A │
│ 观察响应结果 │
└───────────────┬───────────────┘
│
┌──────────┴──────────┐
│ │
成功响应 失败响应
│ (error/timeout/5xx)
│ │
▼ ▼
fails 计数清零 fails 计数 + 1
│ │
│ ▼
│ ┌────────────────────────┐
│ │ fail_timeout 内 │
│ │ fails >= max_fails ? │
│ └─────┬──────────┬────────┘
│ │ │
│ 否 是
│ │ │
│ ▼ ▼
│ 继续正常 ┌──────────────────┐
│ 转发请求 │ 标记 Server A │
│ │ 为 "不可用" │
│ │ 停止转发请求到该节点 │
│ └────────┬─────────┘
│ │
│ ▼
│ ┌───────────────────┐
│ │ 等待 fail_timeout │
│ │ 时间到期 │
│ └────────┬──────────┘
│ │
│ ▼
│ ┌────────────────────┐
│ │ 发送一个真实请求 │
│ │ 到 Server A 试探 │
│ └───┬────────────┬───┘
│ │ │
│ 成功 失败
│ │ │
▼ ▼ ▼
┌────────────┐ Server A 恢复 继续标记不可用
│ 正常工作 │ 重新加入负载池 等待下一个周期
└────────────┘
用时间线举例说明(max_fails=3 fail_timeout=30s):
时间 事件 Server A 状态
───────────────────────────────────────────────────────────────────
0s 请求1 → Server A → 502 错误 (fails=1) 正常(1/3 失败)
5s 请求2 → Server A → 连接超时 (fails=2) 正常(2/3 失败)
8s 请求3 → Server A → 503 错误 (fails=3) ✗ 标记不可用
8-38s 所有请求跳过 Server A,转发到 B 和 C ✗ 不可用(冷却期)
38s Nginx 用一个真实请求试探 Server A 探测中...
38s → 如果成功 ✓ 恢复!重新接收请求
38s → 如果失败 ✗ 继续不可用,再等 30s
3.1.5 被动检查的优点与局限性
| 维度 | 说明 |
|---|---|
| 优点 | 开源版默认支持,无需安装任何模块;配置简单,只需 max_fails + fail_timeout |
| 局限1 | 有滞后性:必须等真实请求失败才能发现故障,第一批用户请求会受影响 |
| 局限2 | 低流量盲区:没有真实流量就无法检测,凌晨等低流量时段故障可能长时间不被发现 |
| 局限3 | 恢复探测也是用真实请求,不是专门的健康探针,可能影响该用户的体验 |
| 局限4 | 无法区分"服务完全宕机"和"服务偶尔慢",只能通过调整阈值缓解误判 |
因此,生产环境强烈建议被动检查 + 主动检查结合使用。 被动检查作为兜底,主动检查实现秒级发现故障。
3.2 主动健康检查(需要第三方模块)
参考:https://juejin.cn/post/7231395869689282617
主动健康检查会定期向后端发送探测请求,无需等待真实流量触发。当后端服务器发生故障或宕机时,模块会自动将该服务器从负载均衡池中移除,直到该服务器恢复正常工作。
方案一:nginx_upstream_check_module(推荐,开源)
模块简介
nginx_upstream_check_module 是 Nginx 的第三方模块,可以定期向后端服务器发送 HTTP/TCP 请求,检测后端服务器的健康状况,并根据检测结果动态地调整负载均衡策略,从而保证后端服务器的可用性和稳定性。
项目地址:https://github.com/yaoweibin/nginx_upstream_check_module
安装步骤(完整流程)
第一步:下载 Nginx 源码和模块
# 下载 Nginx 源码(以 1.20.2 为例)
wget https://nginx.org/download/nginx-1.20.2.tar.gz
tar zxvf nginx-1.20.2.tar.gz
# 下载 nginx_upstream_check_module
wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/refs/tags/v0.4.0.tar.gz \
-O nginx_upstream_check_module-0.4.0.tar.gz
tar zxvf nginx_upstream_check_module-0.4.0.tar.gz
第二步:打补丁(关键步骤,不同 Nginx 版本对应不同补丁)
# 安装 patch 工具(如果没有)
yum install -y patch
# 切换到 Nginx 源码目录
cd nginx-1.20.2
# 打补丁(根据 Nginx 版本选择对应的 patch 文件)
patch -p1 < /path/to/nginx_upstream_check_module-0.4.0/check_1.20.1+.patch
补丁版本对照: 在模块目录下有多个
.patch文件,按 Nginx 版本选择:
- Nginx 1.20.x →
check_1.20.1+.patch- Nginx 1.18.x →
check_1.16.1+.patch- Nginx 1.14.x →
check_1.14.0+.patch- 其他版本查看模块 README
第三步:编译安装
# 配置(--add-module 指向解压后的模块目录)
./configure --prefix=/usr/local/nginx \
--add-module=/path/to/nginx_upstream_check_module-0.4.0
apt-get install zlib1g zlib1g-dev apt-get install libpcre3 libpcre3-dev
# 编译并安装
make && make install
第四步:验证安装
# 检查配置语法(如果能识别 check 指令说明模块安装成功)
/usr/local/nginx/sbin/nginx -t
# 启动 Nginx
/usr/local/nginx/sbin/nginx
# 验证可用
curl http://localhost:80
check 指令参数详解
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
| 参数 | 含义 | 示例值 | 说明 |
|---|---|---|---|
interval |
检查间隔时间(毫秒) | 3000 |
每 3 秒检查一次 |
rise |
连续成功次数阈值 | 2 |
连续 2 次探测成功 → 标记为健康,重新加入负载池 |
fall |
连续失败次数阈值 | 3 |
连续 3 次探测失败 → 标记为不健康,从负载池中剔除 |
timeout |
单次探测的超时时间(毫秒) | 1000 |
1 秒内没有响应视为失败 |
type |
检查协议类型 | http |
支持多种协议 |
port |
指定检查端口(可选) | 8080 |
不指定则使用 upstream 中定义的端口 |
default_down |
初始状态(可选) | true |
true=启动时默认不可用,需探测成功后才接收流量 |
支持的检查类型(type 参数)
| 类型 | 说明 | 适用场景 |
|---|---|---|
tcp |
仅检查 TCP 端口是否可连接 | 最基础,适用于任何 TCP 服务 |
http |
发送 HTTP 请求,检查响应状态码 | Web 服务,推荐 |
ssl_hello |
发送 SSL Client Hello 并检查 Server Hello 响应 | HTTPS 后端,不需要证书验证 |
mysql |
检查 MySQL 服务是否可连接 | MySQL 数据库代理 |
ajp |
检查 AJP 协议(Tomcat) | Tomcat 后端 |
TCP 检查配置
最简单的检查方式,仅验证端口是否能连通:
upstream backend_tcp_check {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
# 每 3 秒检查一次 TCP 连接
# 连续失败 3 次剔除,连续成功 2 次恢复
check interval=3000 rise=2 fall=3 timeout=2000 type=tcp;
}
TCP 检查的局限: 端口可连通不代表服务正常(如应用内部死锁、数据库连接池耗尽等)。生产环境推荐 HTTP 检查。
HTTP 检查配置(推荐)
发送真实的 HTTP 请求到后端的健康检查接口,验证应用层是否正常:
upstream backend_http_check {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
# HTTP 健康检查
check interval=3000 rise=2 fall=3 timeout=2000 type=http;
# 探测请求内容(必须包含完整的 HTTP 请求头)
check_http_send "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
# 期望的响应状态码:2xx 或 3xx 视为健康
check_http_expect_alive http_2xx http_3xx;
}
check_http_send 格式说明:
"请求方法 请求路径 HTTP版本\r\nHost: 主机名\r\nConnection: close\r\n\r\n"
注意事项:
- 末尾必须有
\r\n\r\n(空行),表示 HTTP 请求头结束- 推荐使用
GET方法 +HTTP/1.1,兼容性最好Connection: close避免健康检查占用长连接
check_http_expect_alive 可选值:
| 值 | 说明 |
|---|---|
http_2xx |
200-299 状态码视为健康 |
http_3xx |
300-399 状态码视为健康 |
http_4xx |
400-499 状态码视为健康 |
http_5xx |
500-599 状态码视为健康(一般不用) |
SSL 后端检查配置
upstream backend_ssl_check {
server 192.168.1.10:443;
server 192.168.1.11:443;
# SSL Hello 检查,仅验证 SSL 握手是否正常
check interval=3000 rise=2 fall=3 timeout=2000 type=ssl_hello;
}
健康状态监控页面
配置一个状态页面,可以实时查看所有后端节点的健康状态:
server {
listen 8888;
location /upstream_status {
check_status; # 输出 HTML 状态页
access_log off;
# 安全限制:仅允许内网访问
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;
}
}
访问 http://nginx-ip:8888/upstream_status 即可查看各节点状态。
状态页面输出格式支持:
| URL | 格式 |
|---|---|
/upstream_status |
HTML 页面(默认) |
/upstream_status?format=json |
JSON 格式(适合程序解析) |
/upstream_status?format=csv |
CSV 格式 |
主动检查的故障剔除与恢复流程
┌──────────────────────────────┐
│ Nginx 启动,开始定时探测 │
│ 每隔 interval 毫秒发送一次 │
└──────────────┬───────────────┘
│
▼
┌───────────────────────┐
│ 向 Server A 发送探测 │
│ (TCP连接 或 HTTP请求) │
└─────┬───────────┬─────┘
│ │
成功 失败/超时
│ │
▼ ▼
成功计数 +1 失败计数 +1
失败计数清零 成功计数清零
│ │
▼ ▼
成功 >= rise? 失败 >= fall?
│ │ │ │
是 否 是 否
│ │ │ │
▼ ▼ ▼ ▼
标记为健康 继续 标记为不健康 继续
加入负载池 探测 从负载池剔除 探测
与被动检查的关键区别:
| 维度 | 被动检查 | 主动检查 |
|---|---|---|
| 探测方式 | 依赖真实用户请求 | 独立的定时探测请求 |
| 发现速度 | 滞后(需等待失败请求) | 秒级(interval 可配) |
| 用户影响 | 第一批请求会失败 | 故障在用户请求前就被发现 |
| 低流量时段 | 可能长时间无法发现故障 | 不受流量影响,持续探测 |
| 恢复方式 | 用真实请求试探 | 专门的探测请求,不影响用户 |
| 模块依赖 | 无(内置) | 需要第三方模块或商业版 |
方案二:Nginx Plus 商业版(内置 health_check 指令)
upstream backend_plus {
zone backend_zone 64k; # 共享内存区域(必需)
server 192.168.1.10:8080;
server 192.168.1.11:8080;
server 192.168.1.12:8080;
}
server {
location / {
proxy_pass http://backend_plus;
health_check interval=5s fails=3 passes=2 uri=/health;
}
}
3.3 被动 + 主动检查结合(推荐生产方案)
upstream backend_combined {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
# 主动检查(需 nginx_upstream_check_module)
check interval=5000 rise=2 fall=3 timeout=3000 type=http;
check_http_send "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
check_http_expect_alive http_2xx;
}
双重保障:
- 主动检查:每 5 秒探测一次,及时发现宕机节点
- 被动检查:即使主动检查间隔内节点突然故障,真实请求失败也会触发剔除
3.4 备用服务器(Backup)
当所有主服务器都不可用时,请求转发到备用服务器。
upstream backend_with_backup {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 backup; # 备用服务器,正常情况不接收请求
}
⚠️ 注意:
ip_hash策略下backup参数无效。
3.5 后端应用健康检查接口示例
后端应用需提供一个轻量健康检查接口,以下是各语言示例:
Python (Flask):
@app.route('/health')
def health_check():
# 检查数据库连接、Redis 连接等关键依赖
try:
db.session.execute('SELECT 1')
return 'OK', 200
except Exception:
return 'Service Unavailable', 503
Java (Spring Boot):
@RestController
public class HealthController {
@GetMapping("/health")
public ResponseEntity<String> health() {
return ResponseEntity.ok("OK");
}
}
Node.js (Express):
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
3.6 Upstream 容错机制详解
3.6.1 Nginx 判断节点失效状态的规则
Nginx 默认以 connect refuse(连接拒绝) 和 timeout(超时) 状态作为判断节点失效的依据,不以 HTTP 错误状态码作为失败判定标准——因为只要后端能返回 HTTP 响应(即使是 500/502/503 等错误码),说明该节点网络层面仍然可达,Nginx 默认认为它是存活的。
错误计数规则:
| 状态 | 是否计入 fails |
是否需要额外配置 |
|---|---|---|
timeout(超时) |
✓ | 否,默认记录 |
connect refuse(连接拒绝) |
✓ | 否,默认记录 |
502 Bad Gateway |
✗ → ✓ | 需配置 proxy_next_upstream http_502 |
500 Internal Server Error |
✗ → ✓ | 需配置 proxy_next_upstream http_500 |
503 Service Unavailable |
✗ → ✓ | 需配置 proxy_next_upstream http_503 |
504 Gateway Timeout |
✗ → ✓ | 需配置 proxy_next_upstream http_504 |
404 Not Found |
✗ | 即使配置了 proxy_next_upstream http_404,也不计入错误数 |
| 其他 HTTP 状态码(200/301/403 等) | ✗ | 不记录为失败 |
⚠️ 关键点:
timeout和connect refuse永远会被记录为错误状态;502/500/503/504只有在配置了proxy_next_upstream后才会被记录到fails计数中;404比较特殊——配置了proxy_next_upstream http_404后会触发请求转发到下一台,但不会累加到fails错误数中。
3.6.2 节点失效与恢复的触发条件
当 fails 达到 max_fails 设定值后,Nginx 将该节点标记为失效,在 fail_timeout 时间内不再向该节点转发请求:
upstream backend {
# 在 30s 内失败 3 次 → 节点被标记不可用 30s
# 30s 后 Nginx 重新尝试向该节点发送请求
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}
完整状态转换:
正常 → fails 累加(在 fail_timeout 窗口内)→ fails >= max_fails → 标记失效
│
▼
等待 fail_timeout 到期
│
▼
用真实请求试探该节点
│ │
成功 失败
│ │
▼ ▼
恢复为正常 重新标记失效
重新接收流量 再等 fail_timeout
3.6.3 所有节点失效后的处理
当所有后端节点(包括备用节点)均被标记为失效时,Nginx 不会直接返回错误,而是:
- 重置所有节点为有效状态,重新开始探测
- 将请求尝试转发到各节点
- 如果某个节点能正常响应,则返回正确内容
- 如果所有节点仍然无法响应,则返回 502 Bad Gateway
- 下次请求时继续尝试探测,直到找到可用节点
所有节点失效 → 全部恢复为有效 → 重新逐一探测
│
┌─────────┴─────────┐
│ │
发现可用节点 全部仍然失败
│ │
▼ ▼
返回正常响应 返回 502 错误
下次请求继续探测
这个机制确保了即使 Nginx 误判(如网络瞬时抖动导致所有节点被标记失效),系统也能自动恢复,而不是永久拒绝服务。
3.6.4 proxy_next_upstream 的容错与重复处理问题
参考【non_idempotent】 https://zhuanlan.zhihu.com/p/35
场景一:容错——HTTP 错误码也触发自动转发
location / {
proxy_pass http://backend;
# 当后端返回这些错误时,自动将请求转发到下一台服务器
proxy_next_upstream http_500 http_502 http_503 http_504 http_404;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
适用于前台展示类请求,优先保证用户能看到正常页面。
场景二:关闭自动转发——防止重复处理
location /api/order/ {
proxy_pass http://backend;
# 关闭自动转发!
proxy_next_upstream off;
}
为什么要关闭?
因为 proxy_next_upstream 默认值为 error timeout,当请求到达 Server A 且 A 返回超时时,Nginx 会将同一个请求重新转发到 Server B。如果这个请求是创建订单、扣款、发短信等非幂等操作,就会导致:
用户下单 → Nginx → Server A(处理中,但响应超时)→ Nginx 判定超时
│
▼
转发到 Server B
Server B 再次处理
│
▼
结果:订单被创建了两次!
最佳实践——按路由区分策略:
upstream backend {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}
server {
# 读操作(GET 请求为主):开启容错转发
location /api/query/ {
proxy_pass http://backend;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
# 写操作(POST 请求为主):关闭自动转发,防止重复提交
location /api/order/ {
proxy_pass http://backend;
proxy_next_upstream off;
}
# 或者只允许幂等请求重试(默认行为,POST/PATCH/DELETE 不重试)
location /api/ {
proxy_pass http://backend;
# 注意:不加 non_idempotent,POST 等非幂等方法默认不会被转发到下一台
proxy_next_upstream error timeout http_502 http_503 http_504;
}
}
补充说明: Nginx 默认只对幂等请求(GET/HEAD/PUT/DELETE/OPTIONS/TRACE)进行
proxy_next_upstream转发。POST/LOCK/PATCH 等非幂等方法默认不转发,除非显式加上non_idempotent参数。这是 Nginx 对重复处理问题的一层内置保护。
3.6.5 超时参数详解
Nginx 可以针对不同 location 独立配置超时时间,根据业务特点灵活调整:
location / {
proxy_pass http://backend;
# 与后端建立 TCP 连接的超时时间(发起握手等候响应)
proxy_connect_timeout 5s; # 默认 60s
# 等待后端响应的超时时间(请求已发出,等待后端处理和返回)
proxy_read_timeout 30s; # 默认 60s
# 向后端发送请求体的超时时间(后端必须在规定时间内接收完数据)
proxy_send_timeout 10s; # 默认 60s
}
| 参数 | 含义 | 默认值 | 触发场景 |
|---|---|---|---|
proxy_connect_timeout |
与后端建立 TCP 连接的超时 | 60s | 后端宕机、端口未监听、网络不通 |
proxy_read_timeout |
等待后端返回响应的超时 | 60s | 后端处理慢、死锁、资源耗尽 |
proxy_send_timeout |
向后端发送请求的超时 | 60s | 后端接收缓慢、带宽不足 |
⚠️
proxy_connect_timeout最好不要超过 75 秒(Linux 内核 TCP 连接超时默认值),设置更大没有意义。生产建议: 这三个参数的默认值都是 60s,在后端宕机时会导致用户请求卡住很久。务必根据业务实际情况调小(参见 5.1 节)。
3.6.6 Upstream 相关变量(日志排查必备)
在 log_format 中加入以下变量,可以方便地排查负载均衡和容错问题:
| 变量 | 说明 | 示例值 |
|---|---|---|
$upstream_addr |
实际处理请求的后端服务器地址 | 192.168.1.10:8080 |
$upstream_status |
后端服务器返回的 HTTP 状态码 | 200、502 |
$upstream_response_time |
后端服务器的响应时间(秒),精确到毫秒 | 0.025 |
$upstream_connect_time |
与后端建立连接的耗时 | 0.002 |
$upstream_header_time |
接收到后端响应头的耗时 | 0.018 |
$upstream_cache_status |
缓存命中状态(使用 proxy_cache 时) | HIT、MISS、EXPIRED |
推荐日志格式:
log_format upstream_log '$remote_addr - [$time_local] "$request" $status '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_response_time=$upstream_response_time '
'request_time=$request_time';
access_log /var/log/nginx/access.log upstream_log;
容错转发时的日志表现:
当请求经历了多次转发时,$upstream_addr 和 $upstream_status 会以逗号分隔记录每次尝试:
# 请求先到 10:8080(返回 502),然后被转发到 11:8080(返回 200)
upstream_addr=192.168.1.10:8080, 192.168.1.11:8080
upstream_status=502, 200
upstream_response_time=0.003, 0.025
---
4. 生产环境综合配置示例
4.1 完整的 nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 10240;
use epoll;
}
http {
include mime.types;
default_type application/octet-stream;
# ========== 日志格式(包含 upstream 信息用于排查)==========
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_response_time=$upstream_response_time '
'request_time=$request_time';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
gzip on;
# ========== 后端服务器组定义 ==========
# 场景1:API 服务 — 加权轮询 + 被动健康检查
upstream api_servers {
server 10.0.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
server 10.0.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
server 10.0.1.20:8080 backup; # 备用节点
# 主动健康检查(需 nginx_upstream_check_module)
check interval=3000 rise=2 fall=3 timeout=2000 type=http;
check_http_send "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
check_http_expect_alive http_2xx;
# 与后端保持长连接,减少 TCP 握手开销
keepalive 32;
}
# 场景2:WebSocket 服务 — IP Hash 保持连接
upstream ws_servers {
ip_hash;
server 10.0.2.10:9090 max_fails=2 fail_timeout=15s;
server 10.0.2.11:9090 max_fails=2 fail_timeout=15s;
server 10.0.2.12:9090 max_fails=2 fail_timeout=15s;
}
# 场景3:静态资源/缓存 — 一致性哈希
upstream static_servers {
hash $request_uri consistent;
server 10.0.3.10:80 max_fails=2 fail_timeout=20s;
server 10.0.3.11:80 max_fails=2 fail_timeout=20s;
server 10.0.3.12:80 max_fails=2 fail_timeout=20s;
}
# 场景4:长耗时任务 — 最少连接
upstream task_servers {
least_conn;
server 10.0.4.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.4.11:8080 max_fails=3 fail_timeout=30s;
}
# ========== 虚拟主机配置 ==========
server {
listen 80;
server_name api.example.com;
# API 接口
location /api/ {
proxy_pass http://api_servers;
# 代理头信息传递
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;
# 超时设置
proxy_connect_timeout 5s; # 连接后端超时
proxy_send_timeout 10s; # 发送请求超时
proxy_read_timeout 30s; # 读取响应超时
# 失败重试策略
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
# 使用 HTTP/1.1 保持长连接
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# WebSocket
location /ws/ {
proxy_pass http://ws_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # WebSocket 长连接超时
}
# 静态资源
location /static/ {
proxy_pass http://static_servers;
proxy_cache_valid 200 1d;
expires 7d;
}
# 长耗时任务
location /task/ {
proxy_pass http://task_servers;
proxy_read_timeout 300s; # 长任务超时 5 分钟
proxy_next_upstream error timeout;
proxy_next_upstream_tries 2;
}
}
# ========== 健康检查状态页(仅内网可访问)==========
server {
listen 8888;
allow 10.0.0.0/8;
allow 172.16.0.0/12;
deny all;
location /upstream_status {
check_status;
access_log off;
}
# Nginx 基础状态
location /nginx_status {
stub_status;
access_log off;
}
}
}
4.2 upstream server 参数速查表
| 参数 | 说明 | 示例 |
|---|---|---|
weight=N |
权重,默认 1 | weight=5 |
max_fails=N |
允许最大失败次数,默认 1 | max_fails=3 |
fail_timeout=Ns |
失败统计窗口 & 恢复等待时间,默认 10s | fail_timeout=30s |
backup |
备用服务器 | backup |
down |
永久标记为下线 | down |
max_conns=N |
限制最大并发连接数 | max_conns=100 |
slow_start=Ns |
恢复后缓慢增加权重(商业版) | slow_start=30s |
5. 常见问题排查
5.1 后端宕机后 Nginx 非常卡顿(最常见问题)
现象: 后端某台服务器宕机后,部分请求会卡住很久才返回。
根本原因: 不是因为没配 max_fails(默认值就是 1),而是 proxy_connect_timeout 默认 60 秒。
当后端宕机时,Nginx 尝试连接该节点,要等整整 60 秒超时后才判定失败并切换到下一台:
之前(默认超时):
请求 → 宕机节点 → 等待连接 60 秒超时 → 判定失败 → 切到下一台 → 用户等了 60 秒
之后(优化超时):
请求 → 宕机节点 → 等待连接 3 秒超时 → 判定失败 → 自动切到下一台 → 用户等 ~3 秒
解决方案 — 必须配置超时 + 重试策略:
upstream backend {
server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}
server {
location / {
proxy_pass http://backend;
# ===== 关键:缩短超时时间 =====
proxy_connect_timeout 3s; # 连接后端超时(默认60s,必须改小)
proxy_send_timeout 5s; # 发送请求到后端超时
proxy_read_timeout 10s; # 等待后端响应超时
# ===== 关键:失败后自动重试下一台 =====
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3; # 最多重试 3 台服务器
proxy_next_upstream_timeout 10s; # 所有重试的总耗时上限
}
}
各超时参数默认值对照(都偏大,生产环境建议调小):
| 参数 | 默认值 | 建议值 | 说明 |
|---|---|---|---|
proxy_connect_timeout |
60s | 3-5s | 与后端建立 TCP 连接的超时 |
proxy_send_timeout |
60s | 5-10s | 向后端发送请求体的超时 |
proxy_read_timeout |
60s | 10-30s | 等待后端返回响应的超时 |
快速排查命令 — 检查当前配置是否缺少超时设置:
grep -n "proxy_connect_timeout\|proxy_read_timeout\|proxy_next_upstream" /etc/nginx/nginx.conf /etc/nginx/conf.d/*.conf
如果没有输出,说明全部使用默认的 60 秒超时,这就是卡顿的原因。
5.2 如何确认请求分配到了哪台后端?
检查 access_log 中的 upstream 字段:
tail -f /var/log/nginx/access.log | grep 'upstream='
输出示例:
10.0.0.1 - - [09/May/2026:10:00:00 +0800] "GET /api/users" 200 ... upstream=10.0.1.10:8080 upstream_status=200 upstream_response_time=0.025
5.3 如何确认节点是否被剔除?
检查 error_log:
grep 'upstream' /var/log/nginx/error.log
被剔除时会出现类似日志:
upstream server temporarily disabled while connecting to upstream
5.4 配置变更后如何验证?
# 1. 检查配置语法
nginx -t
# 2. 平滑重载(不中断服务)
nginx -s reload
# 3. 验证 upstream 状态(需 check_module)
curl http://localhost:8888/upstream_status
5.5 节点恢复后请求暴增怎么办?
Nginx Plus 支持 slow_start,开源版可以通过以下方式缓解:
upstream backend {
# 刚恢复的节点权重低,手动调整后 reload
server 192.168.1.10:8080 weight=1 max_fails=3 fail_timeout=30s;
server 192.168.1.11:8080 weight=5 max_fails=3 fail_timeout=30s;
}
5.6 所有后端都挂了怎么办?
配置一个本地兜底页面:
upstream backend {
server 192.168.1.10:8080 max_fails=2 fail_timeout=10s;
server 192.168.1.11:8080 max_fails=2 fail_timeout=10s;
}
server {
location / {
proxy_pass http://backend;
proxy_intercept_errors on;
error_page 502 503 504 /fallback.html;
}
location = /fallback.html {
root /usr/share/nginx/html;
internal;
}
}