缓存、缓存规则、缓存策略 详解
缓存、缓存规则、缓存策略 详解
结合 HTTP 协议原理与缓存机制,深入理解缓存的三个核心概念。
本文基于个人运维平台(Nginx + Flask)的实际配置讲解。
一、什么是缓存?
1.1 本质定义
缓存(Cache) 的本质是:把数据放在离用户更近的地方,减少重复计算和传输。
生活中到处是缓存:
- 你常吃的菜记在心里,不用每次翻菜谱 → 大脑就是菜谱的缓存
- 书架上的常用书放在手边,不常用的收进箱子 → 桌面就是书架的缓存
- 便利店就在楼下,不用每次去几公里外的批发市场 → 便利店就是批发市场的缓存
计算机世界也一样:
无缓存时:
用户 ──→ 服务器(每次都要完整处理)
有缓存时:
用户 ──→ 缓存(命中就直接返回,不用碰服务器)
│
└─→ 服务器(仅首次或缓存过期时)
1.2 为什么要缓存
| 原因 | 说明 | 类比 |
|---|---|---|
| 减少延迟 | 数据从近处获取,不用远程传输 | 楼下便利店 vs 批发市场 |
| 降低负载 | 后端服务器处理更少请求 | 不用每次做饭都翻菜谱 |
| 节省带宽 | 数据在边缘就被截住了,不需要跨网传输 | 高速路上的收费站分流 |
1.3 缓存的代价
缓存不是免费的——用空间换时间:
- 占用存储空间(硬盘/内存)
- 数据可能陈旧(缓存了旧版本,用户看到的是过期内容)
- 缓存一致性问题(数据改了,缓存还没更新)
所以需要缓存规则和缓存策略来平衡"加速效果"和"数据新鲜度"。
二、缓存层级全景
一个 HTTP 请求从浏览器到源站,会经过多层缓存。以 https://zhoujiayi.xyz 的请求为例:
┌─ 浏览器 ─────────────────────────┐
│ ① 浏览器缓存(内存/磁盘) │ ← 规则:Cache-Control / Expires
│ 如果命中,请求根本不会发出去 │
└──────────┬───────────────────────┘
│ 未命中
▼
┌─ Nginx 反向代理 ──────────────────┐
│ ② Nginx proxy_cache(磁盘缓存) │ ← 规则:proxy_cache_valid / TTL
│ 按路径分不同缓存时间: │
│ / 60s(通用页面) │
│ /note/ 300s(笔记详情) │
│ /search 30s(搜索/标签) │
│ /gold 3600s(黄金价格) │
│ /monitor 不缓存(实时监控) │
│ /static/ Nginx 直读磁盘发文件 │
└──────────┬───────────────────────┘
│ 未命中 / 不缓存
▼
┌─ Flask 应用 ──────────────────────┐
│ ③ 应用处理(只有前两级都没命中才到)│
│ 处理完后通过响应头告诉 Nginx/浏览器 │
│ 该怎么缓存 │
└───────────────────────────────────┘
每一层都是上一层的"缓存"。离用户越近 = 越快,但容量越小、越可能过期。
注: 如果再上层叠加 CDN(如 Cloudflare),CDN 边缘节点会位于浏览器和 Nginx 之间,成为新的缓存层。参见第六节 CDN 原理。
三、缓存规则
3.1 规则的定义
缓存规则(Cache Rules) 是一组条件 + 动作的描述:什么情况下,对哪些内容,做怎样的缓存处理。
就像交通规则:
- "红灯停,绿灯行" → 条件(红/绿)+ 动作(停/行)
- "学校路段限速 30" → 条件(学校路段)+ 动作(限速)
Nginx 的缓存规则:
# 条件:访问 /static/ 路径
location /static/ {
# 动作:用 alias 直读磁盘,缓存 30 天
alias /root/note-center/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
3.2 规则由什么组成
所有缓存规则都包含几个核心要素:
| 要素 | 含义 | 举例 |
|---|---|---|
| 匹配条件 | 什么内容适用这条规则 | 路径 /static/*、文件类型 .jpg、HTTP 状态码 200 |
| 有效期(TTL) | 缓存多久 | 7 天、60 秒、30 天 |
| 存储位置 | 缓存存在哪里 | 内存、磁盘、CDN 边缘 |
| 验证方式 | 过期后怎么确认是否还新鲜 | ETag 对比、Last-Modified 时间比对 |
3.3 不同层次的缓存规则
| 层次 | 规则类型 | 表达方式 |
|---|---|---|
| 浏览器 | HTTP 头 | Cache-Control, Expires |
| CDN | 厂商控制台 | Cloudflare Page Rules / Cache Rules, 阿里云 CDN 规则 |
| Nginx | nginx.conf | proxy_cache_valid, proxy_cache_bypass |
| 应用代码 | 程序逻辑 | 手动 set/get 缓存 |
四、HTTP 缓存协议(最关键的基础)
这是所有缓存规则的基础。HTTP 协议定义了一套标准化的缓存协商机制,浏览器和 CDN 都遵守。
4.1 强缓存(不需要问服务器)
浏览器自己就决定了要不要用缓存,完全不用问服务器。
第一次请求:
浏览器 ──GET /static/style.css ──→ 服务器
浏览器 ←── 200 OK ───────────────── 服务器
Cache-Control: max-age=2592000
(告诉浏览器:这个文件 30 天内不会变)
30 天内再次请求:
浏览器:看了一眼 Cache-Control,才过了几天,没到期
浏览器:直接用本地缓存,不发请求 ✅
关键头字段:
| 头 | 说明 | 我们的配置 |
|---|---|---|
Cache-Control: max-age=2592000 |
缓存 2592000 秒(30天) | /static/ 已配 |
Cache-Control: public |
允许任何中间代理缓存 | 已配 |
Cache-Control: immutable |
文件不会变,无需重新验证 | 已配 |
Cache-Control: no-cache |
每次都问服务器(但可能用缓存) | 监控面板用(不缓存) |
Cache-Control: no-store |
完全不缓存 | /monitor 等实时页面用 |
Expires |
过期时间点(老协议,优先级低于 Cache-Control) | 已配 |
4.2 协商缓存(需要问服务器)
缓存过期了,但问一下服务器"这东西变了吗?"——没变就接着用。
缓存过期后的请求:
浏览器 ──GET /static/style.css ──→ 服务器
If-Modified-Since: Mon, 22 Jun 2026 ...
If-None-Match: "abc123"
浏览器 ←── 304 Not Modified ─────── 服务器
(服务器说:没变,用你缓存的吧)
浏览器:继续用本地缓存 ✅
关键头字段:
| 头 | 方式 | 原理 |
|---|---|---|
Last-Modified / If-Modified-Since |
时间对比 | 服务器返回最后修改时间,浏览器下次带着问"变了没" |
ETag / If-None-Match |
指纹对比 | 服务器返回内容的哈希指纹,浏览器下次带着问"指纹匹配吗" |
4.3 强缓存 vs 协商缓存
┌──────────┐
│ 发起请求 │
└────┬─────┘
│
┌─────▼──────┐
│ 强缓存生效? │ ← Cache-Control / Expires
└──┬───┬─────┘
有效 │ │ 无效/过期
┌──────┘ └──────┐
▼ ▼
┌────────────┐ ┌────────────────┐
│ 本地直接返回 │ │ 协商缓存生效? │ ← ETag / Last-Modified
│ 不发请求 │ └──┬───┬──────────┘
└────────────┘ 未变 │ │ 变了
┌────────┘ └────────┐
▼ ▼
┌──────────────┐ ┌────────────────┐
│ 304 Not │ │ 200 OK │
│ Modified │ │ 返回新内容 │
│ 用缓存版本 │ │ 重新缓存 │
└──────────────┘ └────────────────┘
五、缓存策略
5.1 策略的定义
缓存策略(Cache Strategy) 是在理解了缓存规则和 HTTP 协议之后,针对你的业务场景设计的一整套方案。
缓存规则回答"怎么做",缓存策略回答"怎么设计"。
5.2 常见的缓存策略
策略一:静态资源长缓存(Nginx 直读)
适用场景:CSS、JS、图片等不会变的文件
设计:
/static/style.css → Cache-Control: max-age=2592000(30天)
原理:
Nginx 不走 Flask,直接读磁盘发文件(alias 指令)
浏览器收到 30 天缓存指令,期间不再请求
为什么不走 proxy_pass?
proxy_pass 让请求先到 Flask 再到 Nginx 缓存
alias / Nginx 直读:Nginx 自己读磁盘,Flask 完全不知道有静态请求
Nginx 发静态文件用的是零拷贝技术,几乎不占 CPU
我们的实现(优化后):
location /static/ {
alias /root/note-center/static/; # Nginx 直读磁盘,不走 Flask
expires 30d;
add_header Cache-Control "public, immutable";
}
策略二:动态页面微缓存
适用场景:笔记详情页、首页列表
设计:
/note/33 → 缓存 5 分钟
/ → 缓存 1 分钟
原理:
5 分钟内 N 次访问同一笔记 → 只回源 1 次 → 剩下 N-1 次从缓存拿
5 分钟后自动过期 → 下次请求回源拿最新版
我们的实现(优化后):
# 笔记详情:5 分钟缓存(内容不常改)
location ~ ^/note/\d+$ {
proxy_cache micro_cache;
proxy_cache_valid 200 300s;
}
# 首页及其他页面:1 分钟缓存
location / {
proxy_cache micro_cache;
proxy_cache_valid 200 60s;
}
策略三:不缓存的内容
适用场景:增删改操作、实时监控面板
设计:
POST/PUT/DELETE → 永远不缓存
带 ?saved=1 / ?deleted=1 的请求 → 不缓存(刚操作完要看最新结果)
/monitor → 不缓存(监控数据必须实时)
原理:
写操作不缓存,读操作才缓存
实时数据不缓存,历史数据才缓存
我们的实现(优化后):
# 写操作自动跳过缓存
if ($request_method != GET) { set $cache_bypass 1; }
if ($args ~* "saved=|deleted=|restored=") { set $cache_bypass 1; }
proxy_cache_bypass $cache_bypass;
# 监控面板完全不缓存
location /monitor {
proxy_pass http://127.0.0.1:5001;
# 没有 proxy_cache 指令 → 不缓存
}
策略四:Stale-While-Revalidate(容忍过期)
适用场景:对新鲜度要求不高的页面
传统方式:
缓存到期 → 用户请求 → 等回源拿到新数据 → 返回
用户感知:等待时间长(尤其是后端慢的时候)
Stale-While-Revalidate:
缓存到期 → 用户请求 → Nginx 立即返回过期版本(毫秒级)
后台悄悄回源拉新版本 → 更新缓存
用户感知:零等待
效果:消除了"缓存过期风暴"——缓存集体到期时请求全部打到源站
我们的实现:
proxy_cache_use_stale error timeout updating;
# error: 后端挂了 → 用旧的顶着
# timeout: 后端超时 → 用旧的顶着
# updating: 后端正忙 → 先用旧的,别等
5.3 策略设计框架
设计缓存策略时,三个维度需要权衡:
┌── 新鲜度 ──┐
│ 多久要最新?│
└─────┬──────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
高(实时数据) 中(文章) 低(静态文件)
不缓存 TTL=5min TTL=30d
| 维度 | 问题 | 决策 |
|---|---|---|
| 频率 | 内容多久变一次? | 常变 → 短 TTL / 不变 → 长 TTL |
| 成本 | 回源一次多贵? | 贵(大文件/慢查询)→ 宁愿多缓存 |
| 容忍度 | 用户能接受旧数据吗? | 能(图片晚一分钟没关系)→ 长 TTL |
5.4 我们的缓存策略总表(优化后)
| 路径 | 缓存策略 | 缓存时间 | 设计理由 |
|---|---|---|---|
/static/* |
静态文件长缓存 | 30天 | 文件不变 + Nginx 直读磁盘,不走 Flask |
/note/数字 |
动态微缓存 | 5分钟 | 笔记内容不常改,但修改后要能较快看到 |
/ |
动态微缓存 | 1分钟 | 首页列表页,可能新增笔记 |
/search /tags |
短缓存 | 30秒 | 搜索/标签结果,变动较快 |
/trash /personal |
短缓存 | 1分钟 | 类似首页的列表 |
/gold |
长缓存 | 1小时 | 金价每天 09:46 才更新一次 |
/monitor |
不缓存 | — | 监控必须实时 |
六、CDN 原理与缓存
本节为知识拓展,介绍 CDN 的缓存机制。虽然个人运维平台暂未使用 CDN,
但了解 CDN 原理对理解"缓存的层次"至关重要。
6.1 CDN 是什么
CDN(Content Delivery Network,内容分发网络) 是一张遍布全球的缓存代理服务器网络。
无 CDN:
北京用户 ────────→ 服务器(杭州) 延迟 40ms
乌鲁木齐用户 ─────→ 服务器(杭州) 延迟 80ms
有 CDN:
北京用户 ──→ 北京 CDN 节点 ──→ 服务器
乌鲁木齐 ──→ 西安 CDN 节点 ──→ 服务器
CDN 节点 = 你内容的"分店",开在用户家门口
6.2 CDN 的工作流程
以 Cloudflare 为例,CDN 的工作流程:
① 用户访问 https://example.com
DNS 解析到 CDN 的 IP(因为开启了 CDN 代理)
② CDN 边缘节点收到请求
判断内容是否在缓存中
├── 缓存命中(HIT):直接返回,不回源
│ 北京用户 → 北京节点 → 直接返回
│ 上海用户 → 上海节点 → 直接返回
│ 两个节点各存一份,各管各的
│
└── 缓存未命中(MISS):回源拉取
北京节点 ──→ 源服务器 ──→ Nginx ──→ 应用
拉取到后,在北京节点缓存一份,再返回给用户
③ 下一次另一个北京用户请求同一个 URL
北京节点:我有缓存 → HIT → 直接返回
一次回源,万人受益
6.3 CDN 的缓存规则
CDN 厂商的缓存规则 VS Nginx 的缓存规则:
| 维度 | Nginx | Cloudflare |
|---|---|---|
| 配置方式 | nginx.conf | 控制台 / API |
| 规则引擎 | location 块 | Page Rules / Cache Rules |
| 匹配条件 | 路径正则 | URL 模式 + 扩展名 + 设备类型 + Cookie |
| 缓存 TTL | proxy_cache_valid |
Edge Cache TTL / Browser Cache TTL |
| 缓存 Key | proxy_cache_key(默认含 URL + 参数) |
Cache Key(可自定义忽略参数) |
6.4 CDN 缓存 vs Nginx 缓存
CDN 和 Nginx 不是替代关系,而是接力关系:
用户 ──→ CDN 边缘 ──→ Nginx ──→ 应用
↓ ↓
离用户近 离服务器近
存大热门 存小范围
容量大 容量小
TTL 长 TTL 短
CDN 扛住 80% 的请求(全球热门内容)
Nginx 扛住 15% 的请求(近源站的二次缓存)
应用只处理 5% 的请求(真正需要运算的)
6.5 CDN 的特殊能力
CDN 不仅仅是缓存,还有这些能力:
| 能力 | 说明 |
|---|---|
| DDoS 防护 | 攻击流量在边缘被清洗,源站不受影响 |
| SSL 终止 | 在 CDN 节点解密后回源,源站只需 HTTP |
| WAF | Web 应用防火墙,过滤 SQL 注入等恶意请求 |
| HTTP/2 + HTTP/3 | 现代协议加速连接复用 |
| 边缘计算 | 在 CDN 节点上运行自定义逻辑(Cloudflare Workers) |
| 图片优化 | 自动调整尺寸/格式/压缩比 |
6.6 什么场景下需要 CDN
| 场景 | 建议 |
|---|---|
| 静态资源多、全球用户访问 | ✅ 强烈推荐 |
| 国内用户访问,服务器在国外 | ✅ 推荐(CDN 加速回国链路) |
| 需要 DDoS 防护 | ✅ 推荐 |
| 个人运维平台,单人使用 | ❌ 不需要 |
| 国内服务器,只有自己访问 | ❌ 不需要 |
七、我们的缓存架构(当前)
通过优化后的缓存架构:
┌─ 浏览器 ────────────────────────────────────────┐
│ ① 浏览器缓存 │
│ /static/* → 30天(public, immutable) │
│ 动态页面 → 无强缓存(按 Nginx 返回的 TTL) │
└──────────────────────┬──────────────────────────┘
│
┌─ Nginx 反向代理 ──────────────────────────────────┐
│ ② proxy_cache(按路径分 5 级 TTL) │
│ /note/ → 5min / → 1min │
│ /search → 30s /gold → 1h │
│ /monitor → 不缓存 │
│ │
│ ③ Nginx 直读静态文件(alias) │
│ /static/* → Nginx 读磁盘直接返回 │
│ 不走 Flask、不占 Python 进程 │
│ │
│ 缓存兜底策略: │
│ - 后端挂了 → 用过期缓存顶着 │
│ - 后端正忙 → 先返回旧缓存,后台悄悄拉新 │
│ - 写操作 → 自动跳过缓存 │
└──────────────────────┬──────────────────────────┘
│
┌─ Flask 应用 ───────────────────────────────────────┐
│ ④ 只有前三级都没命中时才到 Flask │
│ 大部分场景下 Flask 只需处理动态请求 │
│ 静态请求完全由 Nginx 挡在外面 │
└───────────────────────────────────────────────────┘
优化前后的对比
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 静态文件路径 | proxy_pass → Flask → Nginx 缓存 |
Nginx 直读磁盘 |
| CDN | 配置了 Cloudflare IP 段但未实际接入 | 已清理残留配置 |
/ 缓存 |
15s | 60s |
/note/ 缓存 |
60s | 300s(5min) |
/search /tags |
10s(混在统一规则里) | 30s(独立规则) |
/gold 缓存 |
10s(每日更新一次,白缓存) | 3600s(1h) |
/monitor |
10s(无意义缓存) | 不缓存 |
| 配置文件 | 含 Cloudflare 22 行 + 兜底 server block | 干净简洁 |
八、概念总结
| 概念 | 一句话 | 类似生活中的 |
|---|---|---|
| 缓存 | 把数据放在离用户更近的地方 | 便利店 vs 批发市场 |
| 缓存规则 | "什么情况 + 怎么做"的具体配置 | 交通规则:红灯停、绿灯行 |
| 缓存策略 | 根据业务设计的一整套缓存方案 | 便利店选址:市区密集、郊区稀疏 |
| HTTP 缓存协议 | 浏览器和服务器之间的缓存通信规范 | 快递的"已签收/拒收/退回"标准流程 |
| CDN | 全球分布的缓存代理网络 | 连锁便利店开遍全国 |
| Nginx alias | 让 Nginx 直读磁盘文件,不走后端 | 便利店直接从仓库取货,不去工厂 |
九、最佳实践总结
设计缓存的思路
① 分内容类型(静态 / 动态 / 实时)
② 评估变更频率(天级 / 小时级 / 分钟级)
③ 确定新鲜度要求(精确 / 容忍过期 / 严格实时)
④ 选择缓存位置(浏览器 / Nginx / CDN)
⑤ 设定兜底策略(后端挂了怎么办?)
常见的陷阱
| 陷阱 | 问题 | 正确做法 |
|---|---|---|
| 一刀切缓存 | 所有页面同 TTL,实时页面看到旧数据 | 按路径区分 TTL |
| 从不缓存 | 后端压力大,响应慢 | 动静态分离,合理微缓存 |
| 过度缓存 | 改了内容看不到变化 | 写操作跳过缓存 + 合理 TTL |
| 忽视协商缓存 | 强缓存过期后每次都全量回源 | 配合 ETag / Last-Modified |
| CDN 过度依赖 | 单人低流量场景浪费成本 | 按需选择,Nginx 缓存有时足够 |
本文整理于 2026-06-29,基于个人运维平台(Nginx + Flask)的实际缓存配置撰写。
当日晚间优化:移除 Cloudflare 残留、静态文件改为 Nginx 直读(alias)、缓存 TTL 按路径精细化。