day32 商户查询缓存、缓存一致性与缓存穿透雪崩

1. 今天做了什么

  • 学习缓存基础概念:缓存作用与缓存成本
  • 实现商户查询缓存和商户类型列表缓存
  • 学习数据一致性解决方案:缓存更新策略、主动更新方案选择、删除缓存与更新缓存的选择、先删缓存与先更新数据库的选择
  • 学习缓存穿透问题及解决方案
  • 学习缓存雪崩问题及解决思路

2. 核心知识点

缓存作用与成本

  • 作用:降低数据库压力,提高响应速度
  • 成本:数据一致性、代码维护、运维

商户查询缓存流程

  1. 请求命中缓存,直接返回
  2. 未命中,查询数据库
  3. 未查询到,返回错误
  4. 查询到,建立缓存,返回结果

商户类型列表缓存与上述流程同理。

缓存更新三种策略

  • 内存淘汰:Redis 自动进行,内存达到上限时淘汰数据
  • 超时剔除:设置 TTL,到期后 Redis 自动删除
  • 主动更新:编码手动删除缓存

根据对一致性的要求选择,也可以多种结合。

主动更新方案选择

三种方案中,开发者编码方式最优:在更新数据库的同时更新缓存。

操作缓存:删除还是更新

  • 更新:频繁更新数据库时,每次都要更新缓存,若期间无查询,更新操作没有意义
  • 删除:频繁更新数据库时,若期间无查询,只需对数据库操作一次

删除更优。

先删缓存还是先更新数据库

涉及线程安全问题:

  • 先删缓存:删除缓存后,如果线程切换给一个查询操作,查询到旧数据写入缓存,造成数据库与缓存不一致
  • 先更新数据库:查询操作未命中时从数据库查到旧数据,线程切换到更新数据库并删除缓存,查询操作再拿到线程时将旧数据写入缓存,造成一致性问题

先删缓存 vs 先更新数据库,谁更优:

  • 先删缓存:线程切换发生在一个长时间段(查询数据库、处理数据)
  • 先更新数据库:线程切换发生在一个极短时间(查到数据库与写入缓存之间的间隔)
  • 先更新数据库更优:不一致问题还必须恰好在一个查询任务执行时进入更新数据库,概率较小

结论:先更新数据库,再删除缓存更优。

还要使用事务来保证更新数据库与缓存操作的原子性。

查询操作的变化

查询未命中、写入缓存时设置超时时间。

缓存穿透

缓存未命中且数据库无对应数据,无法建立缓存,这样的请求会一直作用到数据库上,造成数据库压力。

解决方案:

  1. 缓存空对象:简单,占用内存多,可能造成短期数据不一致
  2. 布隆过滤:复杂,占用内存少,有误判风险
  3. 其他方案

缓存空值方案:

  • 未命中时且未查询到数据时,缓存空值
  • 命中时不为 null 且不为空,返回数据
  • 不为 null 但为空值,表示之前查过数据库未查到,直接返回错误,不进行后续查询

缓存雪崩

短时间 key 失效或 Redis 服务器异常,导致请求大量到达数据库。

解决方案:

  1. TTL 设置随机数
  2. Redis 集群
  3. 降级限流
  4. 多级缓存

3. 深度思考

4. 踩过的坑

5. 遗留问题 / 想不通

  • Redis 集群、降级限流、多级缓存尚未学习,待后续在 Redis 高级与 SpringCloud 中学习