0%

redis缓存问题

Redis 缓存常见问题及解决方案

Redis 作为高性能缓存中间件,在实际应用中可能面临缓存击穿、缓存穿透、缓存雪崩等问题,同时需保证缓存与数据库的数据一致性。本文详细解析这些问题的成因及针对性解决方案。

缓存击穿

问题定义

热点 key 过期瞬间,大量并发请求直接穿透缓存,直击数据库,导致数据库压力骤增(甚至宕机)。
例:某热门商品的缓存 key 过期,恰好此时有 1000 个并发请求查询该商品,全部打到数据库。

解决方案

1. 互斥锁(推荐)

原理:当缓存失效时,通过锁机制保证同一时间只有一个请求能查询数据库,其他请求等待重试,避免并发冲击。
步骤

  • 缓存未命中时,先尝试通过 SETNX 获取锁(如 SET lock:key 1 EX 5 NX)。
  • 成功获取锁:查询数据库,写入缓存,释放锁(DEL lock:key)。
  • 未获取锁:等待 50ms 后重试(循环几次后返回默认值)。

优点:简单有效,避免数据库过载。
缺点:可能增加请求延迟(等待锁),需合理设置锁超时时间(避免死锁)。

2. 热点 key 永不过期 + 定时更新

原理:不对热点 key 设置过期时间,而是通过定时任务(如 10 分钟一次)从数据库主动更新缓存,确保缓存始终有效。
适用场景:热点数据更新频率低(如商品基本信息)、可接受轻微延迟。
优点:彻底避免过期问题,性能稳定。
缺点:需维护定时任务,缓存可能存在短暂不一致(更新间隔内)。

缓存穿透

问题定义

请求缓存和数据库中均不存在的数据(如恶意攻击的非法 ID),导致每次请求都穿透到数据库,造成数据库压力过大。
例:黑客用大量不存在的用户 ID 发起请求,缓存未命中,全部打到数据库查询。

解决方案

1. 缓存空值(简单有效)

原理:数据库查询为空时,仍将空值写入缓存(如 SET invalid:id "" EX 60),设置较短过期时间(如 1-5 分钟)。后续请求会从缓存获取空值,避免穿透到数据库。
优点:实现简单,适合临时拦截无效请求。
缺点:可能缓存大量空值浪费内存(需控制过期时间和 key 前缀)。

2. 布隆过滤器(适合海量数据)

原理:预先将数据库中所有合法 key 存入布隆过滤器(一种概率性数据结构),请求先经过过滤器校验:

  • 若过滤器判断 key 不存在,直接返回;
  • 若存在(存在一定误判率),再查询缓存和数据库。

适用场景:数据相对固定(如用户 ID、商品 ID)、查询量极大的场景。
优点:拦截效率高(O (1) 时间复杂度),内存占用小。
缺点:存在误判率(需合理设置哈希函数和容量),不支持删除操作(需定期重建)。

3. 互斥锁(结合缓存空值)

原理:缓存未命中时,通过锁限制同一非法 key 的并发请求,仅允许一个请求查询数据库,查询为空后写入空值缓存,其他请求等待后从缓存获取。
适用场景:无效请求量中等,且需避免缓存大量空值。

缓存雪崩

问题定义

大量缓存 key 同时过期,或缓存服务宕机,导致所有请求瞬间穿透到数据库,造成数据库崩溃。
例:凌晨 0 点缓存集群重启,所有 key 同时失效,此时恰逢流量高峰,请求全部打到数据库。

解决方案

1. 过期时间随机化

原理:设置缓存过期时间时,添加随机值(如 EX 3600 + rand(0, 300)),避免大量 key 在同一时间过期。
优点:简单易行,从源头减少同时过期的概率。
缺点:无法解决缓存服务宕机的问题。

2. 互斥锁 + 重试机制

原理:缓存失效时,通过锁保证同一时间只有一个请求查询数据库,其他请求重试(结合指数退避策略),减少数据库瞬时压力。
适用场景:缓存过期导致的雪崩,不适合缓存服务宕机的场景。

3. 双缓存策略

原理:部署两个缓存(A 和 B):

  • 缓存 A:设置较短过期时间(如 10 分钟),正常提供服务;
  • 缓存 B:不设置过期时间,作为备份。
  • 访问流程:先查 A,命中则返回;未命中则查 B 并返回,同时异步更新 A 和 B。

优点:即使 A 全部过期,B 仍能提供服务,避免雪崩。
缺点:占用双倍内存,需维护异步更新逻辑。

4. 缓存高可用 + 限流降级
  • 高可用:部署缓存集群(如 Redis Cluster),避免单点故障;开启主从复制和哨兵模式,自动故障转移。
  • 限流降级:通过网关(如 Nginx)或代码层对请求限流(如每秒最多 1000 次查询数据库),超出部分返回降级提示(如 “系统繁忙,请稍后再试”)。

缓存与数据库一致性问题

问题定义

缓存与数据库的数据未同步(如更新数据库后未更新缓存),导致查询结果不一致。

解决方案

1. 延时双删(推荐)

步骤

  1. 删除缓存;
  2. 更新数据库;
  3. 延迟一段时间(如 500ms)后,再次删除缓存。

原理:第一次删除缓存避免旧数据被读取;延迟删除是为了覆盖 “更新数据库期间,可能有其他请求读取旧数据并写入缓存” 的场景,确保最终缓存与数据库一致。
注意:延迟时间需大于业务逻辑执行时间(如 500ms 或根据实际情况调整)。

2. 最终一致性(适合低实时性场景)

原理:更新数据库后不主动操作缓存,依赖缓存的过期时间自动失效,后续请求从数据库加载新数据到缓存。
适用场景:对一致性要求不高(如用户行为日志、统计数据)。
优点:实现简单,减少操作缓存的开销。
缺点:过期前存在数据不一致(可接受短期延迟)。

3. 加锁同步(强一致性场景)

原理:更新操作加分布式锁,确保同一时间只有一个请求更新数据库和缓存:

  • 读操作:先查缓存,未命中则查数据库,写入缓存后返回。
  • 写操作:加锁 → 更新数据库 → 删除缓存 → 释放锁。

适用场景:金融交易、库存等强一致性场景。
优点:保证数据实时一致。
缺点:加锁会降低并发性能,需控制锁粒度和超时时间。

欢迎关注我的其它发布渠道