写在前面
本文是 Nginx 学习笔记系列的第二篇,深入讲解反向代理的原理和配置、负载均衡策略、upstream 健康检查,以及四层和七层代理的区别。这是 Nginx 在生产环境最核心的用途。
一、反向代理
1.1 正向代理 vs 反向代理
1
2
3
4
5
6
7
8
9
10
11
12
13
|
正向代理(代理客户端):
客户端 → [代理服务器] → 目标服务器
客户端知道要访问谁,代理帮客户端转发
例:VPN、科学上网、公司出口代理
反向代理(代理服务端):
客户端 → [代理服务器] → 后端服务器
客户端不知道真正提供服务的是谁
例:Nginx、CDN、API 网关
关键区别:
正向代理 — 客户端配置代理,服务端不知道真实客户端
反向代理 — 服务端躲在代理后面,客户端不知道真实服务端
|
1.2 反向代理能做什么
1
2
3
4
5
6
7
|
1. 隐藏后端服务器 — 客户端只看到 Nginx 的 IP
2. 负载均衡 — 请求分发到多台后端服务器
3. SSL 终端 — Nginx 处理 HTTPS,后端用 HTTP
4. 缓存 — 缓存后端响应,减少后端压力
5. 压缩 — Nginx 压缩响应,减少带宽
6. 安全 — 统一的访问控制、限流、WAF
7. 静态文件分离 — Nginx 处理静态,后端处理动态
|
1.3 基本配置
1
2
3
4
5
6
7
8
|
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://192.168.1.100:5000; # 后端地址
}
}
|
就这么简单,所有请求都会被转发到 http://192.168.1.100:5000。
1.4 完整的反向代理配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://192.168.1.100:5000;
# 传递客户端真实信息
proxy_set_header Host $host; # 原始域名
proxy_set_header X-Real-IP $remote_addr; # 客户端真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链
proxy_set_header X-Forwarded-Proto $scheme; # 原始协议(http/https)
# 超时设置
proxy_connect_timeout 10s; # 连接后端超时
proxy_send_timeout 60s; # 发送请求超时
proxy_read_timeout 60s; # 读取响应超时
# 缓冲设置
proxy_buffering on; # 开启缓冲(默认开启)
proxy_buffer_size 4k; # 响应头缓冲区
proxy_buffers 8 16k; # 响应体缓冲区(8 个 16KB)
# 错误处理
proxy_intercept_errors on; # 拦截后端错误响应
proxy_next_upstream error timeout http_502 http_503 http_504;
# 出错时尝试下一个后端
}
}
|
1.5 proxy_pass 的路径规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# 情况一:proxy_pass 带 URI(带 / 或路径)
location /api/ {
proxy_pass http://backend/v1/;
# /api/users → http://backend/v1/users
# /api/ 被替换为 /v1/
}
# 情况二:proxy_pass 不带 URI(没有路径)
location /api/ {
proxy_pass http://backend;
# /api/users → http://backend/api/users
# /api/ 前缀被保留
}
# 情况三:精确替换
location /old/ {
proxy_pass http://backend/new/;
# /old/users → http://backend/new/users
}
|
1
2
3
4
5
|
⚠️ 带不带尾斜线的区别:
proxy_pass http://backend; → 保留 location 前缀
proxy_pass http://backend/; → 替换 location 前缀
这是 Nginx 最容易踩的坑之一,务必记住。
|
1.6 WebSocket 代理
1
2
3
4
5
6
7
8
9
10
11
12
|
location /ws/ {
proxy_pass http://backend;
# WebSocket 必需的头部
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置(WebSocket 长连接)
proxy_read_timeout 3600s; # 1 小时
proxy_send_timeout 3600s;
}
|
二、负载均衡
2.1 upstream 基本配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 定义后端服务器组
upstream backend {
server 192.168.1.101:5000;
server 192.168.1.102:5000;
server 192.168.1.103:5000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend; # 引用 upstream
}
}
|
2.2 负载均衡策略
轮询(Round Robin,默认)
1
2
3
4
5
6
|
upstream backend {
server 192.168.1.101:5000;
server 192.168.1.102:5000;
server 192.168.1.103:5000;
# 请求依次分配:101 → 102 → 103 → 101 → ...
}
|
加权轮询(Weight)
1
2
3
4
5
6
|
upstream backend {
server 192.168.1.101:5000 weight=5; # 5/8 的请求
server 192.168.1.102:5000 weight=2; # 2/8 的请求
server 192.168.1.103:5000 weight=1; # 1/8 的请求
# 适合服务器配置不同的场景
}
|
1
2
3
4
|
权重分配示例(总权重 8):
101(性能好)→ 62.5% 请求
102(普通) → 25% 请求
103(低配) → 12.5% 请求
|
IP 哈希(ip_hash)
1
2
3
4
5
6
|
upstream backend {
ip_hash; # 基于客户端 IP 做哈希
server 192.168.1.101:5000;
server 192.168.1.102:5000;
server 192.168.1.103:5000;
}
|
1
2
3
4
5
6
7
|
特点:
同一客户端 IP 的请求总是打到同一台后端
适合需要会话保持(Session)的场景
缺点:
- 后端服务器变化时,大量会话重新分配
- 大量请求来自同一 IP(如公司出口 NAT)会导致负载不均
|
最少连接(least_conn)
1
2
3
4
5
6
|
upstream backend {
least_conn; # 分配给当前连接数最少的服务器
server 192.168.1.101:5000;
server 192.168.1.102:5000;
server 192.168.1.103:5000;
}
|
1
2
3
|
适合场景:
请求处理时间差异大(有的快有的慢)
避免某些服务器积压大量慢请求
|
一致性哈希(hash)
1
2
3
4
5
6
|
upstream backend {
hash $request_uri consistent; # 基于请求 URI 做一致性哈希
server 192.168.1.101:5000;
server 192.168.1.102:5000;
server 192.168.1.103:5000;
}
|
1
2
3
4
|
特点:
同一 URI 总是路由到同一台后端(提高缓存命中率)
consistent 参数:添加/删除节点时只影响相邻节点
可以基于 $uri、$arg_user_id 等任何变量
|
随机(random)
1
2
3
4
5
6
|
upstream backend {
random two least_conn; # 随机选 2 个,挑连接少的
server 192.168.1.101:5000;
server 192.168.1.102:5000;
server 192.168.1.103:5000;
}
|
2.3 策略选择总结
1
2
3
4
5
6
7
8
|
场景 推荐策略
──────────────────────────────────────────────────
一般 Web 应用 轮询(默认)
服务器性能不均 加权轮询
需要会话保持 ip_hash / hash $cookie_sessionid
请求耗时差异大 least_conn
缓存命中率优先 hash $request_uri consistent
分布式文件/缓存 一致性哈希
|
三、upstream 服务器状态
1
2
3
4
5
6
7
8
9
10
11
12
13
|
upstream backend {
server 192.168.1.101:5000 weight=5;
server 192.168.1.102:5000 weight=3;
server 192.168.1.103:5000 backup; # 备用服务器,其他全挂才启用
server 192.168.1.104:5000 down; # 标记为下线,不参与负载
server 192.168.1.105:5000 max_fails=3 fail_timeout=30s;
# max_fails=3:30s 内失败 3 次认为不可用
# fail_timeout=30s:不可用后 30s 后再尝试
}
|
1
2
3
4
5
6
7
8
|
参数说明:
weight=N — 权重(默认 1)
max_conns=N — 最大并发连接数(默认 0,不限制)
max_fails=N — 最大失败次数(默认 1)
fail_timeout=T — 失败超时时间(默认 10s)
backup — 备份服务器
down — 标记下线
resolve — 动态解析域名(适合 Docker/K8s 环境)
|
3.1 动态解析域名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
upstream backend {
# resolve:当后端是域名时,Nginx 会定期重新解析
# 适合 Docker/K8s 环境,容器 IP 可能变化
server api-service:5000 resolve;
}
server {
resolver 127.0.0.11 valid=10s; # Docker 内部 DNS
# 或者使用公共 DNS
# resolver 8.8.8.8 8.8.4.4 valid=30s;
location / {
# 使用变量触发运行时解析(另一种方式)
set $backend_host "api-service:5000";
proxy_pass http://$backend_host;
}
}
|
四、健康检查
4.1 被动健康检查(内置)
Nginx 开源版只支持被动健康检查:在实际请求失败时才标记服务器不可用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
upstream backend {
server 192.168.1.101:5000 max_fails=3 fail_timeout=30s;
server 192.168.1.102:5000 max_fails=3 fail_timeout=30s;
proxy_next_upstream error timeout http_502 http_503 http_504;
}
server {
location / {
proxy_pass http://backend;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_timeout 10s; # 重试总时间
proxy_next_upstream_tries 3; # 最多重试 3 次
}
}
|
1
2
3
4
5
6
|
工作流程:
1. 请求到达 Nginx → 转发给 backend-101
2. 101 超时或返回 502/503/504 → 记录一次失败
3. 自动重试下一个服务器 → 转发给 backend-102
4. 101 失败 3 次(max_fails=3)→ 标记为不可用
5. 30s 后(fail_timeout=30s)→ 再次尝试 101
|
4.2 主动健康检查(Nginx Plus / 第三方模块)
开源版 Nginx 不支持主动健康检查,但有替代方案:
方案一:用 location 模拟
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 健康检查端点
server {
listen 80;
# 简单的健康检查
location /health {
access_log off;
return 200 "healthy\n";
}
# 带后端检测的健康检查
location /health/backend {
access_log off;
proxy_pass http://backend/health;
proxy_intercept_errors on;
error_page 502 503 504 = /health/unhealthy;
}
location /health/unhealthy {
return 503 "unhealthy\n";
}
}
|
方案二:用第三方模块 nginx_upstream_check_module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# 需要编译时添加模块
upstream backend {
server 192.168.1.101:5000;
server 192.168.1.102:5000;
# 主动健康检查(Tengine / 第三方模块)
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;
}
# 查看健康状态
location /status {
check_status;
access_log off;
}
|
方案三:外部健康检查脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#!/bin/bash
# health-check.sh — 配合 crontab 使用
BACKENDS=("192.168.1.101:5000" "192.168.1.102:5000")
NGINX_CONF="/etc/nginx/conf.d/backends.conf"
for backend in "${BACKENDS[@]}"; do
host=$(echo $backend | cut -d: -f1)
port=$(echo $backend | cut -d: -f2)
if curl -sf -o /dev/null "http://$backend/health"; then
echo "$backend: healthy"
else
echo "$backend: unhealthy"
# 动态更新配置,标记 down
sed -i "s/server $backend;/server $backend down;/" $NGINX_CONF
nginx -s reload
fi
done
|
五、四层与七层代理
5.1 区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
OSI 七层模型:
7. 应用层 — HTTP、HTTPS、FTP、SMTP
6. 表示层 — SSL/TLS、JPEG
5. 会话层 — RPC
4. 传输层 — TCP、UDP
3. 网络层 — IP
2. 数据链路层 — 以太网
1. 物理层 — 网线
四层代理(L4):工作在传输层(TCP/UDP)
- 只看到 IP + 端口
- 不解析应用层协议
- 速度更快,开销更小
- 不支持基于路径/域名的路由
七层代理(L7):工作在应用层(HTTP)
- 能看到 HTTP 请求内容
- 支持基于域名、路径、头部、Cookie 的路由
- 支持 rewrite、缓存、压缩等
- 开销更大
|
5.2 四层代理配置(stream 模块)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
# 注意:stream 块和 http 块是平级的,都在 main 层级
# /etc/nginx/nginx.conf
stream {
# TCP 负载均衡 — MySQL
upstream mysql {
server 192.168.1.101:3306;
server 192.168.1.102:3306;
}
server {
listen 3306;
proxy_pass mysql;
proxy_connect_timeout 5s;
proxy_timeout 300s;
}
# TCP 负载均衡 — Redis
upstream redis {
server 192.168.1.101:6379;
server 192.168.1.102:6379;
}
server {
listen 6379;
proxy_pass redis;
}
# UDP 负载均衡 — DNS
upstream dns {
server 8.8.8.8:53;
server 8.8.4.4:53;
}
server {
listen 53 udp;
proxy_pass dns;
}
}
|
1
2
3
4
5
6
|
四层代理的典型场景:
- 数据库代理(MySQL、PostgreSQL)
- Redis 代理
- 邮件代理(SMTP、IMAP)
- 游戏服务器代理
- MQTT 代理
|
5.3 七层代理(http 模块,前面讲的反向代理)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
# 这就是我们一直在用的 proxy_pass
http {
upstream api {
server 192.168.1.101:5000;
server 192.168.1.102:5000;
}
server {
listen 80;
# 按路径路由
location /api/ {
proxy_pass http://api/;
}
location /admin/ {
proxy_pass http://admin-service/;
}
# 按请求头路由(灰度发布)
location /api/ {
if ($http_x_version = "v2") {
proxy_pass http://api-v2;
}
proxy_pass http://api-v1;
}
}
}
|
5.4 四层 vs 七层选择
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
选择四层代理:
✓ 非 HTTP 协议(MySQL、Redis、MQTT)
✓ 追求极致性能(不需要解析应用层)
✓ 只需要简单的 TCP/UDP 转发
选择七层代理:
✓ HTTP/HTTPS 服务
✓ 需要基于路径/域名/头部的路由
✓ 需要 SSL 终端
✓ 需要缓存、压缩、限流
常见架构:四层 + 七层组合
客户端 → L4 负载均衡(VIP)→ L7 Nginx 集群 → 后端服务
L4 做入口流量分发
L7 做精细路由和业务处理
|
六、实战:完整的代理配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
# /etc/nginx/conf.d/api.conf
upstream api_servers {
least_conn;
server 10.0.1.101:5000 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.102:5000 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.1.103:5000 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.1.104:5000 backup;
keepalive 32; # 保持与后端的长连接
}
server {
listen 80;
server_name api.example.com;
# 请求体大小限制
client_max_body_size 50m;
location / {
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_http_version 1.1;
proxy_set_header Connection "";
# 超时
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# 缓冲
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 8 32k;
# 失败重试
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_timeout 10s;
proxy_next_upstream_tries 3;
}
# WebSocket
location /ws/ {
proxy_pass http://api_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;
}
# 健康检查
location /health {
access_log off;
proxy_pass http://api_servers/health;
}
}
|
七、获取客户端真实 IP
多层代理后,如何获取客户端真实 IP?
7.1 单层代理
1
2
3
4
5
6
7
8
|
# Nginx 代理层
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 后端获取(以 ASP.NET Core 为例)
var ip = HttpContext.Connection.RemoteIpAddress; // Nginx 的 IP
var realIp = HttpContext.Request.Headers["X-Real-IP"]; // 客户端真实 IP
var forwarded = HttpContext.Request.Headers["X-Forwarded-For"]; // 代理链
|
7.2 多层代理
1
2
3
4
|
客户端(1.1.1.1) → CDN(2.2.2.2) → Nginx(3.3.3.3) → 后端
X-Forwarded-For: 1.1.1.1, 2.2.2.2
X-Real-IP: 1.1.1.1
|
1
2
3
4
5
6
7
8
9
10
11
|
# 第一层 Nginx(或 CDN)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 第二层 Nginx
# 使用 set 指令获取最左侧的 IP(最原始的客户端)
set_real_ip_from 10.0.0.0/8; # 信任的代理 IP 段
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For; # 从这个头部获取真实 IP
real_ip_recursive on; # 递归排除信任的代理 IP
# 现在 $remote_addr 就是客户端真实 IP 了
|
7.3 ASP.NET Core 配置
1
2
3
4
5
6
7
8
9
10
11
|
// Program.cs
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor
| ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear(); // 信任所有代理(按需调整)
options.KnownProxies.Clear();
});
// 在 UseHttpsRedirection 之前调用
app.UseForwardedHeaders();
|
八、小结
本文学习了 Nginx 反向代理和负载均衡:
- 正向代理 vs 反向代理的区别
- 反向代理配置(proxy_pass、proxy_set_header、超时、缓冲)
- proxy_pass 路径规则(带不带尾斜线的坑)
- WebSocket 代理
- 负载均衡策略(轮询、加权、ip_hash、least_conn、一致性哈希)
- upstream 服务器状态管理
- 被动/主动健康检查
- 四层代理(stream)vs 七层代理(http)
- 获取客户端真实 IP
下一篇将学习 HTTPS 配置、性能优化和 .NET 应用部署实战。