编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • 计算机原理

  • 网络协议

    • README
    • 通过看新闻熟悉网络
    • 通过购物熟悉加密
    • 从0到1部书电商网站
    • 请求网络的通用流程
    • 网络编程模型的概念
    • 传输协议TCP和UDP
    • Socket的发展和设计
    • 传输数据的设计思想
    • 网络域名解析的流程
    • HTTP服务设计流程
    • HTTP协议设计思想
    • HTTPS协议设计策略
    • HTTP连接和跳转
    • 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 第一代:单机Tomcat——无缓存无代理
        • 8.3 第二代:Nginx反向代理+浏览器缓存——初具规模
        • 8.4 第三代:CDN+多级缓存——规模化
        • 8.5 第四代:边缘计算+智能失效——终极形态
        • 8.6 四种方案横向对比
        • 8.7 案例升华:缓存与代理的设计哲学
        • 8.8 全文知识图谱回顾
      • 09.思考题与作业
        • 9.1 基础思考题
        • 9.2 进阶思考题
        • 9.3 动手作业
    • 如何去排查网络故障
    • WebSocket实时通信
    • HTTP3与QUIC协议
  • 操作系统

  • 数据库原理

  • 计算机
  • 网络协议
杨充
2023-10-30
目录

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 → 返回给用户
1
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 正向代理

正向代理代表客户端向服务器发送请求。服务器不知道真正的客户端是谁。

客户端 → [正向代理] → 服务器
         代表客户端       不知道真正的客户端
1
2

典型场景:

  • 翻墙/科学上网:客户端通过境外代理访问被屏蔽的网站
  • 企业网络管控:所有员工的网络流量经过代理,企业可以审计和过滤
  • 隐藏客户端身份:服务器只能看到代理的IP,看不到客户端真实IP

# 2.3 反向代理

反向代理代表服务器接收客户端的请求。客户端不知道真正的服务器是谁。

客户端 → [反向代理] → 服务器集群
不知道真正的服务器   代表服务器
1
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;  # 事故①
    }
}
1
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)
    ↓ 未命中
数据库
1
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天)
1
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%)→ 无缓存则回源
1
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
1

作用:追踪请求路径、防止循环(事故⑤的检测手段)。

# 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
1
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
1

# 5.4 透明代理设计

透明代理通过网络设备重定向流量,客户端无感知。常见于企业审计、运营商缓存。透明代理无法解密 HTTPS 流量。

# 06.缓存的深度设计

# 6.1 Vary头部与缓存Key

Vary 告诉缓存:"对于这个URL,还要根据这些请求头来区分缓存"。

Vary: Accept-Encoding, Accept-Language
→ 缓存Key = URL + Accept-Encoding值 + Accept-Language值
1
2

# 6.2 缓存失效策略

基于TTL过期、主动Purge、基于版本的缓存(推荐)、ETag标签验证。

# 6.3 缓存穿透与雪崩

1. 缓存穿透(事故③):
   不存在的key → 缓存中无 → 每次查DB
   防御:缓存空值(短TTL)、布隆过滤器

2. 缓存击穿:
   热点key过期瞬间 → 大量请求涌向DB
   防御:互斥锁、热点永不过期

3. 缓存雪崩(事故④):
   大量key同时过期 → DB被冲垮
   防御:过期时间加随机偏移(TTL = base + random(0, 300))、多级缓存、限流降级
1
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%
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→返回
无缓存、无代理、无负载均衡
1
2
3
4
V1 大促表现:
  QPS上限:~500
  P99延迟:800ms
  每条请求都查DB → DB CPU 70%
  事故风险:全部命中
  
瓶颈:单点瓶颈,Tomcat既要处理静态资源又要处理业务逻辑
1
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;
}
1
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
1
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)
1
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;
    }
}
1
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;
}
1
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(事故②仍需手动)
  - 个性化内容(用户名、购物车)无法缓存
