高并发系统设计-缓存保护
一、概述
1.1 什么是缓存保护
缓存保护(Cache Protection)是指:
通过一系列缓存设计策略,避免缓存异常导致数据库被大量请求直接访问,从而保证系统稳定性。
典型架构:
Client
│
▼
Gateway
│
▼
Application
│
┌─────────┴─────────┐
│ │
▼ ▼
Local Cache Redis Cache
│
Cache Miss?
│
▼
MySQL
1.2 为什么需要缓存保护
Redis 可以承受几十万甚至百万级 QPS,而 MySQL 通常只能支撑几千 QPS。
例如:
Redis
100000 QPS
↓
MySQL
3000 QPS
如果 Redis 出现问题:
100000 请求
↓
全部进入 MySQL
↓
数据库连接耗尽
↓
整个系统崩溃
缓存的首要目标是保护数据库,其次才是提升访问性能。
1.3 企业缓存架构
企业通常采用:
Client
│
▼
Application
│
▼
Local Cache(Caffeine)
│
▼
Redis Cluster
│
▼
MySQL
同时配合:
- Bloom Filter
- 分布式锁
- 逻辑过期
- 多级缓存
- 消息队列
共同组成完整的缓存保护体系。
1.4 缓存问题分类
- 缓存穿透(Cache Penetration)
- 缓存击穿(Cache Breakdown)
- 缓存雪崩(Cache Avalanche)
- 热点 Key(Hot Key)
- 大 Key(Big Key)
- 缓存一致性(Cache Consistency)
这些问题如果处理不当,都可能导致数据库在短时间内承受无法处理的流量。
二、缓存穿透
2.1 介绍
缓存穿透是指:
查询一个数据库和缓存中都不存在的数据,导致每次请求都会直接访问数据库。
攻击者可以利用这一点发起恶意请求,使数据库承受巨大压力。
2.2 原理
由于缓存中没有记录"不存在"这一事实,所以每次都会重复查询数据库。
2.3 解决方案
方案一:缓存空值(Null Cache)
数据库不存在:
key -> NUL LTTL = 60s
下次直接命中空值。
优点:
- 简单
- 成本低
缺点:
- 占用少量缓存空间
- 数据新增后需要及时失效
方案二:Bloom Filter(企业推荐)
所有合法 Key:
UserID->Bloom Filter->不存在->直接返回
根本不会访问 Redis 和 MySQL。
优点:
- 内存占用极低
- 非常适合海量数据
缺点:
- 存在一定误判率(可能误判存在,但不会误判不存在)
方案三:接口参数校验
例如:
UserId < 0
↓
直接返回
避免无效请求进入业务系统。
三、缓存击穿(Cache Breakdown)
3.1 介绍
缓存击穿是指:
某一个热点 Key 在失效瞬间,大量请求同时访问数据库。
问题核心:
热点数据 + 缓存失效
3.3 解决方案
方案一:分布式锁(企业最常用)
只有一个线程回源数据库:
Redis Miss
↓
获取 Lock
↓
成功
↓
查询数据库
↓
写 Redis
↓
释放 Lock
其它线程等待或返回旧数据。
方案二:逻辑过期(推荐)
缓存不真正删除:
Redis
↓
数据 + expire_time
读取:
未过期
↓
直接返回
过期
↓
后台异步刷新
用户始终可以获取数据。
方案三:热点数据永不过期
例如:
- 系统配置
- 地区信息
- 字典数据
后台主动更新。
四、缓存雪崩(Cache Avalanche)
4.1 介绍
缓存雪崩是指:
大量缓存 Key 在同一时间失效,导致数据库压力骤增。
4.2 原理
Redis
100万个Key
↓
同一时间过期
↓
全部访问数据库
4.3 解决方案
方案一:随机过期时间(企业标准)
不要统一:3600
而是:3600~4200 秒
避免集中失效。
方案二:多级缓存
Local Cache
↓
Redis
↓
MySQL
降低数据库压力。
方案三:缓存预热
系统启动时:
提前加载热点数据。
五、热点 Key(Hot Key)
5.1 介绍
热点 Key 是指:
某一个缓存 Key 的访问量远高于其他 Key。
例如:
- 首页推荐
- 商品详情
5.2 原理
100万请求
↓
同一个Key
↓
Redis CPU飙高
5.3 解决方案
方案一、本地缓存
减少网络访问。
方案二、Redis Cluster
热点数据分散。
方案三、多副本缓存
例如
product:1001_1
product:1001_2
product:1001_3
随机读取。
六、大 Key(Big Key)
6.1 介绍
Big Key 是指:
单个 Key 存储的数据量过大。
例如:
一个 List,500万条数据
6.2 原理
读取:
GET
↓
几十 MB
↓
网络阻塞
删除:
DEL
↓
Redis 卡顿
6.3 解决方案
方案一、数据拆分
例如:
user:1:page:1
user:1:page:2
方案二、使用 SCAN
避免:
KEYS *
方案三、使用 UNLINK
异步删除:
UNLINK key
避免 Redis 主线程阻塞。
七、缓存一致性(Cache Consistency)
7.1 介绍
缓存一致性是指:
缓存中的数据与数据库中的数据保持一致。
7.2 原理
问题通常发生在:
更新数据库
↓
缓存未更新
或:
更新缓存
↓
数据库更新失败
7.3 解决方案
方案一、延迟双删
流程:
删除缓存
↓
更新数据库
↓
延迟删除缓存
方案二、Cache Aside(推荐)
读:
Redis
↓
MySQL
↓
Redis
写:
MySQL
↓
删除 Redis
这是目前企业最广泛采用的缓存模式。
方案三、MQ 异步更新
更新数据库
↓
发送 MQ
↓
更新缓存
适用于高并发场景。
八、最佳实践
建议组合使用:
Client
│
▼
Gateway
│
▼
Local Cache
│
▼
Redis Cluster
│
┌─────────┴─────────┐
│ │
Bloom Filter Mutex Lock
│ │
└─────────┬─────────┘
▼
MySQL
│
▼
MQ 更新缓存
推荐策略:
- 缓存穿透 → Bloom Filter + 空值缓存
- 缓存击穿 → 分布式锁 + 逻辑过期
- 缓存雪崩 → 随机 TTL + 缓存预热 + 多级缓存
- 热点 Key → 本地缓存 + 多副本 + Redis Cluster
- 大 Key → 数据拆分 + SCAN + UNLINK
- 缓存一致性 → Cache Aside + 延迟双删 + MQ