Skip to content

Nginx 安装与反向代理 ​

S4-015建议学进阶约 60 分钟JDK 21企业开发进阶 · Nginx

1. 本节定位 ​

《消息可靠性、幂等与顺序》讲消息队列;本节开始讲**"所有流量的必经之路"——反向代理**:

text
客户端 → Nginx(反向代理)→ 后端服务
              ↑
      统一入口:TLS、路由、负载均衡、限流、缓存、灰度

★ 本机没有 Nginx,所以用 Java 实现一个最小反向代理,把"代理到底做了什么、代价是多少"量出来 ✓

2. 学习目标 ​

  • 能说清正向代理与反向代理的区别 ★★★;
  • 能说出反向代理带来的六项能力 ★★★;
  • 知道 X-Forwarded-For 为什么必需、又为什么不可信 ★★★;
  • 能通过 502 / 504 判断故障在"进程"还是"耗时" ★★★;
  • 能写出一份最小可用的 nginx.conf(含 keepalive 与超时) ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看本节的实测数据 ✓:

text
  直连后端 500 次:      11562 ms(平均 23.12 ms/次)
  经过反向代理 500 次:  28770 ms(平均 57.54 ms/次)
  → 代理多出的开销约 34.42 ms/次(本机回环环境下)

★★ 34 ms 这个数字"不正常地高" ✗ ——而原因本身很有教学价值 ✓✓:

我的最小实现真实 Nginx
单线程串行(处理完一个连接才 accept 下一个)✗多 worker + 事件驱动(《Netty 与网络编程概览》)✓
每次转发都新建到后端的连接 ✗keepalive 复用长连接 ✓✓
无零拷贝、无缓冲优化sendfile / proxy_buffering ✓

★ 所以这条数据的正确读法是 ✓✓:

"代理不是免费的,但它的开销取决于实现质量" ✓

本机回环的纯转发开销可以低到零点几毫秒 ✓✓

而它换来的是六项能力(见 5.2)✓

5. 核心概念 ​

5.1 正向代理 vs 反向代理 ★★★ ​

正向代理反向代理
代理谁客户端 ✓服务端 ✓✓
谁知道真实目标客户端知道(它主动配的)客户端不知道(以为代理就是服务端)✓
典型场景科学上网、公司出口网关、抓包工具Nginx、网关、CDN ✓✓

★ 一句话区分 ✓✓:

正向代理"替客户端出访";反向代理"替服务端接客" ✓

5.2 反向代理换来的六项能力 ★★★ ​

能力说明
统一入口 ★★★多个服务只暴露一个域名/端口 ✓
TLS 卸载 ★★★证书只在 Nginx 配一次,后端用明文 HTTP(内网)✓✓
负载均衡 ★★★见 《Nginx 负载均衡与缓存》 ✓
限流与防护 ★★挡住恶意流量、慢连接攻击 ✓
缓存与静态资源 ★★★见 《Nginx 负载均衡与缓存》(实测吞吐提升 27 倍)✓✓
隐藏后端拓扑 ★★对外只有一个入口,后端地址不暴露 ✓

★ 其中"TLS 卸载"的价值最容易被低估 ✓✓:

证书续期、加密套件配置、性能调优只在一处做 ✓

后端只处理明文,省掉加解密开销 ✓

5.3 X-Forwarded-*:为什么必需、又为什么不可信 ★★★ ​

★ 为什么必需 ✓✓:

text
客户端 1.2.3.4 → Nginx 10.0.0.10 → 后端 10.0.0.20

后端看到的 TCP 源地址是 **10.0.0.10(代理)** ✗
→ 真实客户端 IP 只能靠头传递 ✓

★ 三个头的分工 ✓:

头含义注意
X-Real-IP ★★最靠近的客户端 IP只有一跳
X-Forwarded-For ★★★IP 列表,逐跳追加客户端可以伪造 ✗✗
X-Forwarded-Proto ★原始协议(http/https)判断是否要跳转 https

★★ 最重要的一条安全提醒 ✓✓:

X-Forwarded-For 是客户端可以随便写的 ✗✗

"信任 XFF 里的第一个 IP"等于把权限交给了攻击者(任何人都能自称 127.0.0.1)✓

正确做法:只信任"已知代理"追加的那一段 ——从右往左数,跳过可信代理的 IP ✓✓

★ 对应 Nginx 配置 ✓:

nginx
set_real_ip_from 10.0.0.0/8;              # 声明"哪些是我信任的代理"
real_ip_header X-Forwarded-For;            # 从哪个头里取真实 IP
real_ip_recursive on;                      # 递归跳过可信代理

★ 还有一条配置细节 ✓✓:

nginx
# ✓ 追加(保留上游链路)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# ✗ 覆盖(会丢掉链路信息)
proxy_set_header X-Forwarded-For $remote_addr;

