1. 今天做了什么
- 学习缓存基础概念:缓存作用与缓存成本
- 实现商户查询缓存和商户类型列表缓存
- 学习数据一致性解决方案:缓存更新策略、主动更新方案选择、删除缓存与更新缓存的选择、先删缓存与先更新数据库的选择
- 学习缓存穿透问题及解决方案
- 学习缓存雪崩问题及解决思路
2. 核心知识点
缓存作用与成本
- 作用:降低数据库压力,提高响应速度
- 成本:数据一致性、代码维护、运维
商户查询缓存流程
- 请求命中缓存,直接返回
- 未命中,查询数据库
- 未查询到,返回错误
- 查询到,建立缓存,返回结果
商户类型列表缓存与上述流程同理。
缓存更新三种策略
- 内存淘汰:Redis 自动进行,内存达到上限时淘汰数据
- 超时剔除:设置 TTL,到期后 Redis 自动删除
- 主动更新:编码手动删除缓存
根据对一致性的要求选择,也可以多种结合。
主动更新方案选择
三种方案中,开发者编码方式最优:在更新数据库的同时更新缓存。
操作缓存:删除还是更新
- 更新:频繁更新数据库时,每次都要更新缓存,若期间无查询,更新操作没有意义
- 删除:频繁更新数据库时,若期间无查询,只需对数据库操作一次
删除更优。
先删缓存还是先更新数据库
涉及线程安全问题:
- 先删缓存:删除缓存后,如果线程切换给一个查询操作,查询到旧数据写入缓存,造成数据库与缓存不一致
- 先更新数据库:查询操作未命中时从数据库查到旧数据,线程切换到更新数据库并删除缓存,查询操作再拿到线程时将旧数据写入缓存,造成一致性问题
先删缓存 vs 先更新数据库,谁更优:
- 先删缓存:线程切换发生在一个长时间段(查询数据库、处理数据)
- 先更新数据库:线程切换发生在一个极短时间(查到数据库与写入缓存之间的间隔)
- 先更新数据库更优:不一致问题还必须恰好在一个查询任务执行时进入更新数据库,概率较小
结论:先更新数据库,再删除缓存更优。
还要使用事务来保证更新数据库与缓存操作的原子性。
查询操作的变化
查询未命中、写入缓存时设置超时时间。
缓存穿透
缓存未命中且数据库无对应数据,无法建立缓存,这样的请求会一直作用到数据库上,造成数据库压力。
解决方案:
- 缓存空对象:简单,占用内存多,可能造成短期数据不一致
- 布隆过滤:复杂,占用内存少,有误判风险
- 其他方案
缓存空值方案:
- 未命中时且未查询到数据时,缓存空值
- 命中时不为 null 且不为空,返回数据
- 不为 null 但为空值,表示之前查过数据库未查到,直接返回错误,不进行后续查询
缓存雪崩
短时间 key 失效或 Redis 服务器异常,导致请求大量到达数据库。
解决方案:
- TTL 设置随机数
- Redis 集群
- 降级限流
- 多级缓存
3. 深度思考
4. 踩过的坑
5. 遗留问题 / 想不通
- Redis 集群、降级限流、多级缓存尚未学习,待后续在 Redis 高级与 SpringCloud 中学习