Redis

Redis 缓存

这篇主要整理 Redis 缓存的数据更新方式、淘汰策略和常见异常场景。

数据变更后的处理

当 MySQL 发生新增、修改、删除时,缓存也要跟着处理。常见思路是:

  1. 先修改 MySQL。
  2. 删除 Redis 中对应的 key。
  3. 下次查询时再按实时生成流程写回 Redis。

第二步选择“删除缓存”,不直接修改缓存里的值。原因是缓存里的数据可能不是数据库单表字段,可能经过了关联查询、计算、排序、过滤,直接修改缓存值容易遗漏逻辑。

如果倒过来,先删缓存再改 MySQL,在单一线程下一般没有问题;在并发情况下,可能出现这种顺序:

  1. 线程 A 删除缓存。
  2. 线程 B 查询缓存未命中,读取旧 MySQL 数据,并写入 Redis。
  3. 线程 A 修改 MySQL。

这会导致 Redis 里留下的还是旧数据,后续处理起来会比较麻烦。

缓存更新策略

缓存更新本质上是在处理两个问题:

  • Redis 里应该提前放哪些数据。
  • MySQL 数据变化后,Redis 里的旧数据怎么办。

定期生成

定期生成就是按固定周期把一批数据写入 Redis。比如每隔一段时间,通过 shell 或 Python 脚本从 MySQL 查出热门数据,再写入缓存。

适合场景:

  • 数据变化频率不高。
  • 查询量比较大。
  • 允许短时间读到旧数据。

它的好处是读请求来了以后可以直接查 Redis,请求链路比较短。问题是数据会有延迟:如果 MySQL 已经变化,但下一轮缓存生成还没执行,用户可能读到旧数据。

实时生成

实时生成也就是常见的 Cache Aside 读流程:

  1. 请求查询 key,先访问 Redis。
  2. Redis 命中则直接返回;未命中则查询 MySQL。
  3. MySQL 查询到数据,返回并更新 Redis;未查询到则直接返回。

这种方式不会一开始就把所有数据放进 Redis,而是用户访问到哪个 key,就把哪个 key 放进去。它适合数据量比较大、热点数据会自然出现的场景。

经过一段时间的动态平衡,Redis 中存储的数据会逐渐成为热点数据。随着时间推移,数据越写越多,可能逐渐达到 Redis 的内存上限。旧的热点 key 访问变少,新的热点 key 又无法插入,此时就需要引入内存淘汰策略

注意事项

内存淘汰策略

如果 Redis 内存达到上限,就会触发淘汰策略。这里不一定是物理内存,也可能是 Redis 配置中规定的最大内存容量。

常见策略有以下几种:

  • FIFO(First In First Out):先进先出,删除缓存中存在时间最久的 key。
  • LRU(Least Recently Used):淘汰最久未使用的 key,根据最近一次访问时间选择。
  • LFU(Least Frequently Used):淘汰访问次数最少的 key,根据最近一段时间的访问频率选择。
  • Random:随机淘汰 key。

缓存预热

Redis 服务器首次接入时没有任何数据,所有请求都会先访问 MySQL,再逐渐写入 Redis,数据库的压力只会慢慢下降。

缓存预热就是为了解决这个问题:它将定期生成和实时生成结合,通过离线统计先找出一批热点数据,预先导入 Redis,提前帮数据库承担一部分访问压力。随着后续访问变化,再逐渐使用新的热点数据淘汰旧数据。

缓存穿透(Cache Penetration)

请求查询一个不存在的数据,Redis 没有,MySQL 也没有。每次请求都会打到 MySQL,缓存没有起到保护作用。

可能原因:

  • 业务设计不合理,缺少必要的参数校验。
  • 开发或运维误操作,将部分数据从数据库中删除。
  • 恶意攻击。

常见处理:

  • 缓存空值:MySQL 查询不到时,把空结果也写入 Redis,并设置较短的过期时间。
  • 布隆过滤器:先判断 key 是否可能存在,不可能存在就直接拦截。
  • 参数校验:明显非法的 id、页码、业务参数,不进入缓存和数据库查询流程。

缓存空值时要注意过期时间不能太长,否则真实数据后来写入 MySQL 后,Redis 里仍然保留空结果,用户会继续查询不到。

缓存雪崩(Cache Avalanche)

短时间内大量缓存 key 同时失效,或者 Redis 整体不可用,导致缓存命中率大幅下降。请求集中访问 MySQL,数据库压力迅速上升,甚至直接宕机。

典型场景:

  • Redis 突然不可用,或者集群模式下大量节点宕机。
  • 某个时间点大量 key 同时过期。

处理思路:

  • 给过期时间增加随机值,避免大量 key 同时失效。
  • 对特别核心的数据做缓存预热。
  • 对数据库访问进行限流,避免请求把 MySQL 打满。

缓存雪崩是“大面积缓存同时失效”的问题。沿着这个问题继续看,还会出现一个更具体的场景:不是很多 key 同时失效,而是针对热点 key 失效,大量请求都冲向同一份数据,这就是缓存击穿。

缓存击穿(Cache Breakdown)

缓存击穿可以看作缓存雪崩的特殊情况。热点 key 突然失效时,大量并发请求同时发现 Redis 未命中,随后集中访问 MySQL。

它和缓存雪崩的区别:

  • 缓存雪崩:大量 key 同时失效,影响面大。
  • 缓存击穿:一个热点 key 失效,但访问量非常大。

常见处理:

  • 热点 key 设置更长的过期时间,或者使用逻辑过期控制刷新。
  • 进行服务降级。
  • 访问数据库时使用分布式锁,限制查询数据库的并发数。