HTTP代理和缓存设计
# 14.HTTP代理和缓存设计
# 目录介绍
- 01.工作案例引入
- 1.1 一次大促中缓存和代理的连环事故
- 1.2 事故背后的代理缓存知识图谱
- 02.HTTP代理设计
- 2.1 什么是代理
- 2.2 正向代理
- 2.3 反向代理
- 2.4 正向与反向代理对比
- 2.5 代理的典型应用
- 03.HTTP缓存设计
- 3.1 为什么需要缓存
- 3.2 缓存的层级
- 3.3 强缓存机制
- 3.4 协商缓存机制
- 3.5 缓存决策流程
- 04.CDN加速原理
- 4.1 CDN是什么
- 4.2 CDN的工作流程
- 4.3 CDN回源机制
- 05.代理协议与头部
- 5.1 Via头部追踪
- 5.2 X-Forwarded头部族
- 5.3 PROXY Protocol
- 5.4 透明代理设计
- 06.缓存的深度设计
- 6.1 Vary头部与缓存Key
- 6.2 缓存失效策略
- 6.3 缓存穿透与雪崩
- 6.4 边缘计算与ESI
- 07.CDN深度架构
- 7.1 CDN多级缓存架构
- 7.2 CDN智能调度
- 7.3 CDN安全防护
- 7.4 CDN与HTTPS
- 08.综合案例:从0到1搭建高可用缓存代理架构
- 8.1 案例背景与目标
- 8.2 第一代:无缓存无代理(裸奔模式)
- 8.3 第二代:Nginx反向代理+浏览器缓存(初具规模)
- 8.4 第三代:CDN+多级缓存(规模化)
- 8.5 第四代:边缘计算+智能失效(终极形态)
- 8.6 四种方案横向对比
- 8.7 案例升华:缓存与代理的设计哲学
- 8.8 全文知识图谱回顾
- 09.思考题与作业
- 9.1 基础思考题
- 9.2 进阶思考题
- 9.3 动手作业
# 01.工作案例引入
# 1.1 一次大促中缓存和代理的连环事故
场景:小孙是电商公司的运维工程师。公司准备"618 大促",小孙负责保障网站的高可用。架构很简单:Nginx 作为反向代理,后面挂着 4 台 Tomcat 应用服务器,静态资源已经接了 CDN。小孙觉得一切就绪。
大促开始后第三分钟,告警响了——不是一条,是五条。
事故 ① —— "后台看到的用户 IP 全是 Nginx 的 IP,没办法做风控":安全团队发现所有请求的源 IP 都是 10.0.0.5(Nginx 内网 IP),真正的用户 IP 丢了。原因是 Nginx 反向代理时没有配置 X-Forwarded-For 头部——Tomcat 只能看到直接连接它的 IP,也就是 Nginx。
事故 ② —— "首页改了广告图,有些用户看到的还是旧图":运营在大促中紧急更换了一张促销 banner 图(图片名称没变,内容替换了),但大量用户看到的还是旧图。小孙检查 CDN——图片的新版本已经上传到源站,但 CDN 边缘节点还缓存着旧版本,因为旧文件的 Cache-Control: max-age=86400(1 天),CDN 不会在 TTL 内回源更新。
事故 ③ —— "一个不存在的商品 ID,被刷了上万次,数据库 CPU 飙到 90%":攻击者用脚本不断请求 /api/product/99999999(不存在的商品 ID)。每次请求都穿透缓存查数据库,因为"查不到"这件事没有被缓存——这就是经典的缓存穿透。
事故 ④ —— "促销商品的热点缓存同时过期,瞬间 3000 QPS 打到数据库":大促开始时,运营一次性上架了 200 个促销商品,缓存时设置了统一的 TTL=600 秒。10 分钟后,200 个缓存在同一秒全部过期,所有请求涌向数据库。数据库连接池瞬间打满。
事故 ⑤ —— "运维说 Nginx 日志里出现了 Via: nginx, nginx, nginx...,请求在循环":运维在排查慢请求时发现 Via 头中同一个代理名出现了多次——请求在 Nginx 的反向代理和后端之间形成了转发循环。原因是某条 location 规则将 /api/ 代理到了 proxy_pass http://backend/,而该请求在某种条件下又被 backend 重新转发回 Nginx。
疑惑链条:
- "Nginx 后面为什么拿不到真实 IP?" → 反向代理默认只传递自己的 IP → 需要用
X-Forwarded-For或 PROXY Protocol 传递真实客户端 IP - "换了图片为什么 CDN 不更新?" → CDN 缓存按 TTL 过期 → TTL 内不会回源检查 → 需要主动 Purge 或改用文件 hash 命名(
banner.a1b2.jpg) - "查询不存在的数据为什么不缓存?" → 缓存穿透——"查不到"这件事如果不缓存,每次都穿透到 DB → 需要缓存空值或布隆过滤器
- "为什么缓存同时过期?" → 缓存雪崩——相同 TTL 等于定时炸弹 → 过期时间需要加随机偏移量
- "请求为什么在代理间循环?" → 代理转发规则没有终止条件 → Via 头可以检测循环并拒绝
小孙这一串问题,本质都是在问:代理到底改了什么?缓存什么时候命中、什么时候失效、什么时候会变成炸弹?CDN 的缓存和浏览器缓存有什么不同?——这正是"HTTP 代理和缓存设计"要回答的。
# 1.2 事故背后的代理缓存知识图谱
用户请求 https://www.shop.com/product/123 的完整路径:
浏览器
↓ ① 查浏览器缓存(强缓存/协商缓存)
浏览器缓存 ← 事故②的发生地(TTL过长未更新)
↓ 未命中
↓ ② DNS解析 → CDN智能DNS返回最近节点IP
CDN边缘节点
↓ ③ 查CDN缓存
CDN缓存 ← 事故②③④的发生地(缓存穿透/雪崩/不更新)
↓ 未命中 → 回源
↓ ④ Nginx反向代理
Nginx ← 事故①的发生地(丢失真实IP)
↓ ⑤ proxy_pass → 后端Tomcat
Tomcat(查本地缓存→Redis→DB)
↓ 返回响应
Nginx ← 事故⑤的发生地(转发循环)
↓ 返回给CDN(缓存)
CDN → 返回给用户
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
五类事故与后续章节的映射关系:
| 事故 | 症状 | 根因所在的知识点 | 对应章节 |
|---|---|---|---|
| ① | 丢失真实IP | 反向代理→未配置X-Forwarded-For | 05.代理协议与头部 |
| ② | 旧图不更新 | CDN TTL过长→无主动Purge | 03.强缓存、06.缓存失效 |
| ③ | 缓存穿透 | 不存在的数据不缓存→每次都查DB | 06.缓存穿透 |
| ④ | 缓存雪崩 | 200个key同时过期→DB被冲垮 | 06.缓存穿透与雪崩 |
| ⑤ | 代理转发循环 | location规则→请求环路→Via检测 | 05.Via头部追踪 |
本章的主线就是沿着这五类事故,一层一层拆解代理和缓存的深度设计。读完之后,你不仅能排查这些问题,还能理解为什么 CDN 是多级缓存架构、为什么 Nginx 是最流行的反向代理、为什么缓存失效是计算机科学中最难的问题之一。
# 02.HTTP代理设计
# 2.1 什么是代理
疑惑:客户端直接和服务器通信就好了,为什么要在中间加一个代理?
答疑:代理(Proxy)是客户端和服务器之间的"中间人",它接收客户端的请求,转发给服务器,再把响应返回给客户端。代理的价值在于:它可以在转发过程中做很多事情——缓存、过滤、负载均衡、安全审计、加速等。
# 2.2 正向代理
正向代理代表客户端向服务器发送请求。服务器不知道真正的客户端是谁。
客户端 → [正向代理] → 服务器
代表客户端 不知道真正的客户端
2
典型场景:
- 翻墙/科学上网:客户端通过境外代理访问被屏蔽的网站
- 企业网络管控:所有员工的网络流量经过代理,企业可以审计和过滤
- 隐藏客户端身份:服务器只能看到代理的IP,看不到客户端真实IP
# 2.3 反向代理
反向代理代表服务器接收客户端的请求。客户端不知道真正的服务器是谁。
客户端 → [反向代理] → 服务器集群
不知道真正的服务器 代表服务器
2
典型场景:
- 负载均衡:将请求分发到多台后端服务器
- SSL终止:在代理层处理HTTPS加解密,后端服务器只需处理HTTP
- 安全防护:隐藏真实服务器IP,防止直接攻击
- 缓存加速:缓存静态资源,减轻后端压力
回到事故①:Nginx 作为反向代理,默认只会把 Nginx 自己的 IP 传给后端。要传递真实客户端 IP,必须配置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for。
# 2.4 正向与反向代理对比
| 对比项 | 正向代理 | 反向代理 |
|---|---|---|
| 代表谁 | 客户端 | 服务器 |
| 谁配置 | 客户端配置 | 服务器端配置 |
| 隐藏谁 | 隐藏客户端 | 隐藏服务器 |
| 典型软件 | Shadowsocks, Squid | Nginx, HAProxy |
# 2.5 代理的典型应用
Nginx反向代理配置示例:
upstream backend {
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
}
server {
listen 80;
server_name api.example.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; # 事故①
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
回到事故⑤:代理转发循环可以通过配置 proxy_set_header Via 并在收到含自己标识的 Via 请求时拒绝转来防护——或更简单地,确保 location 规则的终止路径不会把请求重新导回 Nginx。
代理在转发请求时需要处理多个头部:
| 头部 | 处理方式 | 目的 |
|---|---|---|
| Host | 可能修改 | 让后端知道原始域名 |
| X-Real-IP | 添加 | 传递客户端真实IP |
| X-Forwarded-For | 追加 | 记录代理链 |
| X-Forwarded-Proto | 添加 | 传递原始协议 |
| Connection | 通常删除 | 逐跳头部不应转发 |
# 03.HTTP缓存设计
# 3.1 为什么需要缓存
疑惑:网络速度已经很快了,为什么还需要缓存?
答疑:即使网络速度再快,从服务器获取数据也需要:DNS解析 + TCP握手 + TLS握手 + 请求传输 + 服务器处理 + 响应传输。而缓存可以将数据存在离用户更近的地方,延迟几乎为零。
# 3.2 缓存的层级
浏览器缓存(内存/磁盘)
↓ 未命中
代理缓存(CDN/Nginx/Varnish)
↓ 未命中
源服务器(应用缓存/Redis)
↓ 未命中
数据库
2
3
4
5
6
7
# 3.3 强缓存机制
强缓存:客户端直接使用本地缓存,不向服务器发送任何请求。
Expires(HTTP/1.0):依赖客户端和服务器时钟同步,已不推荐。
Cache-Control(HTTP/1.1,优先级高于Expires):
| 指令 | 含义 | 适用场景 |
|---|---|---|
| max-age=N | 缓存N秒内有效 | 静态资源(CSS/JS/图片) |
| no-cache | 必须先验证才能使用 | 频繁变化的资源 |
| no-store | 禁止任何缓存 | 敏感数据 |
| public | 可被共享缓存存储 | CDN可缓存的公共资源 |
| private | 只能被私有缓存存储 | 包含用户信息的响应 |
| immutable | 资源永远不变 | 带hash的静态资源 |
疑惑:no-cache和no-store名字相似,有什么区别?
答疑:
no-cache并不是"不缓存"!它的意思是"可以缓存,但每次使用前必须向服务器验证"no-store才是真正的"不缓存"
回到事故②的解决方案:图片更新时,要么主动 CDN Purge,要么用 Cache-Control: max-age=60(短 TTL),要么改用 hash 文件命名(banner.a1b2c3.jpg,内容变=文件名变=新URL=天然绕过缓存)。
# 3.4 协商缓存机制
方式一:Last-Modified / If-Modified-Since(秒级精度) 方式二:ETag / If-None-Match(字节级精度,优先级高于 Last-Modified)
| 对比项 | Last-Modified | ETag |
|---|---|---|
| 精度 | 秒级 | 字节级 |
| 性能 | 只需比较时间 | 需要计算hash |
| 优先级 | 低 | 高 |
# 3.5 缓存决策流程
最佳实践(现代前端资源缓存策略):
HTML文件: Cache-Control: no-cache(每次验证)
CSS/JS(带hash): Cache-Control: max-age=31536000, immutable(一年)
API响应: Cache-Control: no-store 或 private, max-age=0
图片/字体: Cache-Control: max-age=2592000(30天)
2
3
4
5
6
核心思想:HTML是入口,必须最新;CSS/JS文件名包含hash,内容变则URL变,可安全超长缓存。
# 04.CDN加速原理
# 4.1 CDN是什么
CDN(Content Delivery Network,内容分发网络)由全球分布式边缘节点组成,将源站内容缓存到离用户最近的节点。
# 4.2 CDN的工作流程
1. DNS解析 → CNAME → CDN智能DNS → 返回最近节点IP
2. 用户请求CDN节点 → 有缓存则直接返回(命中率>90%)→ 无缓存则回源
2
# 4.3 CDN回源机制
回源策略:主动回源、被动回源、预热推送。
回源优化:合并回源、Stale-While-Revalidate过期缓存兜底、范围请求+条件请求。
回到事故②:CDN按 TTL 工作,TTL 内不会主动检查源站是否有新版本。解决方案是主动 Purge 或用版本化 URL。
# 05.代理协议与头部
# 5.1 Via头部追踪
Via: 1.1 proxy1.example.com, 1.0 proxy2.example.com
作用:追踪请求路径、防止循环(事故⑤的检测手段)。
# 5.2 X-Forwarded头部族
客户端(1.2.3.4) → 代理1(5.6.7.8) → 代理2(9.10.11.12) → 服务器
X-Forwarded-For: 1.2.3.4, 5.6.7.8
X-Forwarded-Proto: https
X-Real-IP: 1.2.3.4
2
3
4
5
安全注意:X-Forwarded-For 可以被客户端伪造!只信任自己控制的代理添加的IP。
回到事故①:正确配置后,Tomcat 从 X-Forwarded-For 取最左边(或从右往左跳过已知代理IP)获取真实客户端 IP。
# 5.3 PROXY Protocol
在 TCP 连接建立后、HTTP 数据发送前,先发送客户端信息,无法被伪造:
PROXY TCP4 1.2.3.4 5.6.7.8 12345 80\r\n
# 5.4 透明代理设计
透明代理通过网络设备重定向流量,客户端无感知。常见于企业审计、运营商缓存。透明代理无法解密 HTTPS 流量。
# 06.缓存的深度设计
# 6.1 Vary头部与缓存Key
Vary 告诉缓存:"对于这个URL,还要根据这些请求头来区分缓存"。
Vary: Accept-Encoding, Accept-Language
→ 缓存Key = URL + Accept-Encoding值 + Accept-Language值
2
# 6.2 缓存失效策略
基于TTL过期、主动Purge、基于版本的缓存(推荐)、ETag标签验证。
# 6.3 缓存穿透与雪崩
1. 缓存穿透(事故③):
不存在的key → 缓存中无 → 每次查DB
防御:缓存空值(短TTL)、布隆过滤器
2. 缓存击穿:
热点key过期瞬间 → 大量请求涌向DB
防御:互斥锁、热点永不过期
3. 缓存雪崩(事故④):
大量key同时过期 → DB被冲垮
防御:过期时间加随机偏移(TTL = base + random(0, 300))、多级缓存、限流降级
2
3
4
5
6
7
8
9
10
11
回到事故④:将 TTL 从统一的 600 改为 600 + random(0, 180),200 个 key 的过期时间分散在 600~780 秒之间,不会再同时失效。
# 6.4 边缘计算与ESI
ESI(Edge Side Includes):CDN边缘节点组装页面——静态部分从缓存读取,动态部分实时请求。现代 CDN 已发展到边缘计算(Cloudflare Workers、Lambda@Edge)。
# 07.CDN深度架构
# 7.1 CDN多级缓存架构
边缘节点 → 区域中心 → 中心节点(Origin Shield) → 源站
命中率递减:90%+ → 96%+ → 99%+ → <1%
2
多级架构实现回源收敛——100 个边缘节点→10 个区域中心→1 个 Origin Shield→源站只收到 1 个请求。
# 7.2 CDN智能调度
基于地理位置、网络质量、负载、运营商匹配度进行动态权重调度。实现方式:DNS调度(最常用)、HTTP重定向、Anycast。
# 7.3 CDN安全防护
DDoS防护(Tbps级带宽吸收)、WAF、Bot管理、源站隐藏、速率限制。
# 7.4 CDN与HTTPS
三种部署模式:全链路HTTPS、边缘HTTPS+回源HTTP、边缘HTTPS+回源HTTPS(自签名)。Keyless SSL 方案让 CDN 不需要持有私钥。
# 08.综合案例:从0到1搭建高可用缓存代理架构
本章用一个贯穿全文的实战案例——从一个"单机裸奔"的电商网站开始,经历四次架构升级,最终构建一个有 CDN+Nginx+多级缓存+边缘计算的高可用架构。
# 8.1 案例背景与目标
电商网站 www.shop.com,日活 100 万,大促期间 QPS 从平时的 2000 飙升到 20000。需要保证 99.9% 可用性。
| 版本 | 架构 | 缓存命中率 | 对应事故 |
|---|---|---|---|
| V1 | 单机Tomcat | 0% | — |
| V2 | Nginx反向代理+浏览器缓存 | ~40% | ① |
| V3 | CDN+多级缓存 | ~90% | ②③④ |
| V4 | 边缘计算+智能失效 | ~95%+ | 全部解决 |
# 8.2 第一代:单机Tomcat——无缓存无代理
V1架构:用户 → Tomcat(:8080) → MySQL
每次请求:DNS→TCP→Tomcat处理→查DB→返回
无缓存、无代理、无负载均衡
2
3
4
V1 大促表现:
QPS上限:~500
P99延迟:800ms
每条请求都查DB → DB CPU 70%
事故风险:全部命中
瓶颈:单点瓶颈,Tomcat既要处理静态资源又要处理业务逻辑
2
3
4
5
6
7
# 8.3 第二代:Nginx反向代理+浏览器缓存——初具规模
# V2 Nginx配置
server {
listen 80;
server_name www.shop.com;
# 静态资源 - 浏览器缓存
location /static/ {
root /var/www;
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
}
# 图片 - 短期缓存
location /images/ {
root /var/www;
expires 1h;
}
# API - 反向代理到Tomcat
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;
}
}
upstream backend {
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=3;
server 10.0.0.3:8080 weight=2;
server 10.0.0.4:8080 weight=2;
keepalive 64;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
V2 大促表现:
QPS上限:~4000(Nginx epoll + 4台Tomcat)
缓存命中率:~40%(静态资源命中,API仍回源)
P99延迟:300ms
事故①已解决:正确传递真实IP
瓶颈:API层无缓存,大促时20000 QPS仍然打满Tomcat
2
3
4
5
6
7
# 8.4 第三代:CDN+多级缓存——规模化
V3架构:
用户 → CDN边缘节点 → (未命中) → Nginx → Tomcat集群 → Redis缓存 → MySQL
CDN缓存层:
静态资源:/static/* max-age=2592000(30天)
图片资源:/images/* max-age=86400(1天)
HTML模板:/templates/* max-age=3600(1小时)
支持Purge API立即刷新
Nginx缓存层(proxy_cache):
热门API响应:proxy_cache_valid 200 60s
商品详情:proxy_cache_valid 200 300s
防缓存穿透:对null值缓存30s
防缓存雪崩:TTL = base + random(0, 60s)
2
3
4
5
6
7
8
9
10
11
12
13
14
# V3 Nginx + proxy_cache配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:100m
max_size=10g inactive=60m use_temp_path=off;
server {
location /api/product/ {
proxy_pass http://backend;
proxy_cache api_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 60s; # 成功响应缓存60秒
proxy_cache_valid 404 30s; # 404也缓存(防穿透——事故③)
proxy_cache_lock on; # 防击穿
add_header X-Cache-Status $upstream_cache_status;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Redis层(防穿透+防雪崩——事故③④):
// 防穿透:缓存空值
public Product getProduct(Long id) {
String key = "product:" + id;
Product product = redis.get(key);
if (product != null) {
return product == NULL_PLACEHOLDER ? null : product;
}
product = db.findById(id);
if (product == null) {
redis.setex(key, 30, NULL_PLACEHOLDER); // 空值缓存30秒(事故③)
} else {
int ttl = 300 + ThreadLocalRandom.current().nextInt(180); // 加随机偏移(事故④)
redis.setex(key, ttl, product);
}
return product;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
V3 大促表现:
QPS上限:~15000(CDN承担90%请求)
缓存命中率:~90%
CDN命中:90% → 回源仅10%到Nginx
P99延迟:80ms
事故③④已解决:空值缓存+TTL随机偏移
瓶颈:
- CDN缓存TTL固定,紧急换图需要Purge(事故②仍需手动)
- 个性化内容(用户名、购物车)无法缓存
2
3
4
5
6
7
8
9
10
# 8.5 第四代:边缘计算+智能失效——终极形态
V4架构新增:
1. 智能缓存失效:基于事件的主动Purge(CMS发布→自动Purge CDN)
2. ESI边缘组装:静态模板+动态用户信息在CDN节点组装
3. Stale-While-Revalidate:CDN过期后先给旧内容,后台异步更新
4. 自适应TTL:根据资源访问频率动态调整TTL
5. 分级降级:CDN→Nginx→Redis→DB,每层都有兜底
2
3
4
5
6
# V4 CDN配置(以Cloudflare示例)
# 1. 智能失效:CMS发布时调用 CDN Purge API
# curl -X POST "https://api.cloudflare.com/.../purge_cache" \
# -H "Authorization: Bearer xxx" \
# -d '{"files":["https://www.shop.com/images/banner.jpg"]}'
# 2. Stale-While-Revalidate(事故②的优化方案)
add_header Cache-Control "max-age=3600, stale-while-revalidate=86400";
# → 过期后1天内,先返回旧缓存,后台异步更新
# 3. ESI边缘组装
# 页面模板包含ESI标签,CDN节点实时组装
# <esi:include src="/api/user-greeting" /> ← 个性化部分实时请求
2
3
4
5
6
7
8
9
10
11
12
13
V4 大促表现:
QPS上限:~50000+
缓存命中率:~95%+
CDN:95% → 回源仅5%
P99延迟:30ms
事故②已解决:Purge API瞬时生效 + Stale-While-Revalidate兜底
架构分层:
L1: CDN边缘 ← 95%请求在此终结(RTT < 10ms)
L2: Nginx proxy_cache ← 4%请求(RTT ~50ms)
L3: Redis缓存 ← 0.8%请求(RTT ~100ms)
L4: MySQL ← 0.2%请求(RTT ~200ms)
2
3
4
5
6
7
8
9
10
11
12
# 8.6 四种方案横向对比
| 维度 | V1 单机 | V2 Nginx+浏览器 | V3 CDN+多级缓存 | V4 边缘计算 |
|---|---|---|---|---|
| QPS上限 | 500 | 4000 | 15000 | 50000+ |
| 缓存命中率 | 0% | ~40% | ~90% | ~95%+ |
| P99延迟 | 800ms | 300ms | 80ms | 30ms |
| 回源比例 | 100% | 60% | 10% | 5% |
| 防穿透 | ❌ | ❌ | ✅ | ✅ |
| 防雪崩 | ❌ | ❌ | ✅ | ✅ |
| 智能失效 | ❌ | ❌ | ❌ | ✅ |
| 对应事故 | — | ① | ③④ | ①②③④⑤ |
| 对应章节 | — | 2.5/5.2 | 6.3/7.1 | 6.2/6.4/7.2 |
QPS与延迟进化:
QPS上限 P99延迟
│ │
50000 ████ V4 800ms ████ V1
15000 ████ V3 300ms ████ V2
4000 ████ V2 80ms ████ V3
500 ████ V1 30ms ████ V4
关键洞察:缓存每多一层,QPS提升一个数量级,延迟降低一个数量级
2
3
4
5
6
7
8
9
10
# 8.7 案例升华:缓存与代理的设计哲学
代理和缓存的四个核心哲学:
1. 空间换时间(缓存的本质)
CDN在全球部署数千节点,存储着海量副本 → 用户永远访问最近的副本
存储成本 << 带宽成本 + 延迟代价
2. 分而治之(多级缓存的智慧)
边缘→区域→中心→源站,每层过滤一批请求
L1过滤90% → L2过滤9% → L3过滤0.8% → 源站仅接收0.2%
这是"漏斗模型"在缓存中的经典应用
3. 间接层万能定理
"任何计算机问题都可以通过加一层间接层来解决"
反向代理 = 客户端和服务器的间接层(负载均衡、SSL、缓存)
CDN = 用户和源站的间接层(就近访问、安全防护)
4. 失效比命中更重要
"缓存失效是计算机科学中最难的两个问题之一"
命中率95%不难 → 难的是那5%不命中时系统不崩
穿透、击穿、雪崩 —— 解决的不是"缓存能多快",而是"没缓存时能不能活"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 8.8 全文知识图谱回顾
小孙的五类事故
│
┌───────┬───────┼───────┬───────┐
│ │ │ │ │
①丢IP ②旧图 ③穿透 ④雪崩 ⑤循环
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
X-Forward CDN TTL 缓存穿透 TTL雪崩 Via循环
反向代理 强缓存 空值缓存 随机偏移 代理检测
[5.2] [3.3] [6.3] [6.3] [5.1]
│ │ │ │ │
└───────┴───────┼───────┴───────┘
│
┌───────────┴───────────┐
│ │
CDN多级架构 [7.1] 边缘计算 [6.4]
边缘→区域→中心→源站 ESI+Workers
│ │
└───────────┬───────────┘
│
V1→V2→V3→V4 缓存代理架构的四次进化
[第8章] QPS 500→50000
│
▼
空间换时间 / 分而治之 / 间接层万能 / 失效>命中
缓存与代理的四大设计哲学 [8.7节]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
最终的方法论沉淀——设计缓存和代理架构时,都应该问自己三个问题:
- 缓存了没?(哪些层有缓存?TTL 多长?怎么更新?→ 命中率决定性能上限)
- 失效了咋办?(穿透、击穿、雪崩防住了吗?→ 没缓存时的下限才是真实力)
- 中间人改了什么?(真实IP传了吗?头部改了吗?循环检测了吗?→ 代理是帮手也是透明人)
把这三个问题问到位,你就从"会用 Nginx"进化到了"能设计高可用缓存代理架构的工程师"。
# 09.思考题与作业
# 9.1 基础思考题
正向代理 vs 反向代理:分别举出 3 个正向代理和 3 个反向代理的典型应用场景。为什么 VPN 是正向代理,而 Nginx 是反向代理?
强缓存 vs 协商缓存:
Cache-Control: no-cache和Cache-Control: no-store的区别是什么?在 Chrome DevTools 中,两者各显示什么状态?CDN回源收敛:一个 CDN 有 500 个边缘节点,某个热门资源缓存同时过期。如果所有节点直接回源,源站会承受多大压力?引入 Origin Shield 后,源站只收到几个请求?这种设计叫什么?
缓存穿透的三种防线:缓存空值、布隆过滤器、参数校验,三者分别解决什么层面的穿透问题?如果用布隆过滤器来做第一道防线,误判率为 1% 时能挡住多少穿透请求?
# 9.2 进阶思考题
事故④的量化分析:200 个 key、TTL=600 秒、高峰期 3000 QPS。如果 TTL 中不加随机偏移,10 分钟后数据库会承受多大瞬时 QPS?加上 0~180 秒随机偏移后,同一时刻最多有多少个 key 过期?瞬时 QPS 降低到多少?
代理循环的检测:Via 头可以防止代理循环,但如果攻击者伪造 Via 头(不包含自己的标识),能绕过检测吗?除了 Via 头,还有哪些方法可以防止代理循环?(提示:请求ID、TTL计数)
CDN Purge vs 版本化URL:CDN Purge 需要调用 API 清缓存,版本化 URL(
file.a1b2.jpg)天然绕过缓存。两种方案各有什么优缺点?为什么大流量网站更推荐版本化 URL?边缘计算的边界:ESI 和 Cloudflare Workers 都能在 CDN 边缘做计算,但能力边界不同。请分析:什么逻辑适合放在 CDN 边缘?什么逻辑必须回源?(提示:数据一致性、计算复杂度、个性化程度)
# 9.3 动手作业
作业一(必做):用 Chrome DevTools 分析缓存行为。
- 打开
https://www.baidu.com,查看Network → Size列。 - 找出
(disk cache)和(memory cache)的资源,对比它们的Cache-Control和ETag头部。 - 强制刷新(Ctrl+Shift+R),观察哪些变成了 304,哪些变成了 200。
作业二(选做):用 Nginx 搭建反向代理+缓存服务器。
- 用 Docker 启动一个 Nginx,配置
proxy_pass反向代理到你的后端服务。 - 配置
proxy_cache,对GET /api/product/:id接口缓存 60 秒。 - 用
curl -I测试第一次请求(应返回X-Cache-Status: MISS)和第二次请求(应为HIT)。 - 模拟缓存穿透:请求一个不存在的 ID → 确认 Nginx 也会缓存 404(
proxy_cache_valid 404 30s)。
作业三(架构思考):分析你项目的缓存架构。
- 画出当前项目的请求链路图,标注每一层的缓存情况。
- 找出一个缓存相关的痛点(如穿透、雪崩、TTL不合理),写出优化方案和预期收益。