Day 9-2 反向代理与负载均衡

Day 9-2 反向代理与负载均衡 本节学习 Nginx 作为“流量入口”的核心能力:反向代理和负载均衡。 Java 运维视角:本节为后续 Spring Boot 应用部署打基础。无论微服务还是传统 Web 应用,Nginx 都是最前端的“流量守门员”。 目标 说明 理解反向代理 掌握正向代理与反向

Day 9-2 反向代理与负载均衡

本节学习 Nginx 作为“流量入口”的核心能力:反向代理和负载均衡。

Java 运维视角:本节为后续 Spring Boot 应用部署打基础。无论微服务还是传统 Web 应用,Nginx 都是最前端的“流量守门员”。

Nginx 将流量分发到多台服务器

目标 说明
理解反向代理 掌握正向代理与反向代理的本质区别
配置反向代理 掌握 proxy_pass 核心配置、路径替换规则
传递真实客户端信息 理解 proxy_set_header 的原理与配置
理解负载均衡 掌握轮询、权重、ip_hash、least_conn 四种算法及适用场景
配置健康检查 理解被动健康检查机制及参数调优
理解四层代理 理解 L4 与 L7 的区别,为后续 LVS/K8s 做铺垫

一、原理篇:代理的本质

1.1 什么是代理?

代理的本质是“中间人”——在客户端和服务端之间插入一层,代为转发请求和响应。

对比维度 正向代理 反向代理
代理对象 为客户端做代理 为服务端做代理
谁配置 客户端手动配置 服务端配置,客户端无感知
隐藏的是 客户端的真实 IP 服务端的真实 IP
典型场景 公司内网通过代理访问外网 Nginx 将请求转发给内网应用

1.2 正向代理 vs 反向代理

正向代理代表客户端,反向代理代表服务端

本质区别:正向代理是“帮客户端说话”,反向代理是“帮服务端接客”。

1.3 反向代理的核心价值

在 Java 生产环境中,Nginx 反向代理解决了以下问题:

问题 Nginx 的解决方案 价值
后端直接暴露不安全 后端只监听内网或 localhost,Nginx 做唯一入口 安全
多个应用需要统一入口 根据域名/路径分发到不同后端 统一管理
SSL 证书需要在每个应用配置 SSL 在 Nginx 层统一终止(SSL Termination) 简化运维
静态文件和动态请求混在一起 静态文件由 Nginx 直接返回 性能优化
单点故障 后端多实例,Nginx 做负载均衡 高可用

Nginx 统一入口分发到应用、静态文件和管理后台

反向代理 = 用户请求的“总入口” + 内网后端的“安全门” + 流量的“调度中心”。

二、实验环境准备

2.1 环境规划

主机名 IP 地址 角色 部署组件
lb1 192.168.100.10 接入层 Nginx(反向代理/负载均衡器)
backend1 192.168.100.11 后端 1 Nginx(模拟后端服务)
backend2 192.168.100.12 后端 2 Nginx(模拟后端服务)
backend3 192.168.100.13 后端 3 Nginx(模拟后端服务)

LB 与三台后端的实验环境拓扑

2.2 部署后端模拟服务

在 backend1、backend2、backend3 上分别执行:

# 创建站点目录和 API 子目录
[root@backend1 ~]# mkdir -p /var/www/backend/api

# 创建测试页面(每台机器内容不同)
[root@backend1 ~]# echo "Backend 1 (192.168.100.11)" > /var/www/backend/index.html
[root@backend1 ~]# echo '{"code":200,"data":"Backend 1"}' > /var/www/backend/api/user

# 配置 Nginx 监听 8080 端口
[root@backend1 ~]# cat > /etc/nginx/conf.d/backend.conf <<'EOF'
server {
    listen 8080;
    server_name backend1.local;
    root /var/www/backend;
    index index.html;
}
EOF

[root@backend1 ~]# nginx -t && systemctl reload nginx
  • backend2 内容:Backend 2 (192.168.100.12) / {"code":200,"data":"Backend 2"}。
  • backend3 内容:Backend 3 (192.168.100.13) / {"code":200,"data":"Backend 3"}。

