高并发项目中的缓存的常见问题及其解决方案

1.缓存穿透

  • 现象:大量请求查询数据库中不存在的数据(如恶意攻击),缓存未命中导致直接穿透到数据库。
  • 原因:非法请求利用系统漏洞频繁访问不存在的数据。
  • 解决方案
    • 布隆过滤器(Bloom Filter):在缓存层前增加布隆过滤器,预先存储所有合法 Key 的哈希值,快速拦截非法请求。
    • 缓存空值(Null Caching):对查询结果为空的 Key 缓存短期空值(如 5 分钟),避免重复穿透。
    • 请求校验:对参数合法性进行严格校验(如 ID 格式),拦截无效请求。
关于布隆过滤器的解释

是一种高效的概率型数据结构,用于快速判断一个元素是否属于某个集合。它的核心特点是空间效率极高(节省内存),但存在一定的误判率(False Positive,即可能误判不存在的元素为存在,但不会漏判存在的元素)。

核心原理

  1. 位数组(Bit Array)
    布隆过滤器底层是一个长度为 m 的二进制位数组(初始全为0)。
  2. 多个哈希函数(Hash Functions)
    使用 k 个不同的哈希函数,每个函数将元素映射到位数组的某个位置。
  3. 添加元素
    将元素依次通过 k 个哈希函数,得到 k 个位置,将这些位置的值设为1。
  4. 查询元素
    将元素依次通过同样的 k 个哈希函数,得到 k 个位置:
  • 如果所有位置的值为1,则认为元素可能存在(可能存在误判)。
  • 如果任一位置的值为0,则元素一定不存在(100%准确)。

2.缓存击穿

  • 现象:热点 Key 突然过期,大量并发请求直接冲击数据库。
  • 原因:高并发场景下热点数据失效导致瞬时数据库压力。
  • 解决方案
    • 互斥锁(Mutex Lock):使用分布式锁(如 Redis 的 SETNX)控制单线程重建缓存,其他线程等待后重试。
    • 逻辑过期:缓存不设置物理过期时间,数据中存储逻辑过期时间,异步更新缓存。
    • 永不过期策略:对极热点数据不设置过期时间,通过定时任务或消息队列异步更新。
2.1. 缓存击穿的核心原因

缓存击穿是指 某个热点 Key 在缓存中过期时,大量并发请求瞬间穿透缓存,直接访问数据库的现象。其本质原因是:

  • 热点 Key 同时失效:例如某个高频访问的 Key 设置了相同的过期时间,导致大量请求在缓存失效的瞬间涌入数据库。
  • 缺乏并发控制:没有在缓存失效时对数据库访问进行限流或互斥锁保护。
  • 缓存未预热:缓存失效后,未及时重建新缓存,导致后续请求继续穿透。

2.2 Redis 的存储结构与 Key 过期
  • Redis 的存储形式
    Redis 支持多种数据结构(如 String、Hash、List 等),但无论使用何种结构,Key 的过期机制是独立于数据结构的。当 Key 过期时,Redis 会自动删除它(无论其底层是 HashMap 还是其他结构)。

  • Key 过期的触发方式
    Redis 采用 惰性删除 + 定期删除 策略:

    • 惰性删除:当客户端尝试访问某个 Key 时,Redis 会检查其是否过期,若过期则删除。
    • 定期删除:Redis 周期性随机检查一批 Key,删除其中已过期的。

    因此,Key 过期本身并不会直接导致缓存击穿,真正的问题在于:
    大量并发请求在 Key 过期后同时触发缓存重建,且未有效控制对数据库的访问。

3.缓存雪崩

  • 现象:大量缓存 Key 同时失效或缓存集群宕机,导致数据库压力暴增。
  • 原因:缓存集中过期或缓存服务不可用。
  • 解决方案
    • 随机过期时间:为 Key 的过期时间添加随机值(如基础 1 小时 + 随机 0-10 分钟),分散失效时间。
    • 多级缓存架构:采用本地缓存(Caffeine) + 分布式缓存(Redis)的多级结构,降低单点风险。
    • 高可用设计:使用 Redis Cluster 或 Sentinel 实现集群高可用,避免单节点故障。

4. 缓存与数据库一致性

  • 现象:缓存数据与数据库数据不一致,导致业务逻辑错误。

  • 原因:异步更新延迟或并发写操作引发数据冲突。

  • 解决方案:

    双写策略:更新数据库后同步更新缓存(需处理失败重试)。

    延迟双删:先删缓存 → 更新数据库 → 延迟再次删缓存(应对并发读脏数据)。

    订阅数据库日志:通过 Canal 监听 MySQL Binlog,异步更新缓存,保证最终一致性。

5. 热点数据倾斜(Hotspot Skew)

  • 现象:某些 Key 访问量极高(如明星微博),导致单节点负载过大。
  • 解决方案
    • 本地缓存:在应用层使用本地缓存(如 Guava Cache)分流热点请求。
    • Key 分片:对热点 Key 添加随机后缀(如 key_1, key_2),分散到多个节点。
    • 限流降级:对热点接口实施限流(如 Sentinel),防止系统过载。
posted @ 2025-03-29 20:44  留梦&  阅读(68)  评论(0)    收藏  举报