透明多级分流
透明多级分流通过在客户端 → CDN → 反向代理 → 应用 → 数据库各层设置缓存,让请求尽早返回。越靠近用户,缓存命中收益越大。
提示
客户端缓存、CDN、反向代理、分布式缓存的多级流量分流体系
客户端缓存
强缓存 vs 协商缓存
| 类型 | 响应头 | 行为 | 状态码 |
|---|---|---|---|
| 强缓存 | Cache-Control: max-age=86400 | 有效期内不发请求 | 200(from cache) |
| 协商缓存 | ETag / Last-Modified | 每次请求验证 | 304(未修改) |
强缓存优先级高于协商缓存。
缓存策略
| 内容类型 | 策略 |
|---|---|
| 静态资源(有 hash 名) | max-age=31536000 强缓存 |
| HTML 页面 | no-cache 每次协商 |
| API 接口 | no-store 不缓存 |
CDN
CDN 通过全球分布的边缘节点缓存静态资源,用户请求时 DNS 调度系统根据地理位置返回最近节点,命中缓存就近返回,未命中则回源站拉取并缓存。
适合内容:不频繁变动的静态资源。不适合内容:个性化内容、实时变动数据。
反向代理缓存
Nginx 在反向代理时缓存后端响应,减少对应用服务器的请求。
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=my_cache:10m;
server {
location /api/ {
proxy_cache my_cache;
proxy_cache_valid 200 60m; # 200 响应缓存 60 分钟
proxy_pass http://backend;
}
}分布式缓存(Redis)
缓存一致性策略
缓存与数据库的数据一致性是读多写少系统的核心问题。常用策略:
| 策略 | 读流程 | 写流程 | 特点 |
|---|---|---|---|
| Cache Aside(旁路缓存) | 先读缓存,未命中读 DB 回填 | 先更新 DB,再删除缓存 | 最常用,简单可靠 |
| Read/Write Through | 应用只与缓存交互 | 缓存代理负责同步 DB | 对应用透明,实现复杂 |
| Write Behind(异步写) | 同上 | 先写缓存,异步批量写 DB | 性能最高,可能丢数据 |
推荐 Cache Aside,两个要点:
- 先更新 DB 再删缓存(而非先删缓存再更新 DB),避免并发读写导致的旧值回填
- 删除失败兜底:延迟双删(更新 DB → 延迟 1s 删缓存)或订阅 binlog(Canal)异步删缓存,配合消息队列保证最终一致
三大问题
缓存穿透:查询数据库中不存在的数据,每次都绕过缓存直击数据库。解决:缓存空值(短 TTL)或布隆过滤器拦截。
布隆过滤器原理:一个位数组 + k 个哈希函数。写入时把 key 经过 k 个哈希映射到 k 个位并置 1;查询时校验 k 个位是否全为 1——全为 1 才可能存在,任一为 0 必然不存在。
- 优点:空间极小(亿级 key 仅需百 MB 级位图)、查询 O(k)
- 缺点:有误判率(可能把不存在的 key 判为存在,但绝不会把存在的判为不存在)、不支持删除(可换用计数布隆过滤器)
应用方式:初始化时把数据库全量 key 载入布隆过滤器,请求先查布隆过滤器,判定不存在直接返回,拦截 99%+ 的穿透流量。
缓存击穿:热点 key 过期瞬间,大量并发涌入数据库。解决:互斥锁(仅允许一个线程重建缓存)或热点数据永不过期。
缓存雪崩:大量 key 同时过期,大量请求打到数据库。解决:随机化 TTL + 多级缓存 + Redis 高可用。
互斥锁方案
public User getUserWithLock(Long id) {
User user = redis.get("user:" + id);
if (user != null) return user;
if (redis.setNX("lock:user:" + id, "1", 10, TimeUnit.SECONDS)) {
try {
user = userRepository.findById(id).orElse(null);
redis.set("user:" + id, user, 30, TimeUnit.MINUTES);
} finally {
redis.delete("lock:user:" + id);
}
}
return user;
}负载均衡分层
| 层级 | 代表技术 | 工作原理 |
|---|---|---|
| L4(四层) | LVS、F5 | 基于 IP + 端口转发 |
| L7(七层) | Nginx、Gateway | 解析 HTTP 内容智能路由 |
大流量入口用 L4 + L7 组合,微服务内部用 L7。
缓存层选型
| 缓存层 | 工具 | 缓存对象 |
|---|---|---|
| 浏览器 | Cache-Control / ETag | 静态资源 |
| CDN | 阿里云/腾讯云 CDN | 静态资源 + 非个性化内容 |
| 反向代理 | Nginx proxy_cache | 低频变化 API 响应 |
| 本地缓存 | Caffeine / Guava Cache | 高频读、不跨节点共享 |
| 分布式缓存 | Redis | 热点业务数据、Session |
适用场景
| 场景 | 推荐缓存层 | 原因 |
|---|---|---|
| 静态资源(JS/CSS/图片) | CDN + 浏览器强缓存 | 就近访问,减少带宽 |
| 高频读 API | Nginx 反向代理缓存 | 减轻应用服务器压力 |
| 热点业务数据 | Redis 分布式缓存 | 跨节点共享,支持持久化 |
| 用户 Session | Redis | 分布式会话管理 |
FAQ
Q: 缓存穿透、击穿、雪崩有什么区别? A: 穿透是查询不存在的数据,缓存永远不命中,请求直达数据库——解决:缓存空值或布隆过滤器。击穿是热点 key 过期瞬间,大量并发涌入数据库——解决:互斥锁或永不过期。雪崩是大量 key 同时过期,数据库压力骤增——解决:随机化 TTL + 多级缓存 + Redis 高可用。
Q: CDN 适合什么内容? A: 适合不频繁变动的静态资源(图片、视频、JS/CSS 文件、字体)。不适合:个性化内容(用户数据)、实时变动数据(股票行情)、需要鉴权的 API。动态内容可以用 CDN 的边缘计算能力处理,但成本较高。
Q: 本地缓存和分布式缓存如何选择? A: 本地缓存(Caffeine/Guava Cache)适合高频读、数据量小、不跨节点共享的场景(如配置信息、字典表),速度最快但各节点数据可能不一致。分布式缓存(Redis)适合需要跨节点共享、数据量大、需要持久化的场景。生产环境通常两者配合:本地缓存作为 L1,Redis 作为 L2。