Redis 的过期删除和内存淘汰机制是什么?
简化版
Redis 过期删除主要靠惰性删除和主动过期检查:访问 key 时发现过期就删,同时后台周期性抽样删除过期 key。内存淘汰发生在达到 maxmemory 后,Redis 按配置策略淘汰 key,比如 LRU、LFU、随机、TTL 优先或不淘汰。
详细版
过期删除和内存淘汰是两件事:
- 过期删除:key 设置了 TTL,到期后应该被删除。
- 内存淘汰:内存达到上限,为了给新写入腾空间而驱逐一部分 key。
过期删除方式:
- 惰性删除:访问 key 时检查 TTL,过期就删除。
- 主动删除:Redis 后台周期性抽样检查带过期时间的 key,删除已经过期的部分。
常见淘汰策略:
noeviction:不淘汰,写入报错;allkeys-lru/volatile-lru:按近似 LRU 淘汰;allkeys-lfu/volatile-lfu:按近似 LFU 淘汰;allkeys-random/volatile-random:随机淘汰;volatile-ttl:优先淘汰剩余 TTL 更短的 key。
完整版教学
一、过期不代表立刻物理删除
给 key 设置 TTL 后,到了过期时间,这个 key 在逻辑上已经不可用,但 Redis 不一定在那一刻立即把它从内存里删除。
原因很简单:如果 Redis 为每个 key 都设置一个定时器,到点立刻删除,key 很多时定时器成本会很高。所以 Redis 用惰性删除加主动抽样删除来平衡性能和内存释放。
比如 1000 万个 key 都设置了 TTL,如果 Redis 为每个 key 精确定时,到期瞬间就要维护海量定时事件,主线程会承受很高调度成本。Redis 的选择是把过期判断延后到访问时,以及后台周期抽样清理。这样牺牲的是“物理删除的及时性”,换来运行时开销可控。
TTL 到期:
逻辑上:key 已过期,不应再返回给用户
物理上:可能还占内存,等待访问触发或后台抽样清理
二、惰性删除解决访问正确性
惰性删除发生在访问 key 时。Redis 检查这个 key 是否过期,如果过期,就删除并返回不存在。
这保证了用户不会读到已经过期的数据。缺点是如果某些过期 key 再也没人访问,它们可能继续占用内存一段时间。
惰性删除很适合保证语义正确:只要你访问过期 key,Redis 会发现它已过期并当作不存在处理。问题是冷 key 如果过期后永远不再被访问,就不会被惰性删除触发,只能等主动过期检查或内存淘汰处理。因此只靠惰性删除不能保证内存及时回收。
三、主动删除负责后台清理
Redis 会周期性从设置了过期时间的 key 中抽样检查,发现过期就删除。它不是每轮扫描全库,因为全量扫描会阻塞主线程并带来巨大开销。
抽样机制意味着过期 key 的清理是渐进的。业务如果短时间写入大量带 TTL 的 key,到期后可能出现内存释放不够及时的现象。
例如业务批量写入 500 万个 1 小时 TTL 的验证码 key,如果它们在同一时间过期,主动删除会逐步抽样清理,而不是瞬间释放所有内存。此时可能看到内存下降有延迟,甚至在 maxmemory 压力下触发淘汰策略。TTL 加随机值不只防缓存雪崩,也能让过期清理压力更平滑。
| 删除方式 | 触发时机 | 优点 | 局限 |
|---|---|---|---|
| 惰性删除 | 访问 key 时 | 保证读不到过期值 | 冷 key 可能滞留 |
| 主动删除 | 后台周期抽样 | 渐进释放内存 | 不是全量即时清理 |
| 内存淘汰 | 达到 maxmemory | 保护写入空间 | 可能淘汰未过期 key |
记忆钩子:过期删除管“到期了怎么清”,内存淘汰管“内存满了怎么腾”,这两套机制不要混。
四、内存淘汰是 maxmemory 下的保护机制
当 Redis 使用内存达到 maxmemory,如果还要写入新数据,就需要根据策略决定是否淘汰旧 key。
这里要分清两组策略:
allkeys-*:所有 key 都可能被淘汰;volatile-*:只在设置了过期时间的 key 中淘汰。
如果你把 Redis 当纯缓存,常用 allkeys-lru 或 allkeys-lfu;如果有些 key 不能被淘汰,就要谨慎设置策略,甚至将缓存和关键数据拆到不同实例。
volatile-* 只从设置了过期时间的 key 中淘汰,这意味着如果大量 key 没有 TTL,而策略是 volatile-lru,Redis 可能找不到合适淘汰对象,写入仍会失败。纯缓存实例更常见 allkeys-lru 或 allkeys-lfu;既有缓存又有重要状态的实例,要么分实例隔离,要么非常谨慎地配置淘汰策略。
五、LRU 和 LFU 都是近似实现
Redis 的 LRU/LFU 不是精确维护一个全局链表,那样成本太高。它采用抽样和近似统计,在性能和效果之间取平衡。
这意味着淘汰结果不是绝对精确的“最久未使用”或“最低频使用”,但对缓存场景通常足够好。
精确 LRU 需要每次访问都移动节点并维护全局链表,在 Redis 高吞吐场景下成本太高。Redis 采用采样方式,从一小批候选 key 中挑更符合策略的淘汰。采样大小越大,结果越接近精确,成本也更高;这就是缓存系统常见的工程折中。
六、生产配置要先区分实例用途
如果 Redis 存的是可重建缓存,淘汰是正常保护机制;如果 Redis 存的是分布式锁、会话、重要状态,随意淘汰可能造成业务错误。最稳妥的方式是按用途拆实例:缓存实例允许淘汰,关键状态实例尽量不淘汰并设置容量告警。还要避免所有 key 没有 TTL,否则缓存会逐步变成“内存数据库”,最终只能靠 maxmemory 硬淘汰。
缓存实例:allkeys-lru/lfu + TTL + 容量监控
状态实例:谨慎 noeviction + 持久化/高可用 + 告警
七、常见误区与追问
- 误区:key 到期后一定马上从内存消失。 Redis 过期 key 逻辑不可读,但物理删除依赖惰性删除和主动抽样,可能延迟释放。
- 误区:过期删除和内存淘汰是一回事。 过期删除处理 TTL 到期,内存淘汰处理 maxmemory 满后的空间腾挪。
- 误区:
volatile-lru会淘汰所有最近少用的 key。 volatile 策略只在设置了 TTL 的 key 中淘汰,没 TTL 的 key 不在候选范围。 - 误区:LRU/LFU 是绝对精确的全局算法。 Redis 为了性能采用近似采样,不保证每次都淘汰全局最优 key。
- 追问:为什么 TTL 要加随机值? 可以避免大量 key 同时过期造成回源洪峰,也能平滑主动删除压力。
- 追问:缓存和关键状态能混一个实例吗? 不建议,缓存淘汰可能影响关键状态,最好按数据重要性和淘汰策略拆分实例。
八、加强记忆
Redis 过期删除和内存淘汰是两件事:过期删除处理 TTL 到期,惰性删除保证访问时不返回过期值,主动抽样负责后台渐进清理;内存淘汰处理 maxmemory 满后如何腾空间,按 LRU、LFU、随机、TTL 等策略选 key。缓存实例可以使用 allkeys-lru 或 allkeys-lfu,关键状态不要和可淘汰缓存混放。TTL 随机化、容量监控和实例隔离,是这题落到生产的关键。