验证后端服务:

[root@lb1 ~]# curl http://192.168.100.11:8080/
Backend 1 (192.168.100.11)
[root@lb1 ~]# curl http://192.168.100.11:8080/api/user
{"code":200,"data":"Backend 1"}

2.3 准备 LB 静态资源

[root@lb1 ~]# mkdir -p /var/www/nginx/images
[root@lb1 ~]# echo "LB Home Page" > /var/www/nginx/index.html
[root@lb1 ~]# echo "LB Test Page" > /var/www/nginx/test.html

三、实验 1:单后端反向代理

3.1 实验拓扑

客户端通过 lb1 访问 backend1:8080

3.2 配置反向代理

在 lb1 上配置:

[root@lb1 ~]# cat > /etc/nginx/conf.d/proxy-single.conf <<'EOF'
server {
    listen 80;
    server_name proxy.magedu.com;

    location / {
        proxy_pass http://192.168.100.11:8080;
    }
}
EOF

[root@lb1 ~]# nginx -t && systemctl reload nginx

3.3 验证

[root@lb1 ~]# curl http://proxy.magedu.com/
Backend 1 (192.168.100.11)
[root@lb1 ~]# curl http://proxy.magedu.com/api/user
{"code":200,"data":"Backend 1"}

3.4 原理分析

请求到达 lb1、匹配 location、转发到后端并返回响应

关键点:客户端只知道 Nginx 的地址(proxy.magedu.com),对后端一无所知。后端的真实 IP 被 Nginx 完全隐藏。

四、实验 2:传递真实客户端信息(proxy_set_header)

4.1 问题场景

当 Nginx 转发请求时,后端看到的 remote_addr 是 Nginx 的 IP,而不是真实客户端 IP。

透传与未透传客户端 IP 的对比

4.2 为什么需要传递真实 IP?

场景 为什么需要
日志分析 统计真实用户来源,而非 Nginx 的 IP
安全审计 追踪攻击来源,封禁恶意 IP
地域封禁 根据客户端 IP 做地域访问控制
限流 按真实 IP 做请求限流

4.3 配置 proxy_set_header

[root@lb1 ~]# cat > /etc/nginx/conf.d/proxy-single.conf <<'EOF'
server {
    listen 80;
    server_name proxy.magedu.com;

    location / {
        proxy_pass http://192.168.100.11:8080;

        # 传递真实客户端信息
        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)
    }
}
EOF

[root@lb1 ~]# nginx -t && systemctl reload nginx

4.4 验证透传效果

在 backend1 上查看访问日志:

[root@backend1 ~]# tail -f /var/log/nginx/access.log
# 可以看到 X-Real-IP 为真实客户端 IP

原文未给出相应日志格式。默认访问日志通常不会记录 X-Real-IP;需在后端 log_format 中加入 $http_x_real_ip 才能直接看到该头部。设置请求头也不会自动改变后端的 remote_addr。

4.5 各头部的作用

头部 值 后端获取方式 作用
X-Real-IP $remote_addr request.getHeader("X-Real-IP") 客户端真实 IP(单层代理)
X-Forwarded-For $proxy_add_x_forwarded_for request.getHeader("X-Forwarded-For") 完整代理链(多层代理)
X-Forwarded-Proto $scheme request.getHeader("X-Forwarded-Proto") 原始协议(http/https)
Host $host request.getHeader("Host") 原始域名

生产最佳实践:proxy_set_header X-Real-IP $remote_addr; 是必配项,否则后端看到的全是 Nginx 的 IP。

五、实验 3:负载均衡

5.1 什么是负载均衡?

负载均衡 = 把请求分发到多台后端服务器,分摊压力、提高可用性。

用户流量由负载均衡器分发给三台后端

5.2 负载均衡的核心价值

价值 说明
高可用 一台后端宕机,请求自动切换到其他后端(健康检查)
高并发 多台后端分担请求,整体处理能力线性提升
弹性扩展 流量增长时,动态增加后端节点
灰度发布 部分后端运行新版本,逐步切流

5.3 upstream 语法

upstream 用于定义一组后端服务器:

