Nginx 学习笔记(二):反向代理与负载均衡

写在前面

本文是 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 应用部署实战。