反向代理与负载均衡
反向代理与负载均衡
写在前面
Nginx 最早就是做反向代理出身,这是它最经典的能力。本篇会讲清楚 proxy_pass 的 URL 重写规则、upstream 块、各种负载均衡策略的适用场景和坑,最后拿公司 API 网关的完整配置做拆解。
一、反向代理与正向代理
简单区分:
- 正向代理:代理客户端,客户端知道自己通过代理上网(公司内网、科学上网)
- 反向代理:代理服务端,客户端不知道自己在跟代理对话(CDN、API 网关)
[Client] → [Proxy] → [Server] 正向代理
[Client] → [Proxy] → [Servers] 反向代理(代理多个 server)
二、proxy_pass 基础
location /api/ {
proxy_pass http://backend;
}
请求 /api/users 会被转发到 http://backend/users(去掉 /api/ 前缀)。
2.1 URL 重写规则
proxy_pass 是否带 URI 决定了如何重写:
location /api/ {
proxy_pass http://backend; # 末尾无 /,保留完整 URI
# /api/users → http://backend/api/users
}
location /api/ {
proxy_pass http://backend/; # 末尾有 /,去掉 location 前缀
# /api/users → http://backend/users
}
location /api/ {
proxy_pass http://backend/api; # 替换为指定路径
# /api/users → http://backend/api/users
}
这条规则最容易出错——加不加末尾的 / 完全改变行为。
2.2 实战坑:正则 location 的 proxy_pass
# NG - 正则 location 不能用 proxy_pass URL 重写
location ~ ^/api/(.*)$ {
proxy_pass http://backend/$1; # 必须用 $1 捕获组
}
正则 location 的 proxy_pass 末尾必须明确指定 URI,否则报错。
三、upstream 块
3.1 基本定义
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
server {
location / {
proxy_pass http://backend;
}
}
3.2 完整 upstream 配置
upstream backend {
# 负载均衡策略
ip_hash; # 或者 least_conn, random
# 节点定义
server 10.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 weight=2 max_fails=3 fail_timeout=30s;
# 备份节点 - 其他全挂了才用
server 10.0.0.4:8080 backup;
# 标记为不可用(临时下线)
# server 10.0.0.5:8080 down;
# 健康检查参数(配合 nginx_upstream_check_module)
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
# 长连接
keepalive 32; # 每个 worker 保持 32 个连接到 upstream
keepalive_timeout 60s;
keepalive_requests 1000;
}
3.3 server 指令的关键参数
server address [parameters];
| 参数 | 含义 |
|---|---|
weight=N |
权重,默认 1 |
max_fails=N |
失败多少次标记为不可用,默认 1 |
fail_timeout=T |
失败超时时间,默认 10s |
backup |
备份节点,其他全挂了才用 |
down |
永久下线 |
resolve |
域名解析(需要 resolver 指令) |
slow_start=T |
节点恢复后,流量逐渐恢复的时间(Nginx Plus 商业版) |
四、负载均衡策略
4.1 轮询(round-robin)—— 默认
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
请求依次分给每个 server,最公平。
4.2 加权轮询(weighted round-robin)
upstream backend {
server 10.0.0.1:8080 weight=5;
server 10.0.0.2:8080 weight=3;
server 10.0.0.3:8080 weight=2;
}
按比例分配。机器配置不同就用权重——比如 4 核机器 weight=4,8 核机器 weight=8。
4.3 ip_hash
upstream backend {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
同一个客户端 IP 永远路由到同一台 server。
适用场景:
- 会话保持(本地 session)
- 缓存命中率(同一个用户的请求总到同一台机器)
坑:
- CDN/代理后面失效——所有用户都是同一个出口 IP,所有请求都到同一台机器
- 真实 IP 必须传过来——
X-Forwarded-For没用,要用realip模块
# 真实 IP 提取
real_ip_header X-Forwarded-For;
set_real_ip_from 10.0.0.0/8; # 信任内网代理
set_real_ip_from 192.168.0.0/16;
4.4 least_conn(最少连接)
upstream backend {
least_conn;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
把请求给当前连接数最少的 server。
适用:长连接服务(WebSocket、长轮询)、后端响应时间差异大。
4.5 url_hash(需要 nginx-upstream-fair 或第三方模块)
upstream backend {
hash $request_uri;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
同一个 URL 总是路由到同一台 server——专门为缓存场景设计。
适用:CDN 缓存后端、视频网站(同一个视频总是命中同一台机器的缓存)。
4.6 random
upstream backend {
random;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}
随机选,Nginx 1.15.7+ 默认算法。带 two 参数就是两次随机选最小连接。
4.7 选择策略
| 场景 | 推荐策略 |
|---|---|
| 一般无状态服务 | 加权轮询 |
| 需要 session 保持 | ip_hash(注意代理) |
| WebSocket / 长连接 | least_conn |
| 缓存敏感 | url_hash / consistent_hash |
| 服务器性能差异大 | 加权 + least_conn |
五、proxy_pass 进阶配置
5.1 传递客户端信息
location / {
proxy_pass http://backend;
# 传递 Host 头
proxy_set_header Host $host;
# 传递真实 IP
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_set_header User-Agent $http_user_agent;
proxy_set_header Referer $http_referer;
}
后端应用就能拿到客户端的真实信息。
5.2 超时设置
location / {
proxy_pass http://backend;
# 连接超时
proxy_connect_timeout 5s; # 连接 upstream 的超时,默认 60s,推荐 5s
# 发送超时
proxy_send_timeout 30s; # 发送请求体的超时
# 接收超时
proxy_read_timeout 30s; # 等待 upstream 响应的超时
# 长连接超时(Nginx 1.15.3+)
proxy_socket_keepalive on;
}
最常踩的坑:proxy_read_timeout 默认 60s。如果后端响应慢,客户端会先断开,但 Nginx 还在等 upstream,浪费资源。
5.3 buffer 调优
location / {
proxy_pass http://backend;
# 响应缓冲
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
# 大响应缓冲到临时文件
proxy_max_temp_file_size 1024m;
# 请求体缓冲
proxy_request_buffering on;
}
proxy_buffering on:Nginx 先把上游响应全部收下来,再发给客户端——可以处理慢客户端。
proxy_buffering off:Nginx 收到一点就发给客户端一点——适合长连接流式响应(SSE、大文件下载)。
5.4 重试
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
}
当后端返回 5xx、连接出错、超时时,自动重试下一个 upstream。
⚠️ 不要对 POST 请求重试——可能产生重复提交。我们一般只对 GET 重试。
5.5 WebSocket 代理
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400s; # 长连接超时
}
关键三个:
proxy_http_version 1.1Upgrade和Connection头- 长 read_timeout
六、公司 API 网关实战
6.1 完整配置
# /etc/nginx/sites-enabled/api-gateway.conf
upstream user_service {
least_conn;
server 10.0.1.1:3000 weight=3 max_fails=2 fail_timeout=30s;
server 10.0.1.2:3000 weight=3 max_fails=2 fail_timeout=30s;
server 10.0.1.3:3000 weight=2 max_fails=2 fail_timeout=30s;
keepalive 64;
}
upstream order_service {
ip_hash; # 订单服务需要 session
server 10.0.2.1:4000 max_fails=2 fail_timeout=30s;
server 10.0.2.2:4000 max_fails=2 fail_timeout=30s;
keepalive 64;
}
upstream product_service {
server 10.0.3.1:5000;
server 10.0.3.2:5000;
keepalive 64;
}
upstream static_assets {
server 10.0.4.1:80;
server 10.0.4.2:80;
keepalive 32;
}
# ====== 全局配置 ======
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# ====== API 网关 server ======
server {
listen 80;
server_name api.company.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.company.com;
ssl_certificate /etc/nginx/ssl/api.company.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.company.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# 通用 header
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_set_header X-Request-ID $request_id; # 链路追踪 ID
# 通用超时
proxy_connect_timeout 3s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# 重试(只对 GET 重试)
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;
# 通用缓冲
proxy_buffering on;
proxy_buffer_size 16k;
proxy_buffers 8 32k;
# ====== 用户服务 ======
location /api/users/ {
proxy_pass http://user_service/;
}
# ====== 订单服务 ======
location /api/orders/ {
proxy_pass http://order_service/;
# 订单服务的 session 在内存里,需要 ip_hash
}
# ====== 商品服务 ======
location /api/products/ {
proxy_pass http://product_service/;
}
# ====== WebSocket ======
location /ws/ {
proxy_pass http://user_service;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 86400s;
}
# ====== 健康检查 ======
location = /health {
access_log off;
return 200 "ok\n";
}
# ====== 限流 ======
location /api/ {
# 全局限流 - 每秒 1000 请求
limit_req zone=api_limit burst=200 nodelay;
# 兜底
return 404;
}
}
# ====== 静态资源 server ======
server {
listen 80;
server_name cdn.company.com;
location / {
proxy_pass http://static_assets;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 缓存静态资源
proxy_cache static_cache;
proxy_cache_valid 200 304 7d;
proxy_cache_valid any 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
6.2 关键设计点
多 upstream 分层
不同服务有不同的:
- 负载均衡策略(订单服务要 ip_hash)
- 健康检查参数
- 连接数限制
不要把所有后端都扔到一个 upstream。
限流 + 重试 + 超时 三件套
# 限流
limit_req zone=api_limit burst=200 nodelay;
# 重试
proxy_next_upstream error timeout http_502 http_503 http_504;
# 超时
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
三者配合,后端短暂故障不会让前端感知到。
链路追踪 ID
proxy_set_header X-Request-ID $request_id;
Nginx 自动生成 UUID,通过 X-Request-ID 头传给后端。后端日志、应用日志都记录这个 ID,排查问题非常方便。
HTTPS 统一入口
所有 HTTP 请求 301 跳 HTTPS,避免代码里处理两种协议。
七、upstream 健康检查
7.1 被动检查(默认)
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
}
请求失败 3 次,标记为不可用;30 秒内不再发请求给这台机器。
缺点:第一次请求会失败(因为还不知道机器挂了)。
7.2 主动检查(需要模块)
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
需要编译时带 nginx_upstream_check_module(淘宝开源)。
生产推荐用主动检查——机器真挂的时候不会有失败请求。
7.3 用 K8s 服务发现代替
如果跑在 K8s 上,直接用 Service:
# K8s Service
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 8080
Nginx Ingress 自动发现,健康检查、自动扩缩容都交给 K8s。
八、本篇小结
| 主题 | 关键点 |
|---|---|
proxy_pass URL 规则 |
末尾 / 决定是否去掉 location 前缀 |
upstream 块 |
多 upstream 分离,不同服务不同策略 |
| 负载均衡 | 加权轮询是默认,特殊场景用 ip_hash / url_hash |
| 超时设置 | proxy_read_timeout 关键,默认 60s 太长 |
| 重试 | 只对 GET 重试,POST 重试会重复提交 |
| 健康检查 | K8s 优先,Nginx 主动检查需要第三方模块 |
下一篇讲 HTTPS 配置全攻略——证书自动续期、TLS 1.3 优化、HSTS、零故障切换的真实过程。