upstream 组名 {
    server 后端地址1 [参数];
    server 后端地址2 [参数];
    server 后端地址3 [参数];
}

5.4 配置负载均衡

[root@lb1 ~]# cat > /etc/nginx/conf.d/proxy-lb.conf <<'EOF'
upstream backend {
    server 192.168.100.11:8080;
    server 192.168.100.12:8080;
    server 192.168.100.13:8080;
}

server {
    listen 80;
    server_name lb.magedu.com;

    root /var/www/nginx;

    # 静态资源:LB 直接返回
    location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|css|js)$ {
        expires 30d;
        access_log off;
    }

    # API 请求:转发到后端(负载均衡)
    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # 其他请求:LB 直接返回静态文件
    location / {
        try_files $uri $uri/ /index.html;
    }
}
EOF

[root@lb1 ~]# nginx -t && systemctl reload nginx

两者本质的区别:

对比 try_files $uri $uri/ /index.html; index index.html;
执行时机 每次请求都执行 仅在访问目录时触发
匹配路径 文件 → 目录 → 兜底 只检查目录
对 / 的访问 返回 /index.html 返回 /index.html
对 /about 的访问 先查找 /about 文件 → 再找 /about/ 目录 → 最后返回 /index.html 不会触发 index(因为没有访问目录)
对 /api/ 的影响 可能干扰,需注意 location 优先级 不影响

静态资源由 LB 返回,API 请求转发给后端

5.5 验证负载均衡(默认轮询)

[root@lb1 ~]# for i in {1..6}; do curl -s http://lb.magedu.com/api/user; done
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 2"}
{"code":200,"data":"Backend 3"}
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 2"}
{"code":200,"data":"Backend 3"}

结果:请求按顺序轮询分配到 3 台后端(backend1 → backend2 → backend3 → …),每台均衡承担 1/3 的请求。

六、实验 4:负载均衡算法对比

6.1 四种算法概览

算法 配置 适用场景
轮询(round-robin) 默认 后端性能均等
权重(weight) weight=N 后端性能不均
ip_hash ip_hash; 需要会话保持(本地 Session)
least_conn least_conn; 长连接场景(WebSocket、SSE)

6.2 算法选择决策树

按性能差异、会话保持和长连接需求选择调度算法

6.3 实验 4a:权重(weight)

场景:backend1 性能是 backend2 的 3 倍,需要按 3:1:1 分配流量。

[root@lb1 ~]# cat > /etc/nginx/conf.d/proxy-lb.conf <<'EOF'
upstream backend {
    server 192.168.100.11:8080 weight=3;   # 高性能机器
    server 192.168.100.12:8080 weight=1;   # 低性能机器
    server 192.168.100.13:8080 weight=1;   # 低性能机器
}

server {
    listen 80;
    server_name lb.magedu.com;

    root /var/www/nginx;

    location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|css|js)$ {
        expires 30d;
        access_log off;
    }

    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}
EOF

[root@lb1 ~]# nginx -t && systemctl reload nginx

[root@lb1 ~]# for i in {1..10}; do curl -s http://lb.magedu.com/api/user; done
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 2"}
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 3"}
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 2"}
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 3"}
{"code":200,"data":"Backend 1"}

结果:每 5 个请求中,backend1 收到 3 个(60%),backend2 和 backend3 各收到 1 个(20%),符合 3:1:1 的权重配比。

6.4 实验 4b:ip_hash(会话保持)

场景:传统应用使用本地 Session,同一用户必须访问同一后端。

[root@lb1 ~]# cat > /etc/nginx/conf.d/proxy-lb.conf <<'EOF'
upstream backend {
    ip_hash;
    server 192.168.100.11:8080;
    server 192.168.100.12:8080;
    server 192.168.100.13:8080;
}

server {
    listen 80;
    server_name lb.magedu.com;

    root /var/www/nginx;

    location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|css|js)$ {
        expires 30d;
        access_log off;
    }

    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}
EOF

[root@lb1 ~]# nginx -t && systemctl reload nginx

