反向代理与负载均衡

反向代理是 Nginx 在生产环境用得最多的功能——无论后端是 Node.js、Python、Java 还是 Go,前面挡一层 Nginx 几乎成了标配。这一章我们系统讲解:什么是反向代理、怎么用 proxy_pass 转发请求、如何透传客户端信息、以及用 upstream 实现负载均衡。

1. 正向代理 vs 反向代理:先理清概念

这俩词很容易混,记住一句话就行:

对用户来说,访问 api.example.com 时只知道这个域名,背后是哪台机器、跑的什么语言,完全是黑盒——这就是反向代理。

2. proxy_pass:最基础的反向代理

反向代理的核心指令是 proxy_pass,它告诉 Nginx 把请求转发到哪个后端:

# 最基础的反向代理:把 /api 转发到本地后端
server {
    listen 80;
    server_name api.example.com;

    location /api/ {
        proxy_pass http://localhost:3000/;
        # 注意结尾的 / 很关键(详见下文 URI 改写规则)
    }
}

# 访问 http://api.example.com/api/users
# Nginx 会把请求转发到 http://localhost:3000/users
# (因为 proxy_pass 带 /,会把 /api/ 部分替换掉)

这一段配置让 Nginx 把所有以 /api/ 开头的请求,转发到本机 3000 端口的应用(比如你启动的 Node.js Express 服务)。

3. proxy_pass 的 URI 改写规则(高频坑点)

proxy_pass 末尾带不带斜杠,行为完全不同。这是新手最容易踩的坑:

# proxy_pass 带 URI 与不带 URI 的行为完全不同!

# === 情况 A:proxy_pass 不带 URI(不带路径或只有 host:port)===
location /api/ {
    proxy_pass http://backend;
    # 请求 /api/users → 后端收到 /api/users(完整路径透传)
}

# === 情况 B:proxy_pass 带 URI ===
location /api/ {
    proxy_pass http://backend/;
    # 请求 /api/users → 后端收到 /users(替换 /api/ 为 /)
}

location /api/ {
    proxy_pass http://backend/v2/;
    # 请求 /api/users → 后端收到 /v2/users
}

# === 情况 C:用正则 location 时,proxy_pass 不能带 URI ===
location ~ ^/api/(.+)$ {
    proxy_pass http://backend/$1;     # 用捕获组手动拼装
    # 请求 /api/users → 后端收到 /users
}

# 记忆口诀:带斜杠会替换,不带斜杠会拼接。

记忆口诀:带斜杠会替换,不带斜杠会拼接。要保证后端收到的路径符合预期,务必搞清楚这点。

4. proxy_set_header:透传客户端信息

用了反向代理后,后端看到的"客户端 IP"永远是 Nginx 的 IP(比如 127.0.0.1),真实客户端 IP 丢失了。解决方法:通过 HTTP 头透传:

location /api/ {
    proxy_pass http://localhost:3000;

    # === 1. 透传客户端真实信息(最重要!)===
    proxy_set_header Host $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

    # === 2. 超时控制 ===
    proxy_connect_timeout 5s;     # 连接后端超时
    proxy_send_timeout 60s;       # 发请求给后端超时
    proxy_read_timeout 60s;       # 读后端响应超时

    # === 3. 缓冲区(影响大响应)===
    proxy_buffering on;           # 默认 on,开启后 Nginx 缓存完整响应再发给客户端
    proxy_buffer_size 4k;         # 第一块缓冲区大小(响应头)
    proxy_buffers 8 4k;           # 后续缓冲区数量和大小

    # === 4. 上传文件大小 ===
    client_max_body_size 20m;     # 客户端请求体最大大小(默认只有 1m)
}

这几个头的含义:

后端代码要主动读取这些头才有用。Express/Koa 用 app.set('trust proxy', true),Django 配置 SECURE_PROXY_SSL_HEADER

5. upstream:负载均衡的基础

当一台后端扛不住时,多搞几台机器分摊流量。upstream 指令定义一组后端服务器:

