Redis 缓存穿透解决
时间:2026-10-9 07:36 作者:emer 分类: 无
Redis 缓存穿透解决
缓存穿透是常见问题,恶意请求能把数据库打挂。
1. 什么是缓存穿透
现象: 请求的数据在缓存和数据库中都不存在,每次请求都直接打到数据库。
危害:
- 恶意攻击时数据库压力大
- 正常请求被拖慢
- 数据库CPU飙升
2. 为什么会穿透
- 查询一个不存在的ID
- 缓存没有命中
- 数据库也查不到
- 请求直接穿透到数据库
3. 解决方案一:缓存空值
思路: 即使数据库查不到,也在Redis存一个空值,设置短过期时间。
function getUser($id) {
$key = "user:$id";
$user = $redis->get($key);
if ($user !== false) {
// 空值标记
if ($user === 'null') {
return null;
}
return json_decode($user, true);
}
// 查数据库
$user = $db->query("SELECT * FROM users WHERE id = $id");
if ($user) {
// 存在就缓存1小时
$redis->setex($key, 3600, json_encode($user));
} else {
// 不存在也缓存,5分钟过期
$redis->setex($key, 300, 'null');
}
return $user;
}
优点: 简单有效
缺点: 占内存,可能被恶意key刷爆
4. 解决方案二:布隆过滤器
思路: 在缓存前面加一层布隆过滤器,不存在的key直接拦截。
原理:
- 所有存在的数据先存到布隆过滤器
- 请求先过布隆过滤器
- 不存在直接返回,不查缓存和数据库
PHP 示例(用 Redis 的布隆过滤器模块):
# 加载 RedisBloom 模块
redis-cli BF.ADD user:1
redis-cli BF.EXISTS user:1
function getUser($id) {
$key = "user:$id";
// 先过布隆过滤器
if (!$redis->rawCommand('BF.EXISTS', 'user_filter', $id)) {
return null; // 一定不存在
}
// 布隆说可能存在,再查缓存
$user = $redis->get($key);
if ($user) {
return json_decode($user, true);
}
// 查数据库...
}
优点: 内存占用小,效率高
缺点: 有误判率,实现复杂
5. 解决方案三:参数校验
思路: 从源头过滤非法请求。
function getUser($id) {
// 参数校验
if ($id <= 0 || !is_numeric($id)) {
return null;
}
// 正常查询逻辑...
}
适用: ID类查询,不合法直接拦截
6. 三种方案对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 缓存空值 | 简单 | 占内存 | 数据量不大 |
| 布隆过滤器 | 高效 | 误判,复杂 | 数据量大 |
| 参数校验 | 最直接 | 只防非法 | ID类请求 |
7. 生产建议
1)组合使用
- 先做参数校验
- 再缓存空值
- 大流量用布隆过滤器
2)空值过期时间要短
- 5分钟到10分钟
- 不要太长
3)监控异常请求
- 发现大量404要警惕
- 及时加防护
4)接口限流
- 用 Nginx 或网关限流
- 防止恶意刷
8. 缓存雪崩和击穿
缓存穿透: 查不存在的数据
缓存击穿: 热点key过期,大量请求打数据库
缓存雪崩: 大量key同时过期
区分清楚: 三个问题现象类似但原因不同,解决方案也不同。
总结
Redis 缓存穿透解决要点:
- 查不存在的数据穿透到数据库
- 缓存空值是最简单方案
- 布隆过滤器适合大数据量
- 参数校验从源头拦截
- 空值过期时间要短
- 生产建议组合使用
- 注意和雪崩、击穿区分开
- 监控异常请求
- 接口加限流
缓存穿透是面试高频题,也是生产常见问题,一定要掌握。