[root@lb1 ~]# curl -s http://lb.magedu.com/api/user
{"code":200,"data":"Backend 1"}
[root@lb1 ~]# curl -s http://lb.magedu.com/api/user
{"code":200,"data":"Backend 1"}
[root@lb1 ~]# curl -s http://lb.magedu.com/api/user
{"code":200,"data":"Backend 1"}

在后端集合和可用状态稳定时,同一客户端 IP 通常映射到同一后端;故障或拓扑变化可能使映射改变,不能把它当作绝对不变的会话持久性保证。

ip_hash 决策建议:

  • 使用 Redis 共享 Session → 不需要 ip_hash。
  • 使用本地 Session(传统应用)→ 需要 ip_hash。

6.5 实验 4c:least_conn(最少连接)

upstream backend {
    least_conn;
    server 192.168.100.11:8080;
    server 192.168.100.12:8080;
}

原理:Nginx 将请求分配给当前活动连接数最少的后端,避免某台后端因为长连接积累过多负载。

七、实验 5:被动健康检查

7.1 什么是健康检查?

健康检查 = Nginx 自动检测后端是否存活,故障节点自动剔除,恢复后自动加入。

健康检查分为两种:

类型 说明 优点 缺点
主动检查 Nginx 定期主动 ping 后端 及时发现问题 额外流量开销
被动检查 Nginx 根据请求失败判断 无额外流量 需要实际请求触发

Nginx 开源版支持被动健康检查(不需要额外模块)。

7.2 被动健康检查的工作原理

后端失败后暂时剔除,等待超时后通过实际请求重试

7.3 配置被动健康检查

