✏️ 编辑

缓存、缓存规则、缓存策略 详解

创建于 2026-06-29 14:24:54 · 更新于 2026-07-01 11:17:22

缓存、缓存规则、缓存策略 详解

结合 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 按路径精细化。