Redis 缓存策略
数据变更后的处理
当 MySQL 发生新增、修改、删除时,缓存也要跟着处理。常见思路是:
- 先修改 MySQL
- 删除 Redis 中对应的 key
- 下次查询时再按实时生成流程写回 Redis
第二步这里选择"删除缓存",不直接修改缓存里的值,原因是缓存里的数据可能不是数据库单表字段,可能经过了关联查询、计算、排序、过滤,直接改缓存值容易漏掉逻辑。
如果倒过来,先删缓存再改 MySQL 会出现什么情况呢?单一线程下一般是没有问题的;在并发情况下,可能出现这种顺序:
- 线程 A 删除缓存
- 线程 B 查询缓存未命中,读取旧 MySQL 数据,并写入 Redis
- 线程 A 修改 MySQL
这导致 Redis 里留下的还是旧数据,这类问题后续处理起来会比较麻烦。
缓存更新策略
缓存更新本质上是在处理两个问题:
- Redis 里应该提前放哪些数据
- MySQL 数据变化后,Redis 里的旧数据怎么办
定期生成
定期生成就是按固定周期把一批数据写入 Redis,比如每隔一段时间通过 shell / python 脚本从 MySQL 查出热门数据,再写入缓存。
适合场景:
- 数据变化频率不高
- 查询量比较大
- 允许短时间读到旧数据
它的好处是读请求来了以后可以直接查 Redis,请求链路比较短。问题是数据会有延迟,如果 MySQL 已经变了,但下一轮缓存生成还没执行,用户可能读到旧数据。
实时生成
实时生成也就是常见的 Cache Aside 读流程:
- 请求查 key,先访问 redis
- redis 命中则直接返回;未命中则查 MySQL
- MySQL 查询到数据,返回并更新 redis;未查到则直接返回
这种方式不会一开始就把所有数据都放进 Redis,而是用户访问到哪个 key,就把哪个 key 放进去。它适合数据量比较大、热点数据会自然出现的场景。经过一段时间的"动态平衡",redis 中存储的数据就逐渐成为热点数据了,这又会引出另一个问题:随着时间推移数据越写越多,逐渐达到 redis 的内存上限,旧的热点 key
访问变少了,新的热点 key 插入不进来,此时就要引进内存淘汰策略
注意事项
内存淘汰策略
如果 Redis 内存达到上限,就会触发淘汰策略(这里不一定是物理内存,还可能是 redis 本身的配置文件中规定的最大内存容量)
常见策略有以下几种👇
- FIFO(First in first out)先进先出
把缓存中存在时间最久的 key 给删掉 - LRU(Least recently used)淘汰最久未使用的
记录每个 key 的最近一次的访问时间,淘汰最近访问时间最老的 key - LFU(Least frequently used)淘汰访问次数最少的
记录每个 key 最近一段时间的访问次数,淘汰访问次数最少的 key - Random 随机淘汰
随机挑选一位幸运儿
缓存预热
redis 服务器首次接入的时候,服务器是没有任何数据的,所有的数据都会访问 MySQL 后再进入 redis,数据库的压力才会慢慢变小。缓存预热就是解决这种问题:它将定期生成和实时生成结合了,通过离线的方式,通过一些统计的途径,先把找到一批热点🔥数据,预先导入到 redis 中,能帮数据库承担一部分访问压力。等后续时间的推移,逐渐就使用新的热点数据来淘汰旧的热点数据
缓存穿透(penetration)
请求查询一个不存在的数据,Redis 没有,MySQL 也没有。每次请求都会打到 MySQL,缓存没有起到保护作用
一部分的原因🌧️
- 业务设计不合理,缺少必要的参数校验
- 开发/运维误操作将部分数据从数据库上误删
- 恶意攻击
常见处理:
- 缓存空值:MySQL 查不到时,把空结果也写入 Redis,并设置较短过期时间
- 布隆过滤器:先判断 key 是否可能存在,不可能存在就直接拦截
- 参数校验:明显非法的 id、页码、业务参数,不进入缓存和数据库查询流程
缓存空值要注意过期时间不能太长,不然真实数据后来被写入 MySQL 时,Redis 里还留着空结果,会让用户继续查不到。
缓存雪崩
在短时间内大量缓存 key 在同一时间失效,或者 Redis 整体不可用,导致缓存的命中率大幅下降,请求集中访问 MySQL,数据库的压力迅速上升,甚至直接宕机了。
出现这种情况的几个典型栗子🌰:
- redis 突然不可用 / 集群模式下大量节点宕机
- 某个时间点大量 key 同时过期
处理思路:
- 给过期时间加随机值,避免大量 key 同一时间失效
- 对特别核心的数据做缓存预热
- 对数据库访问加限流,避免请求把 MySQL 打满
缓存雪崩是"大面积缓存同时失效"的问题。沿着这个问题再往下看,会出现一个更具体的场景:不是很多 key 同时失效,而是针对热点 key 失效,大量请求都冲向同一份数据,这就是缓存击穿。
缓存击穿(breakdown)
相当于缓存雪崩的特殊情况,针对热点 key 突然失效时,大量并发请求同时发现 Redis 未命中,大量的请求访问到 MySQL 上
它和缓存雪崩的区别:
- 缓存雪崩:大量 key 同时失效,影响面大
- 缓存击穿:一个热点 key 失效,但访问量非常大
常见处理:
- 热点 key 设置更长的过期时间,或者用逻辑过期控制刷新
- 进行服务降级,如访问数据库时使用\[分布式锁] 🔒,限制请求数据库的并发数