[root@lb1 ~]# cat > /etc/nginx/conf.d/proxy-lb.conf <<'EOF'
upstream backend {
    server 192.168.100.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.100.12:8080 max_fails=3 fail_timeout=30s;
    server 192.168.100.13:8080 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name lb.magedu.com;

    root /var/www/nginx;

    location ~* \.(jpg|jpeg|png|gif|ico|svg|webp|css|js)$ {
        expires 30d;
        access_log off;
    }

    location /api/ {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location / {
        try_files $uri $uri/ /index.html;
    }
}
EOF

[root@lb1 ~]# nginx -t && systemctl reload nginx

7.4 参数说明

参数 默认值 说明
max_fails 1 失败 N 次后标记为不可用
fail_timeout 10s 标记不可用后,等待多久再重试

补充:fail_timeout 同时也是统计 max_fails 失败次数的时间窗口。被动检查在后续真实请求中重试,并非到时主动探测。

7.5 模拟故障

# 模拟 backend2 故障
[root@backend2 ~]# systemctl stop nginx

# 持续访问,观察故障转移
[root@lb1 ~]# for i in {1..20}; do curl -s http://lb.magedu.com/api/user; sleep 0.5; done
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 3"}
{"code":200,"data":"Backend 1"}
{"code":200,"data":"Backend 3"}
# ... backend2 不再出现

预期结果:

  1. backend2 连续 3 次请求失败后被标记为 down。
  2. 后续请求只转发给 backend1 和 backend3。
  3. 30 秒后,Nginx 尝试恢复 backend2。

7.6 查看健康检查状态

# 查看 Nginx 错误日志中的健康检查信息
[root@lb1 ~]# tail -f /var/log/nginx/error.log
# 可以看到 "upstream server temporarily disabled" 日志

生产实践:被动健康检查足以应对大多数场景。主动健康检查需要使用 Nginx Plus 或第三方模块。

八、实验 7:四层代理(L4 认知)

8.1 四层 vs 七层

Nginx 不仅能做 HTTP 代理,还能做 TCP/UDP 代理(四层代理)。

七层解析 HTTP 内容,四层基于 IP 和端口转发

8.2 配置 TCP 代理

[root@magedu ~]# vi /etc/nginx/nginx.conf

在 http 块后面,同一层级添加:

stream {
    upstream mysql_backend {
        server 192.168.100.20:3306 max_fails=3 fail_timeout=30s;
    }

    server {
        listen 3306;
        proxy_pass mysql_backend;
    }
}
[root@lb1 ~]# nginx -t && systemctl reload nginx

8.3 验证

[root@client ~]# mysql -h 192.168.100.10 -u wpuser -p
# 连接 lb1:3306,实际连接的是 mysql_backend

8.4 特别注意

stream 块和 http 块是平级的。

/etc/nginx/nginx.conf
│
├── user nginx;
├── worker_processes auto;
│
├── events { ... }
│
├── http {                     ← 七层代理(HTTP/HTTPS)
│   ├── include /etc/nginx/conf.d/*.conf;
│   └── server { ... }
│   }
│
└── stream {                   ← 四层代理(TCP/UDP),与 http 平级
    ├── upstream mysql_backend { ... }
    ├── upstream redis_backend { ... }
    └── server { ... }
    }

使用此配置需确保 Nginx 已编译或加载 stream 模块。

面试题

反向代理和负载均衡有什么区别?

反向代理关注的是“转发给谁”——隐藏后端服务器,提供统一入口。负载均衡关注的是“转发给哪一台”——在多台后端之间分发流量,分摊压力。

两者是组合关系:反向代理是负载均衡的前提,负载均衡是反向代理的扩展。生产环境中通常同时使用——Nginx 作为反向代理,将请求转发给多台后端,由负载均衡算法决定具体转发到哪一台。

Nginx 的 proxy_pass 末尾加斜杠和不加斜杠有什么区别?

答案:末尾带 / 会去掉 location 匹配的路径部分,不带 / 则原样转发。

以下为两种替代配置,不在同一个 location 中重复写 proxy_pass:

location /api/ {
    proxy_pass http://backend;    # /api/user → /api/user
}

或使用:

location /api/ {
    proxy_pass http://backend/;   # /api/user → /user
}

反向代理中为什么要配置 proxy_set_header X-Real-IP?

因为后端默认看到的 remote_addr 是 Nginx 的 IP,不是真实客户端 IP。配置后,后端可从 X-Real-IP 头获取真实 IP,用于日志分析、安全审计、地域封禁等场景。

补充实验:纯代理入口与 URI 路径替换

本稿以 22 页版为主体,保留静态资源/API 分流、JSON 验证和面试题;此处补入 20 页版独有的完整路径替换实验,重复基础内容不再另留一份文档。

所有请求都交给 upstream

前面的 /api/ 分流只代理 API。若使用纯负载均衡入口,可以在已有 backend upstream 的同一 http 上下文中配置:

server {
    listen 80;
    server_name all-lb.magedu.com;
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

无状态 JWT 或共享 Redis Session 一般不需要仅为本地会话而使用 ip_hash;least_conn 常用于连接持续时间差异较大的场景,如 WebSocket、SSE,是否合适还需看实际负载。

路径映射需求

场景 前端请求 后端期望
保留前缀 /api/user/1 /api/user/1
去前缀 /v1/user/1 /user/1
替换前缀 /old/user/1 /new/user/1

普通前缀 location 中 proxy_pass 的 URI 替换规则

在普通前缀 location 中,proxy_pass 指定 URI 时,用它替换匹配的前缀;没有指定 URI 时通常保留请求 URI。正则 location、变量形式、rewrite 等情况有额外规则,不能只靠“有没有末尾斜杠”概括全部行为。

server {
    listen 80;
    server_name path.magedu.com;

    location /api/ {
        proxy_pass http://192.168.100.11:8080;
    }
    location /v1/ {
        proxy_pass http://192.168.100.12:8080/;
    }
    location /old/ {
        proxy_pass http://192.168.100.13:8080/new/;
    }
}

将该配置保存为 /etc/nginx/conf.d/proxy-path.conf 后验证:

nginx -t && systemctl reload nginx
curl -v http://path.magedu.com/api/user/1
curl -v http://path.magedu.com/v1/user/1
curl -v http://path.magedu.com/old/user/1

域名需解析到 LB;前面的模拟站点只准备了 /api/user,这些新路径若没有对应文件会返回 404,可结合后端访问日志确认映射,不能把 404 直接判成代理规则未生效。

四层 MySQL 代理可按业务增加 proxy_connect_timeout 5s; 与 proxy_timeout 60s;,但后者会影响空闲长连接,实际不能照抄成所有数据库连接的通用超时。

评论