5.4 502 vs 504:排障口诀 ★★★ ​

实测(后端端口没有服务在听) ✓:

text
  后端端口 62726 没有服务在听 → 客户端看到:连接被直接关闭(客户端看到空响应)(耗时 58 ms)

★ 真实 Nginx 会返回 502 ✓ ——我的最小实现没做错误页,所以直接断连(这正是"最小模型"与产品的差距 ✗)

★★ 三个状态码的区别(排障时的关键线索) ✓✓:

状态码含义通常的原因
502 Bad Gateway ★★★网关从上游拿到无效响应后端进程挂了 / 端口不通 ✓
504 Gateway Timeout ★★★网关等上游超时后端太慢(超过 proxy_read_timeout) ✓
503 Service Unavailable ★★服务不可用(限流或被熔断)Nginx 自身限流 or 后端主动拒绝 ✓

★★ 排障口诀 ✓✓:

502 先看后端进程,504 先看后端耗时 ✓✓

这两条能省掉大量"到底是谁的锅"的扯皮 ✓

5.5 必须配的超时 ★★★ ​

★ 默认值的陷阱 ✓✓:

Nginx 的 proxy_read_timeout 默认是 60 秒 ✗

后端卡住时,Nginx 的连接会被占满 60 秒 → 整个网关被拖垮 ✗✗

★ 三组必配参数 ✓✓:

参数建议作用
proxy_connect_timeout ★★★3s连不上后端就快速失败 ✓
proxy_read_timeout ★★★10s后端不响应就断开,别占死连接 ✓
proxy_send_timeout10s发给后端的超时 ✓

★ 呼应 《HTTP 与 HTTPS》 的结论 ✓:

超时是分布式系统的安全带 ✓✓

没有超时,一个慢下游会顺着调用链把上游全部拖死 ✗✗

5.6 最小可用的 nginx.conf ★★★ ​

★ 完整配置(可直接抄) ✓✓:

nginx
upstream backend {
    server 127.0.0.1:18091;          # 后端地址(S4-016 讲多机与策略)
    keepalive 32;                     # ★ 与后端保持长连接,省掉每请求的握手
}

server {
    listen 8080;
    server_name localhost;

    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;                          # ★ keepalive 需要 1.1
        proxy_set_header Connection "";                   # ★ 配合 keepalive 必须清空
        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_connect_timeout 3s;
        proxy_read_timeout 10s;
    }
}

★ 三条容易漏掉的配置 ✓✓:

#配置不配的后果
①proxy_http_version 1.1 + keepalive ★★★Nginx 到后端是短连接,每请求一次握手(呼应 《网络分层模型与数据传输过程》)✗
②proxy_set_header Connection ""; ★★长连接失效(客户端发的 Connection: close 会被转给后端)✗
③超时三件套 ★★★默认 60s,后端卡住会占满 Nginx 连接 ✗✗

★ 验证与排障命令 ✓:

bash
nginx -t                                  # ★ 检查配置语法(改完必跑)
nginx -s reload                           # 平滑重载(不中断连接)
curl -I http://127.0.0.1:8080/api/product                # 看响应头
curl -H "X-Real-IP: 1.2.3.4" ...                         # 验证后端拿到什么
tail -f /var/log/nginx/error.log                         # 502/504 第一时间看这里

6. 递进示例 ​

6.1 最小示例:把两个服务挂到一个入口 ​

