Redis 缓存机制原理:从数据结构到淘汰策略
很多人对 Redis 缓存的理解停留在"设个过期时间的键值对",但真正决定线上系统稳定性的,是它背后一整套过期删除、内存淘汰、读写更新和一致性保障的机制。这篇文章从原理层面梳理 Redis 缓存是怎么运转的。
1. 数据是怎么"过期"的:惰性删除 + 定期删除
Redis 并不会在 EXPIRE 时间一到就立刻把 key 删掉,而是维护一张独立的 expires 字典,专门记录哪些 key 设置了过期时间以及具体的过期时间戳。真正的删除动作由两种机制配合完成。
只靠惰性删除会导致一批设了过期时间但再也不会被访问的 key 一直占着内存不释放;只靠定期删除又可能因为抽样和 CPU 时间片限制而不够及时。两者搭配,再加上内存淘汰机制兜底,才构成 Redis 完整的过期回收体系。
DEL 命令同步过来——这是为了保证主从数据的一致性。2. 内存不够用时怎么办:淘汰策略
当 Redis 内存占用达到 maxmemory 上限时,新的写入会触发淘汰机制,具体行为由 maxmemory-policy 参数决定,一共有 8 种策略可选。
| 策略 | 作用范围 | 淘汰依据 |
|---|---|---|
| noeviction | 不淘汰 | 内存满后写入直接报错 |
| allkeys-lru | 全体 key | 最近最少使用 |
| volatile-lru | 设置了过期时间的 key | 最近最少使用 |
| allkeys-lfu | 全体 key | 访问频率最低 |
| volatile-lfu | 设置了过期时间的 key | 访问频率最低 |
| allkeys-random | 全体 key | 随机 |
| volatile-random | 设置了过期时间的 key | 随机 |
| volatile-ttl | 设置了过期时间的 key | 剩余存活时间最短 |
生产环境最常用的是 volatile-lru 或 allkeys-lru:前者只淘汰设置了过期时间的 key,保证那些永久性的重要数据不会被误删;后者则把 Redis 完全当缓存用,所有 key 都参与淘汰竞争。选择哪种取决于 Redis 实例里是否混用了缓存数据和持久化业务数据。
3. 缓存更新的四种模式
业务代码里到底该先更新数据库还是先操作缓存、该主动写缓存还是被动加载,这几种常见模式各有取舍。
Cache-Aside 是绝大多数业务系统的默认选择:应用代码自己负责查缓存、查数据库、写缓存,逻辑简单,出问题排查也直观。Read/Write-Through 把缓存和存储的读写都封装在缓存层背后,应用只跟缓存打交道,一致性由缓存组件保证,但需要额外的中间层支持。Write-Behind 追求极致的写入性能,先写缓存立即返回,再异步批量刷库,吞吐量最高但有丢数据的风险,通常用在日志、计数这类可以容忍少量丢失的场景。
4. 缓存更新顺序:为什么"先删缓存"更安全
Cache-Aside 模式下,更新数据时是"先更新数据库、再删除缓存"还是"先删除缓存、再更新数据库"?这两种顺序都存在并发问题,业界的共识是先更新数据库,再删除缓存,并在必要时配合延迟双删来兜底。
如果反过来"先删缓存、再更新数据库",在删除缓存和更新数据库之间的窗口期,一旦有读请求进来,就会把数据库里的旧值重新加载回缓存,而且这个旧值会一直留在缓存里直到下一次过期或更新,影响时间比前一种方案更长。所以工程实践中更推荐"先库后缓存"的顺序。对一致性要求更高的场景,还可以通过订阅数据库的 binlog(如借助 Canal)来异步同步缓存,把"删缓存"这个动作从业务代码里解耦出来。
5. 三大经典问题:穿透、击穿、雪崩
这三个问题的本质都是"缓存没能挡住请求",但触发原因和应对方式不同。
6. 热点 key:单个 key 也能压垮整个集群
即使做了分片,某个爆款商品或热搜话题对应的 key 也可能被极高频访问,请求全部落在同一个分片节点上,造成该节点 CPU 或带宽打满,而其他节点却很空闲。常见应对思路是识别出热点 key 后,把它复制成多份(如 hotkey:1、hotkey:2 ... 加随机后缀),读请求随机打到某一份上分摊压力;也可以在应用层加一层进程内本地缓存(如 Caffeine、ristretto),对访问频率极高的数据做二级兜底,减少打到 Redis 的次数。
小结
Redis 缓存机制的核心可以归纳为四层:用主哈希表和过期字典配合惰性删除、定期删除来管理数据的生命周期;用近似 LRU/LFU 算法在内存达到上限时决定淘汰谁;用 Cache-Aside 等模式约定应用代码和缓存、数据库之间的读写顺序;再用穿透、击穿、雪崩的专项应对方案和热点 key 的分摊手段,补齐缓存机制在极端场景下的短板。理解这套原理,才能在设计缓存方案时提前预判风险,而不是等线上出问题了再去救火。


