Skip to content

什么是缓存雪崩、击穿、穿透?

缓存雪崩

  • 大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机,Redis同时无法处理大量请求,请求全部访问数据,可能造成数据库崩溃等一系列问题
  • 避免大量数据同时过期
    • 均匀设置过期时间:设置过期时间时加上一个随机数,保证不会同时过期
    • 互斥锁:保证同一时间一个请求读数据库,更新缓存
      • 未获取到锁的请求可以选择返回空值/默认值/已过期的值,或者等待缓存构建后读取
      • 需要设置超时时间,一个请求一直构建不完成,其他请求可以构建
    • 双 key 策略:
    • 后台更新缓存;
  • 避免Redis宕机:
    • 服务熔断:暂停业务对缓存服务的访问,直接返回错误,不会冲击数据库
    • 服务限流:部分业务可以到数据库完成请求,其他的直接拒绝访问
    • 构建 Redis 集群避免单机问题

缓存击穿

  • 缓存中热点数据过期,大量请求打到数据库
  • 可以看成是缓存雪崩的子集,使用避免大量数据同时过期的策略即可

缓存穿透

  • 用户访问的数据不在缓存也不在数据库中,大量请求到来,缓存构建不出来,导致问题
  • 原因一般有两种:
    • 业务误操作,将缓存和数据库中的数据删除了
    • 黑客攻击,大量读取不存在的数据
  • 解决方案:
    • 非法请求限制:判定请求参数是否合理,恶意请求直接返回错误
    • 缓存空值或默认值:数据库中查不到的字段,缓存一个空值或者默认值,后续就可以直接从缓存获取了
    • 布隆过滤器:存在的数据才会查数据库,否则直接返回错误

数据库和缓存如何保证一致性?

更新的顺序

  • 先更新数据库,再更新缓存
    • A 请求先将数据库的数据更新为 1
    • 然后在更新缓存前,请求 B 将数据库的数据更新为 2
    • 紧接着也把缓存更新为 2
    • 然后 A 请求更新缓存为 1
    • 数据不一致(数据库为2,缓存为1)
  • 先更新缓存,再更新数据库
    • A 请求先将数据库的数据更新为 1
    • 然后在更新数据库前,请求 B 将数据库的数据更新为 2
    • 紧接着也把缓存更新为 2
    • 然后 A 请求将数据库的数据更新为 1
    • 数据不一致(数据库为1,缓存为2)

可以看到更新顺序无论如何都可能出现不一致的问题 因此出现Cache Aside 策略(旁路缓存策略)。

写策略的步骤:

  • 更新数据库中的数据;
  • 删除缓存中的数据。

读策略的步骤:

  • 如果读取的数据命中了缓存,则直接返回数据;
  • 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。

更新删除的顺序

  • 先删除缓存,再更新数据库
    • A 请求先删缓存
    • 在更新数据库前 B 请求删缓缓存,更新数据库为 2
    • 获取数据请求到达,缓存中没有,从数据库读取数据为 2,放入缓存
    • A 将数据库的数据更新为 1
    • 数据不一致(数据库为1,缓存为2)
  • 先更新数据库,再删除缓存
    • A 先更新数据库为 1
    • A 删除缓存,这个时候 C 请求获取数据,更新缓存
    • 更新缓存成功前 B 更新数据库为 2
    • B 删除缓存
    • C 更新缓存为 1
    • 数据不一致(数据库为2,缓存为1)
      • 概率较小,更新缓存会更快
      • 可以通过缓存过期时间,虽然数据不一致,但是时间长了会恢复
    • 一般选择这个方式
      • 但是需要保证两个操作都成功才行
      • 需要重试机制保证
        • 1、消息队列,将删除缓存放入消息队列,多次失败,需要报错告警
        • 2、订阅 Mysql binlog,模拟自己是一个 mysql 从节点,解析 binlog 进行异步删除
        • 两者都是异步删除
  • 延迟双删
    • 删除缓存,更新数据库,睡眠一段时间再删除缓存
    • 这个睡眠时间很难把控,并且会阻塞当前请求,不建议

最建议先更新数据库,再删除缓存,但是更新数据库就会导致缓存被删除,会对缓存命中有影响,如果要求缓存命中率高,可以考虑

  • 先更新数据库再更新缓存,可以通过分布式锁防止多写,但是性能有影响
  • 更新缓存后,过期时间短一点(其实和直接删除差不多)

正在精进