Day 9-2 反向代理与负载均衡
本节学习 Nginx 作为“流量入口”的核心能力:反向代理和负载均衡。
Java 运维视角:本节为后续 Spring Boot 应用部署打基础。无论微服务还是传统 Web 应用,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 做负载均衡 | 高可用 |

反向代理 = 用户请求的“总入口” + 内网后端的“安全门” + 流量的“调度中心”。
二、实验环境准备
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(模拟后端服务) |

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 实验拓扑

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 原理分析

关键点:客户端只知道 Nginx 的地址(proxy.magedu.com),对后端一无所知。后端的真实 IP 被 Nginx 完全隐藏。
四、实验 2:传递真实客户端信息(proxy_set_header)
4.1 问题场景
当 Nginx 转发请求时,后端看到的 remote_addr 是 Nginx 的 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 优先级 | 不影响 |

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 不再出现
预期结果:
- backend2 连续 3 次请求失败后被标记为 down。
- 后续请求只转发给 backend1 和 backend3。
- 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 代理(四层代理)。

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 时,用它替换匹配的前缀;没有指定 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;,但后者会影响空闲长连接,实际不能照抄成所有数据库连接的通用超时。
评论