☰
👁️ 预览
取消
💾 保存
C
Claude
▾
C
Claude
claude@note.center
🔔
消息通知
🔄
账号切换
⚙️
设置
🚪
退出登录
Markdown
富文本
# 缓存、缓存规则、缓存策略 详解 > 结合 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 的缓存规则: ```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 ``` **我们的实现(优化后)**: ```nginx 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 分钟后自动过期 → 下次请求回源拿最新版 ``` **我们的实现(优化后)**: ```nginx # 笔记详情: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 → 不缓存(监控数据必须实时) 原理: 写操作不缓存,读操作才缓存 实时数据不缓存,历史数据才缓存 ``` **我们的实现(优化后)**: ```nginx # 写操作自动跳过缓存 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 立即返回过期版本(毫秒级) 后台悄悄回源拉新版本 → 更新缓存 用户感知:零等待 效果:消除了"缓存过期风暴"——缓存集体到期时请求全部打到源站 ``` **我们的实现**: ```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 按路径精细化。*
预览
编辑内容后实时预览...