缓存架构设计
学习大规模缓存系统架构设计,包括多级缓存、缓存一致性策略、容量规划和降级容灾
缓存架构设计
多级缓存架构
标准三级缓存
┌──────────────────────────────┐
│ 应用层 │
│ ┌─────────────────────────┐ │
│ │ L1: 本地缓存 (Caffeine) │ │ ← 纳秒级,进程内
│ └──────────┬──────────────┘ │
│ │ miss │
│ ┌──────────▼──────────────┐ │
│ │ L2: 分布式缓存 (Redis) │ │ ← 微秒级,集群共享
│ └──────────┬──────────────┘ │
│ │ miss │
│ ┌──────────▼──────────────┐ │
│ │ L3: 数据库 (MySQL/PG) │ │ ← 毫秒级,持久化
│ └─────────────────────────┘ │
└──────────────────────────────┘
各级对比
| 级别 | 技术选型 | 延迟 | 容量 | 一致性 |
|---|---|---|---|---|
| L1 本地 | Caffeine / Guava Cache | 纳秒级 | MB 级 | 弱 |
| L2 分布式 | Redis / Valkey / Memcached | 微秒级 | GB~TB 级 | 最终一致 |
| L3 持久层 | MySQL / PostgreSQL | 毫秒级 | TB~PB 级 | 强一致 |
实现要点
L1 本地缓存策略:
- 热点 key 自动提升(统计访问频率)
- 设置本地缓存上限(防止 OOM)
- 与 L2 保持一致性的策略:TTL 短过期 / 订阅更新 / 主动失效
L2 分布式缓存策略:
- Redis Cluster 分片(16384 槽位)
- 读写分离(主写 + 从读)
- 数据分类存储(热数据 Redis、温数据 SSD、冷数据 DB)
缓存一致性
Cache-Aside 模式(最常用)
写操作流程:
1. 更新数据库
2. 删除缓存(而非更新缓存)
读操作流程:
1. 读缓存 → hit → 返回
2. miss → 读数据库 → 写入缓存 → 返回
优点:简单,缓存只缓存热数据
缺点:并发写可能导致短期不一致
延迟双删
1. 删除缓存
2. 更新数据库
3. 等待 N 毫秒(通常 500ms-1s)
4. 再次删除缓存
目的:防止并发读写造成缓存脏数据
缺点:第二次删除可能失败,需配合重试
订阅 Binlog 更新
MySQL → Canal/Debezium → MQ → 缓存更新服务 → Redis
优点:与业务代码解耦,准实时
缺点:引入额外组件,运维复杂度提升
一致性方案对比
| 方案 | 一致性 | 复杂度 | 适用场景 |
|---|---|---|---|
| Cache-Aside + 删除 | 最终一致 | 低 | 大部分业务 |
| 延迟双删 | 较高 | 中 | 写并发较高 |
| Binlog 订阅 | 较高 | 高 | 多系统缓存一致性 |
| 分布式锁 | 强一致 | 高 | 金融/交易 |
缓存预热与降级
缓存预热
新集群上线或重启后,避免缓存全空导致 DB 压力骤增:
1. 离线预热:从 DB 导出热点数据批量写入 Redis
2. 在线预热:启动时限制 QPS 逐步加载热点 key
3. 惰性加载:第一个请求 miss 时加载(需配合并发控制)
# 离线预热脚本示例
redis-cli --pipe < preload.txt
# preload.txt 格式:
# SET key1 value1
# SET key2 value2
缓存降级
缓存不可用时的降级策略:
1. 本地缓存兜底 → 使用 Caffeine 最近的缓存快照
2. 限流降级 → 超出阈值直接返回默认值/空值
3. 熔断 → 连续失败 N 次后直接返回 fallback,等待恢复
Sentinel 限流
// 伪代码:Sentinel 限流降级
@SentinelResource(value = "getUser", fallback = "getUserFallback")
public User getUser(String id) {
User user = cache.get(id);
if (user == null) {
user = db.get(id);
cache.set(id, user);
}
return user;
}
public User getUserFallback(String id) {
return User.DEFAULT; // 降级返回默认值
}
容量规划
内存估算
所需内存 = 键值对大小 × 数量 × (1 + 冗余系数) × (1 + 碎片率)
≈ 1KB × 1000w × 1.3 × 1.2
≈ 15.6 GB
冗余系数:Redis 内部数据结构的额外开销(约 20-30%)
碎片率:jemalloc 内存分配碎片(通常 1.1-1.5x)
集群规模规划
# Redis Cluster
节点数 = max(3, Ceil(总内存 / (单节点内存 × 0.7)))
= max(3, Ceil(100GB / (16GB × 0.7)))
= max(3, Ceil(8.9)) = 9 节点
# 0.7 是安全水位(预留 30% 用于 BGSAVE、复制缓冲区等)
# Memcached 集群
节点数 = Ceil(总内存 / 单节点内存)
= Ceil(100GB / 16GB) = 7 节点
QPS 规划
| 服务 | 单节点 QPS(估算) | 瓶颈 |
|---|---|---|
| Redis | 10-15w(简单 get/set) | CPU 单线程 |
| Redis Cluster | 10w × 节点数 | 跨槽位操作 |
| Memcached | 20-30w | 网络带宽 |
缓存选型决策树
是否需要复杂数据结构?
├── 是 → Redis / Valkey
└── 否 → 是否需要持久化?
├── 是 → Redis / Valkey
└── 否 → Memcached(性能更好)
是否需要集群?
├── 是 → Redis Cluster 或 客户端分片 Memcached
└── 否 → 单机 Redis 或单机 Memcached
是否需要极致性能?
├── 是 → Dragonfly(Redis 兼容,多线程)
└── 否 → 按以上规则选型
面试回答框架
- 先明确业务场景:缓存什么数据、读写比例、数据大小分布
- 再讲架构设计:为什么选单机/主从/集群,几级缓存
- 再讲一致性策略:Cache-Aside 还是 Binlog 订阅,为什么
- 再讲容量规划:内存估算、QPS 预估、节点数计算
- 最后讲容灾:缓存雪崩/击穿/穿透的应对、降级方案