主题
界面
主题
《消息可靠性、幂等与顺序》讲消息队列;本节开始讲**"所有流量的必经之路"——反向代理**:
客户端 → Nginx(反向代理)→ 后端服务
↑
统一入口:TLS、路由、负载均衡、限流、缓存、灰度★ 本机没有 Nginx,所以用 Java 实现一个最小反向代理,把"代理到底做了什么、代价是多少"量出来 ✓
X-Forwarded-For 为什么必需、又为什么不可信 ★★★;先看本节的实测数据 ✓:
直连后端 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)✓
| 正向代理 | 反向代理 | |
|---|---|---|
| 代理谁 | 客户端 ✓ | 服务端 ✓✓ |
| 谁知道真实目标 | 客户端知道(它主动配的) | 客户端不知道(以为代理就是服务端)✓ |
| 典型场景 | 科学上网、公司出口网关、抓包工具 | Nginx、网关、CDN ✓✓ |
★ 一句话区分 ✓✓:
正向代理"替客户端出访";反向代理"替服务端接客" ✓
| 能力 | 说明 |
|---|---|
| 统一入口 ★★★ | 多个服务只暴露一个域名/端口 ✓ |
| TLS 卸载 ★★★ | 证书只在 Nginx 配一次,后端用明文 HTTP(内网)✓✓ |
| 负载均衡 ★★★ | 见 《Nginx 负载均衡与缓存》 ✓ |
| 限流与防护 ★★ | 挡住恶意流量、慢连接攻击 ✓ |
| 缓存与静态资源 ★★★ | 见 《Nginx 负载均衡与缓存》(实测吞吐提升 27 倍)✓✓ |
| 隐藏后端拓扑 ★★ | 对外只有一个入口,后端地址不暴露 ✓ |
★ 其中"TLS 卸载"的价值最容易被低估 ✓✓:
证书续期、加密套件配置、性能调优只在一处做 ✓
后端只处理明文,省掉加解密开销 ✓
★ 为什么必需 ✓✓:
客户端 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 配置 ✓:
set_real_ip_from 10.0.0.0/8; # 声明"哪些是我信任的代理"
real_ip_header X-Forwarded-For; # 从哪个头里取真实 IP
real_ip_recursive on; # 递归跳过可信代理★ 还有一条配置细节 ✓✓:
# ✓ 追加(保留上游链路)
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# ✗ 覆盖(会丢掉链路信息)
proxy_set_header X-Forwarded-For $remote_addr;实测(后端端口没有服务在听) ✓:
后端端口 62726 没有服务在听 → 客户端看到:连接被直接关闭(客户端看到空响应)(耗时 58 ms)★ 真实 Nginx 会返回 502 ✓ ——我的最小实现没做错误页,所以直接断连(这正是"最小模型"与产品的差距 ✗)
★★ 三个状态码的区别(排障时的关键线索) ✓✓:
| 状态码 | 含义 | 通常的原因 |
|---|---|---|
| 502 Bad Gateway ★★★ | 网关从上游拿到无效响应 | 后端进程挂了 / 端口不通 ✓ |
| 504 Gateway Timeout ★★★ | 网关等上游超时 | 后端太慢(超过 proxy_read_timeout) ✓ |
| 503 Service Unavailable ★★ | 服务不可用(限流或被熔断) | Nginx 自身限流 or 后端主动拒绝 ✓ |
★★ 排障口诀 ✓✓:
502 先看后端进程,504 先看后端耗时 ✓✓
这两条能省掉大量"到底是谁的锅"的扯皮 ✓
★ 默认值的陷阱 ✓✓:
Nginx 的
proxy_read_timeout默认是 60 秒 ✗后端卡住时,Nginx 的连接会被占满 60 秒 → 整个网关被拖垮 ✗✗
★ 三组必配参数 ✓✓:
| 参数 | 建议 | 作用 |
|---|---|---|
proxy_connect_timeout ★★★ | 3s | 连不上后端就快速失败 ✓ |
proxy_read_timeout ★★★ | 10s | 后端不响应就断开,别占死连接 ✓ |
proxy_send_timeout | 10s | 发给后端的超时 ✓ |
★ 呼应 《HTTP 与 HTTPS》 的结论 ✓:
超时是分布式系统的安全带 ✓✓
没有超时,一个慢下游会顺着调用链把上游全部拖死 ✗✗
★ 完整配置(可直接抄) ✓✓:
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 连接 ✗✗ |
★ 验证与排障命令 ✓:
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 第一时间看这里location /api/order/ { proxy_pass http://order-service:8081/; }
location /api/user/ { proxy_pass http://user-service:8082/; }★ 客户端只需要知道一个域名 ✓✓
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(内网)✓
}# ✗ 客户端发 Connection: close,后端就会每次断开,keepalive 池形同虚设
proxy_set_header Connection $http_connection;
# ✓ 清空它,由 Nginx 自己管理到后端的连接
proxy_set_header Connection "";代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/proxy/ReverseProxyLesson.java
运行:
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.proxy.ReverseProxyLesson实测输出(关键部分):
===== 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 ✓ |
Content-Length:观察客户端卡住(《HTTP 与 HTTPS》)✗;curl -v 对比直连与经过代理的头:找出代理加了哪些头 ★;proxy_read_timeout 生效)✓★;X-Forwarded-For: 1.2.3.4 手动伪造:验证后端是否被骗 ⚠️。| # | 错误 | 说明 |
|---|---|---|
| 1 | 以为代理"零成本" ✗✗ | 多一次完整往返(5.4)✓ |
| 2 | 信任 X-Forwarded-For 做鉴权 ✗✗ | 可伪造 ✓✓ |
| 3 | 用 $remote_addr 覆盖 XFF ✗ | 丢掉链路信息 ✓ |
| 4 | 忘了 proxy_http_version 1.1 ✗✗ | keepalive 失效 ✓ |
| 5 | 不配超时 ✗✗ | 默认 60s,一个慢接口拖垮网关 ✓ |
| 6 | 在网关里写业务逻辑 ✗✗ | 网关会变成新的单体 ✓ |
| 7 | 网关只部署一台 ✗✗ | 它是所有流量的必经之路,单点 = 全站挂 ✓ |
目标:亲手量一次"代理的代价",并配出一份可用的反向代理。
步骤:
ReverseProxyLesson,记录直连与代理的耗时差 ✓;curl -v 看代理加了哪些头 ✓;nginx -t 验证 ★★★。验收方式:能说出"你的系统里哪些配置缺了会导致故障"。
扩展任务:设计一个"灰度发布"的反向代理方案:
split_clients 或 map + 权重)✓;X-Forwarded-For 不能被信任?location /api/ {
proxy_pass http://backend;
proxy_read_timeout 300s;
}这段配置有什么风险? 5. (配置编写)写一份反向代理配置:要求支持长连接、有合理超时、把真实 IP 传给后端。
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 ✓✓X-Forwarded-For 必需且不可信,并知道正确用法 ★★★;《Nginx 负载均衡与缓存》:五种策略的实测对比与一致性哈希的取舍。