4. Redis 过期策略与内存淘汰:定期删除、惰性删除、LRU/LFU/TTL 淘汰算法

Redis 作为内存型数据库,其内存资源是有限且昂贵的。当键设置了过期时间,或者内存使用达到上限时,Redis 必须通过一套精密的策略来回收空间。本章深入剖析 Redis 的两大核心回收机制:过期键删除策略(处理已到期的键)与内存淘汰策略(处理内存超限时的键驱逐),并对比不同算法的适用场景。

4.1 过期键删除策略:定时 vs 惰性 vs 定期

Redis 对设置了 EXPIRE / TTL 的键,采用 定期删除 + 惰性删除 的组合策略,而非实时扫描所有键。

策略 原理 优点 缺点 Redis 是否采用
定时删除 为每个过期键创建定时器,到期立即删除 内存释放及时,无过期键残留 CPU 开销极大,高并发下定时器管理成为瓶颈 ❌ 不采用
惰性删除 每次访问键时检查是否过期,过期则删除 CPU 友好,只处理被访问的键 过期键可能长期占用内存(若一直不被访问) ✅ 组合使用
定期删除 每隔一段时间(默认 100ms)随机抽样检查部分键,删除过期键 平衡 CPU 与内存,避免长时间堆积 抽样频率和数量需要调优,存在删除滞后 ✅ 组合使用
实践要点: 定期删除是 Redis 主动回收过期键的主要手段。它通过 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 参数控制每次抽样数量(默认 20),并限制删除操作耗时不超过 CPU 时间的 25%。若过期键比例超过 25%,会立即重复循环,防止堆积。
Redis 过期键删除策略组合流程 客户端请求 惰性删除检查 返回数据 / nil 后台定期删除任务 随机抽样 20 个键 删除过期键 说明: 惰性删除保证每次访问时过期键不被返回;定期删除兜底,防止大量过期键长期占用内存。两者互补,兼顾 CPU 与内存。

4.2 内存淘汰策略:当内存达到上限时

当 Redis 使用的内存达到 maxmemory 配置上限时,写入操作会触发内存淘汰。Redis 8 种淘汰策略可分为三大类:

类别 策略名称 算法 适用场景
不淘汰 noeviction 直接返回错误,禁止写入 纯缓存不可丢失的场景(配合告警)
针对全体键 allkeys-lru LRU:淘汰最近最少使用的键 业务无明确冷热区分,通用缓存
allkeys-lfu LFU:淘汰访问频率最低的键 访问模式存在明显热点倾斜
allkeys-random 随机淘汰 数据访问完全均匀,极少使用
针对设置了 TTL 的键 volatile-lru LRU:淘汰设置了过期时间中最近最少使用的键 区分持久键与缓存键,只淘汰缓存
volatile-lfu LFU:淘汰设置了过期时间中访问频率最低的键 缓存键存在热点倾斜
volatile-random 随机淘汰设置了过期时间的键 极少使用
volatile-ttl TTL:淘汰剩余存活时间最短的键 优先保留即将过期的键(实际效果接近 LRU)
关键区别: volatile-* 系列策略只会在设置了过期时间的键中进行淘汰。如果所有键都没有设置 TTL,则 volatile-* 策略退化为 noeviction,导致写入失败。生产环境建议使用 allkeys-lru 或 allkeys-lfu,避免因未设置过期时间导致意外拒绝写入。

4.3 LRU 与 LFU 算法实现对比

Redis 的 LRU 和 LFU 并非精确实现,而是采用 近似算法 以节省内存:

  • 近似 LRU:维护一个全局的 eviction pool(淘汰池,默认大小 16)。每次淘汰时,随机抽样 maxmemory-samples(默认 5)个键,将其中最久未访问的键放入池中,最终从池中选择最优淘汰键。这种方式避免了维护全局双向链表的高昂内存开销。
  • 近似 LFU:使用 Morris Counter(概率计数器)记录访问频率。计数器以对数概率递增,高频键的计数器增长变慢,且计数器会随时间衰减(通过 lfu-decay-time 配置,默认 1 分钟衰减一次),从而适应访问模式的变化。
特性 近似 LRU 近似 LFU
淘汰依据 最近一次访问时间 历史访问频率 + 时间衰减
内存开销 每个对象 24 bit(用于存储 lru 时间戳) 每个对象 24 bit(16 bit 计数器 + 8 bit 衰减因子)
应对突发流量 一次突发访问可让键长期不被淘汰 突发访问后计数器快速衰减,更公平
适用场景 访问模式相对稳定,无剧烈热点变化 存在明显热点倾斜,且热点会随时间漂移
配置参数 maxmemory-samples(抽样数) lfu-log-factor(计数器增长因子)
lfu-decay-time(衰减分钟数)
LRU 与 LFU 淘汰决策流程对比 近似 LRU 淘汰流程 内存达到 maxmemory 上限 随机抽样 maxmemory-samples 个键 更新淘汰池,保留最久未访问的键 淘汰池中最优键 近似 LFU 淘汰流程 内存达到 maxmemory 上限 随机抽样 maxmemory-samples 个键 比较 Morris Counter 频率值 淘汰频率最低的键

4.4 配置与监控实践

在生产环境中,合理配置淘汰策略和监控指标至关重要:

推荐配置模板(4GB 实例,通用缓存场景):
# 内存上限
maxmemory 4gb

# 淘汰策略:优先使用 allkeys-lru,若热点倾斜明显改用 allkeys-lfu
maxmemory-policy allkeys-lru

# 抽样数:增大可提高淘汰精度,但增加 CPU 开销
maxmemory-samples 10

# LFU 参数(仅当使用 LFU 时)
lfu-log-factor 10
lfu-decay-time 1

监控命令:

  • INFO memory:查看 used_memory、maxmemory、evicted_keys(累计淘汰键数)。
  • INFO stats:查看 expired_keys(累计过期键数)。
  • MEMORY USAGE <key>:估算单个键的内存占用。
  • OBJECT idletime <key>:查看键的空闲时间(LRU 相关)。
  • OBJECT freq <key>:查看键的逻辑访问频率(LFU 相关,需开启 LFU 策略)。
避坑指南:
  1. 不要使用 volatile-* 策略且未设置 TTL:会导致所有键无法淘汰,写入失败。
  2. 避免频繁全量扫描:KEYS * 或 SCAN 不会触发惰性删除,但会消耗大量 CPU。
  3. LFU 计数器溢出:lfu-log-factor 过小会导致高频键计数器快速饱和,失去区分度。
  4. 淘汰抖动:maxmemory-samples 过小(如 1)会导致淘汰近似随机,建议至少 5。

4.5 本章小结

  • Redis 过期键删除采用 定期删除 + 惰性删除 组合,兼顾 CPU 与内存。
  • 内存淘汰策略分为 不淘汰、全体键淘汰、仅淘汰 TTL 键 三大类,推荐 allkeys-lru 或 allkeys-lfu。
  • LRU 关注 最近访问时间,LFU 关注 历史访问频率,两者均为近似算法以降低内存开销。
  • 合理配置 maxmemory-samples、lfu-log-factor 等参数,并通过 INFO 命令持续监控淘汰与过期情况。