Redis 学习笔记:缓存穿透及解决方案
一、什么是缓存穿透?
缓存穿透是指用户请求的数据在 Redis 和 MySQL 中都不存在,导致请求无法命中缓存,每次都直接访问数据库。
例如:
用户请求 ID = 99999
↓
Redis:不存在
↓
MySQL:不存在
↓
返回空数据如果大量请求持续查询不存在的数据,就会给 MySQL 造成很大的压力,甚至导致数据库崩溃。
二、解决方案
1. 缓存空值
当 Redis 和 MySQL 都查询不到数据时,将一个特殊的空值标记写入 Redis。
例如:
Key:user:99999
Value:"__NULL__"
TTL:2分钟这样后续请求就可以直接命中 Redis,不再访问 MySQL。
注意事项:
空值需要设置较短的 TTL,例如 2~5 分钟。
需要区分缓存不存在和缓存的空值标记。
如果数据库新增了对应数据,需要删除或更新原来的空值缓存。
如果大量请求查询不同的无效 ID,仍可能产生大量空值缓存。
优点: 实现简单,适合大多数业务场景。
缺点: 首次请求仍然会访问数据库,大量无效 ID 还可能占用 Redis 内存。
2. 布隆过滤器(Bloom Filter)
在查询 Redis 之前增加一层布隆过滤器,提前判断数据是否可能存在。
核心原理:位数组 + 多个哈希函数。
例如,初始化一个位数组:
位置:0 1 2 3 4 5 6 7 8 9
数值:0 0 0 0 0 0 0 0 0 0添加用户 ID = 1001 时,通过三个哈希函数计算出位置 2、5、8,将对应位置设置为 1。
位置:0 1 2 3 4 5 6 7 8 9
数值:0 0 1 0 0 1 0 0 1 0后续查询时,重新计算哈希位置:
任意位置为 0:数据一定不存在。
所有位置为 1:数据可能存在。
为什么布隆过滤器会误判?
因为不同数据经过哈希计算后,可能落到相同的位置。
例如,某个 ID 没有被添加过,但其他数据恰好已经将它对应的位置全部设置为 1,布隆过滤器就会误认为它可能存在。
这种现象称为假阳性(False Positive)。
布隆过滤器判断不存在,一定不存在;判断存在,不一定真的存在。
注意:以上结论建立在布隆过滤器正确初始化、数据完整同步的前提下。
注意事项:
普通布隆过滤器不支持安全地直接删除单个元素。
数据库新增数据时,需要同步更新布隆过滤器。
如果同步失败,可能把真实存在的数据错误拦截,因此需要补偿、重建或降级机制。
优点: 内存占用较少、查询效率高,可以提前拦截大量无效请求。
缺点: 存在误判概率,需要维护布隆过滤器和数据库之间的数据一致性。
3. 主动防护措施
除了缓存空值和布隆过滤器,还可以从请求入口主动防止缓存穿透。
① 增加 ID 的复杂性
采用 UUID、雪花算法等方式生成 ID,避免使用简单、连续、容易遍历的数字 ID。
需要注意:
UUID v4 具有较高的随机性,不容易被猜测。
雪花 ID 主要保证分布式唯一性,并不保证不可预测。
② 增加参数校验
在访问 Redis 之前,先对请求参数进行合法性校验。
例如:
ID 不能为空。
ID 必须符合规定的格式、长度及取值范围。
对不符合规则的请求直接拒绝。
这样可以提前过滤一部分无效请求,避免访问 Redis 和 MySQL。
③ 增加接口限流
对高频查询接口进行限流,避免短时间内大量请求访问数据库。
常见方式:
根据用户 ID 限流。
根据 IP 地址限流。
根据接口或业务资源限流。
例如,限制某个用户每秒最多请求 10 次,超过限制直接拒绝或提示稍后重试。
限流不只是解决缓存穿透,也能用于防止恶意刷接口和保护热点资源。
三、完整请求流程
客户端请求
↓
参数校验、接口限流
↓
布隆过滤器
↓
判断数据是否存在
├── 不存在 → 返回空结果
│
└── 可能存在
↓
Redis
↓
├── 命中缓存 → 返回数据
│
├── 命中空值 → 返回空结果
│
└── 缓存未命中
↓
MySQL
↓
├── 存在 → 缓存正常数据
│
└── 不存在 → 缓存空值实际项目中,可以将多种方案组合使用,但不是所有系统都必须引入布隆过滤器,应根据业务数据量和请求特点选择。
四、总结
缓存穿透是指请求的数据在 Redis 和数据库中都不存在,导致每次请求都会直接访问数据库。
一般可以采用以下方式解决:
缓存空值:将数据库查询不到的数据以特殊空值标记写入 Redis,并设置较短的过期时间,避免重复访问数据库。
布隆过滤器:在 Redis 前增加过滤机制,提前拦截不存在的数据,但需要注意哈希碰撞导致的误判问题。
主动防护:通过提高 ID 的不可预测性、请求参数校验以及接口限流,减少无效请求和恶意请求。
实际项目中,可以根据业务场景组合使用这些方案。
核心记忆:
缓存空值:让不存在的数据也能命中缓存。
布隆过滤器:提前过滤不存在的数据。
参数校验:拒绝不合法的请求。
接口限流:限制高频访问,保护系统资源。
ID 设计:增加恶意枚举数据的难度。