# === upstream:定义一组后端服务器(负载均衡的基础)===
upstream backend {
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000;
}

server {
    listen 80;
    location / {
        proxy_pass http://backend;    # 直接用 upstream 名字
    }
}

# === 四种负载均衡算法 ===

# 1. 轮询(默认):依次循环
upstream backend {
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

# 2. 加权轮询:weight 越大,分配的请求越多(机器配置好的权重大)
upstream backend {
    server 10.0.0.1:3000 weight=3;   # 收到 3/4 的请求
    server 10.0.0.2:3000 weight=1;   # 收到 1/4 的请求
}

# 3. ip_hash:同一客户端 IP 始终落到同一台后端(解决 session 问题)
upstream backend {
    ip_hash;
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

# 4. least_conn:把请求发给当前连接数最少的那台
upstream backend {
    least_conn;
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
}

6. 四种负载均衡算法对比

算法命令适用场景优缺点
轮询(默认)后端机器配置相同简单,不考虑负载差异
加权轮询weight=N后端机器配置不同灵活,但权重需手动调
IP 哈希ip_hash需保持 session 一致解决 session 问题,但 IP 集中时负载不均
最少连接least_conn请求处理时间差异大负载更均衡,但计算开销略大

现代建议:除非真的有 session 粘性问题,否则优先用 least_conn + 加权。Session 粘性更好的方案是把 session 存到 Redis,而不是依赖 ip_hash。

7. 健康检查:自动剔除宕机节点

负载均衡必须有健康检查,否则一台后端挂了,请求还会被分给它。Nginx 的健康检查:

upstream backend {
    # 被动健康检查(开源版自带)
    server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;

    # max_fails=3      在 fail_timeout 时间内失败 3 次,标记为宕机
    # fail_timeout=30s 宕机后 30 秒内不再发送请求,30s 后重试
    # backup           备用服务器(只有主服务器全挂了才启用)
    # down             永久下线(手动标记)
}

upstream backend {
    server 10.0.0.1:3000;
    server 10.0.0.2:3000;
    server 10.0.0.3:3000 backup;    # 仅当上面两台都挂掉时启用
}

# 注意:开源版 Nginx 只支持"被动检查"——
# 真正的"主动健康检查"(定期发请求探测后端)需要 Nginx Plus 商业版,
# 或用 lua / 第三方模块(如 nginx_upstream_check_module)。

开源版只有被动健康检查——即"请求失败了才标记宕机"。主动探测(定期发 HTTP 请求检查)是 Plus 版功能,社区可以用 nginx_upstream_check_module 或 Lua 扩展。

8. 反向代理 WebSocket

WebSocket(如 Socket.io)的反向代理需要额外配置——它不是普通 HTTP,要先做协议升级:

# 反向代理 WebSocket(如 Socket.io、聊天室、实时推送)
server {
    listen 80;
    server_name ws.example.com;

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;                    # 必须 1.1

        # WebSocket 升级头(关键!)
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        # 通用头
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;

        # WebSocket 长连接超时调长(默认 60s 会断开)
        proxy_read_timeout 300s;
    }
}

关键三步:HTTP/1.1 → Upgrade 头 → 长超时。漏掉任何一个,WebSocket 都会连不上或频繁断开。

9. 一个完整的反向代理模板

整合上面所有内容,下面是一个可直接用的生产配置模板,把它存到团队 wiki 里

改一改 IP 和域名,复制粘贴到 /etc/nginx/conf.d/,nginx -s reload 即可生效。

小结

反向代理 + 负载均衡是 Nginx 在云原生时代最核心的用法。这一章你学会了 proxy_pass、URI 改写规则、客户端信息透传、upstream 四种算法、健康检查、WebSocket 代理——这是后端工程师的核心竞争力。下一篇我们讲 HTTPS:怎么给站点装上小绿锁。

← 上一篇 location 匹配规则

下一篇 HTTPS 与 SSL

✈️💬