1
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,每层都有兜底
1
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" /> ← 个性化部分实时请求
1
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 &lt; 10ms)
  L2: Nginx proxy_cache ← 4%请求(RTT ~50ms)
  L3: Redis缓存 ← 0.8%请求(RTT ~100ms)
  L4: MySQL ← 0.2%请求(RTT ~200ms)
1
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提升一个数量级,延迟降低一个数量级
1
2
3
4
5
6
7
8
9
10

# 8.7 案例升华:缓存与代理的设计哲学

代理和缓存的四个核心哲学:

1. 空间换时间(缓存的本质)
   CDN在全球部署数千节点,存储着海量副本 → 用户永远访问最近的副本
   存储成本 &lt;&lt; 带宽成本 + 延迟代价

2. 分而治之(多级缓存的智慧)
   边缘→区域→中心→源站,每层过滤一批请求
   L1过滤90% → L2过滤9% → L3过滤0.8% → 源站仅接收0.2%
   这是"漏斗模型"在缓存中的经典应用

3. 间接层万能定理
   "任何计算机问题都可以通过加一层间接层来解决"
   反向代理 = 客户端和服务器的间接层(负载均衡、SSL、缓存)
   CDN = 用户和源站的间接层(就近访问、安全防护)

4. 失效比命中更重要
   "缓存失效是计算机科学中最难的两个问题之一"
   命中率95%不难 → 难的是那5%不命中时系统不崩
   穿透、击穿、雪崩 —— 解决的不是"缓存能多快",而是"没缓存时能不能活"
1
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节]
1
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

最终的方法论沉淀——设计缓存和代理架构时,都应该问自己三个问题:

  1. 缓存了没?(哪些层有缓存?TTL 多长?怎么更新?→ 命中率决定性能上限)
  2. 失效了咋办?(穿透、击穿、雪崩防住了吗?→ 没缓存时的下限才是真实力)
  3. 中间人改了什么?(真实IP传了吗?头部改了吗?循环检测了吗?→ 代理是帮手也是透明人)

把这三个问题问到位,你就从"会用 Nginx"进化到了"能设计高可用缓存代理架构的工程师"。

# 09.思考题与作业

# 9.1 基础思考题

  1. 正向代理 vs 反向代理:分别举出 3 个正向代理和 3 个反向代理的典型应用场景。为什么 VPN 是正向代理,而 Nginx 是反向代理?

  2. 强缓存 vs 协商缓存:Cache-Control: no-cache 和 Cache-Control: no-store 的区别是什么?在 Chrome DevTools 中,两者各显示什么状态?

  3. CDN回源收敛:一个 CDN 有 500 个边缘节点,某个热门资源缓存同时过期。如果所有节点直接回源,源站会承受多大压力?引入 Origin Shield 后,源站只收到几个请求?这种设计叫什么?

  4. 缓存穿透的三种防线:缓存空值、布隆过滤器、参数校验,三者分别解决什么层面的穿透问题?如果用布隆过滤器来做第一道防线,误判率为 1% 时能挡住多少穿透请求?

# 9.2 进阶思考题

  1. 事故④的量化分析:200 个 key、TTL=600 秒、高峰期 3000 QPS。如果 TTL 中不加随机偏移,10 分钟后数据库会承受多大瞬时 QPS?加上 0~180 秒随机偏移后,同一时刻最多有多少个 key 过期?瞬时 QPS 降低到多少?

  2. 代理循环的检测:Via 头可以防止代理循环,但如果攻击者伪造 Via 头(不包含自己的标识),能绕过检测吗?除了 Via 头,还有哪些方法可以防止代理循环?(提示:请求ID、TTL计数)

  3. CDN Purge vs 版本化URL:CDN Purge 需要调用 API 清缓存,版本化 URL(file.a1b2.jpg)天然绕过缓存。两种方案各有什么优缺点?为什么大流量网站更推荐版本化 URL?

  4. 边缘计算的边界: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不合理),写出优化方案和预期收益。
上次更新: 2026/06/09, 15:47:57
HTTP连接和跳转
如何去排查网络故障

← HTTP连接和跳转 如何去排查网络故障→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式