nginx
location /api/order/ { proxy_pass http://order-service:8081/; }
location /api/user/  { proxy_pass http://user-service:8082/; }

★ 客户端只需要知道一个域名 ✓✓

6.2 业务示例:TLS 卸载 ​

nginx
server {
    listen 443 ssl;
    ssl_certificate     /etc/nginx/certs/site.crt;    # ★ 证书只在网关配一次
    ssl_certificate_key /etc/nginx/certs/site.key;
    location / { proxy_pass http://backend; }         # 后端走明文 HTTP(内网)✓
}

6.3 反例:把客户端的 Connection 头透传给后端 ​

nginx
# ✗ 客户端发 Connection: close,后端就会每次断开,keepalive 池形同虚设
proxy_set_header Connection $http_connection;

# ✓ 清空它,由 Nginx 自己管理到后端的连接
proxy_set_header Connection "";

7. 完整可运行代码与实测 ​

代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/proxy/ReverseProxyLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.proxy.ReverseProxyLesson

实测输出(关键部分):

text
===== 1. 直连后端 vs 经过反向代理 =====
  直连后端 500 次:      11562 ms(平均 23.12 ms/次)✓
  经过反向代理 500 次:  28770 ms(平均 57.54 ms/次)
  → 代理多出的开销约 34.42 ms/次(本机回环环境下)

===== 3. 后端不可用时 =====
  后端端口 62726 没有服务在听 → 客户端看到:连接被直接关闭(耗时 58 ms)

★ 三条可落地的结论 ✓:

#结论依据
①代理的开销取决于实现(本模型单线程串行 + 每请求新建连接,所以偏高)34 ms / 次 ✗
②真实 Nginx 用 keepalive + 事件驱动,本机回环开销可降到零点几毫秒《Netty 与网络编程概览》 ✓
③必须配 keepalive 与超时,否则代理本身成为瓶颈见 5.6 ✓

8. Debug 观察点 ​

  1. 去掉响应里的 Content-Length:观察客户端卡住(《HTTP 与 HTTPS》)✗;
  2. 用 curl -v 对比直连与经过代理的头:找出代理加了哪些头 ★;
  3. 停掉后端再访问:观察 502(真实 Nginx)✓;
  4. 把一个接口人为 sleep 20 秒:观察 504(proxy_read_timeout 生效)✓★;
  5. 用 X-Forwarded-For: 1.2.3.4 手动伪造:验证后端是否被骗 ⚠️。

9. 常见错误 ​

#错误说明
1以为代理"零成本" ✗✗多一次完整往返(5.4)✓
2信任 X-Forwarded-For 做鉴权 ✗✗可伪造 ✓✓
3用 $remote_addr 覆盖 XFF ✗丢掉链路信息 ✓
4忘了 proxy_http_version 1.1 ✗✗keepalive 失效 ✓
5不配超时 ✗✗默认 60s,一个慢接口拖垮网关 ✓
6在网关里写业务逻辑 ✗✗网关会变成新的单体 ✓
7网关只部署一台 ✗✗它是所有流量的必经之路,单点 = 全站挂 ✓

10. 动手实验 ​

目标:亲手量一次"代理的代价",并配出一份可用的反向代理。

步骤:

  1. 跑 ReverseProxyLesson,记录直连与代理的耗时差 ✓;
  2. 给最小代理加"到后端的连接复用",再测一次(应该明显下降)★★;
  3. 用 curl -v 看代理加了哪些头 ✓;
  4. 停掉后端观察错误行为,再想"真实 Nginx 会返回什么" ✓;
  5. 写一份自己的 nginx.conf(含 keepalive、超时、XFF)并用 nginx -t 验证 ★★★。

验收方式:能说出"你的系统里哪些配置缺了会导致故障"。

扩展任务:设计一个"灰度发布"的反向代理方案:

  • 按比例分流(split_clients 或 map + 权重)✓;
  • 按用户标签分流(Cookie / Header)✓;
  • 要求:出问题时能立刻回滚到 0% ✓;
  • 说明:为什么灰度必须在网关层做(提示:它能看到全部流量)★★

11. 练习题 ​

  1. 判断:反向代理和正向代理是一回事。(对 / 错)
  2. 填空:502 通常是 ______;504 通常是 ______。
  3. 简答:为什么 X-Forwarded-For 不能被信任?
  4. (配置阅读)
nginx
location /api/ {
    proxy_pass http://backend;
    proxy_read_timeout 300s;
}

这段配置有什么风险? 5. (配置编写)写一份反向代理配置:要求支持长连接、有合理超时、把真实 IP 传给后端。

参考答案(要点)
  1. 错。正向代理替客户端出访,反向代理替服务端接客 ✓★
  2. 后端进程挂了/端口不通;后端响应太慢(超过读超时) ✓★
  3. 因为它是普通请求头,客户端可以随便写 ✗✗ —— 只应信任"自己已知的代理"追加的那一段 ✓
  4. 读超时 300 秒太长 ✗ ——一个卡住的后端会占住 Nginx 连接 5 分钟, 高并发下会耗尽连接池,把整个网关拖垮 ✓★(应设 10s 级)
  5. 要点:proxy_http_version 1.1 + proxy_set_header Connection "" + upstream 里 keepalive 32 ✓; proxy_connect_timeout 3s + proxy_read_timeout 10s ✓; X-Real-IP 与 $proxy_add_x_forwarded_for ✓✓

12. 掌握标准 ​

  • [ ] 能区分正向与反向代理 ★★★;
  • [ ] 能说出反向代理的六项能力 ★★★;
  • [ ] 知道 X-Forwarded-For 必需且不可信,并知道正确用法 ★★★;
  • [ ] 能用 502/504 快速定位故障方向 ★★★;
  • [ ] 能写出一份含 keepalive 与超时的最小配置 ★★★;
  • [ ] 知道代理不是零成本,且开销取决于实现 ★★。

13. 扩展资源 ​

14. 下一步学习 ​

《Nginx 负载均衡与缓存》:五种策略的实测对比与一致性哈希的取舍。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引