缓存架构设计

学习大规模缓存系统架构设计,包括多级缓存、缓存一致性策略、容量规划和降级容灾

缓存架构设计

多级缓存架构

标准三级缓存

┌──────────────────────────────┐
│  应用层                        │
│  ┌─────────────────────────┐  │
│  │ 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(估算)瓶颈
Redis10-15w(简单 get/set)CPU 单线程
Redis Cluster10w × 节点数跨槽位操作
Memcached20-30w网络带宽

缓存选型决策树

是否需要复杂数据结构?
├── 是 → Redis / Valkey
└── 否 → 是否需要持久化?
         ├── 是 → Redis / Valkey
         └── 否 → Memcached(性能更好)

是否需要集群?
├── 是 → Redis Cluster 或 客户端分片 Memcached
└── 否 → 单机 Redis 或单机 Memcached

是否需要极致性能?
├── 是 → Dragonfly(Redis 兼容,多线程)
└── 否 → 按以上规则选型

面试回答框架

  1. 先明确业务场景:缓存什么数据、读写比例、数据大小分布
  2. 再讲架构设计:为什么选单机/主从/集群,几级缓存
  3. 再讲一致性策略:Cache-Aside 还是 Binlog 订阅,为什么
  4. 再讲容量规划:内存估算、QPS 预估、节点数计算
  5. 最后讲容灾:缓存雪崩/击穿/穿透的应对、降级方案