3. Redis 过期策略与内存淘汰:TTL 设置、惰性删除、定期删除、LRU/LFU 淘汰机制对缓存的影响

在缓存闭环中,数据何时失效、内存何时释放,直接决定了缓存命中率与系统稳定性。Redis 采用“被动过期 + 主动清理 + 内存淘汰”三层防御体系。理解这三层的协作关系,是设计高可用缓存边界的关键。

3.1 TTL 设置:过期时间的源头

所有过期行为都始于 EXPIRE、PEXPIRE、EXPIREAT 或 SETEX 命令。Redis 在每个键的 redisObject 中记录 expire 字段(毫秒级时间戳)。

命令 精度 典型场景
EXPIRE key seconds 秒 通用缓存 TTL
PEXPIRE key ms 毫秒 高精度限流窗口
EXPIREAT key timestamp 秒时间戳 固定时刻失效(如每日 0 点)
SETEX key seconds value 秒 原子写入+过期
避坑提示: 对已有 TTL 的键再次执行 EXPIRE 会覆盖原过期时间。使用 TTL key 返回 -2 表示键不存在,-1 表示未设置过期。

3.2 惰性删除:被动防御

当客户端访问一个键时,Redis 会先检查该键是否已过期。如果过期,立即删除并返回 nil。这种策略称为 惰性删除。

  • 优点: CPU 开销极低,只在实际访问时检查。
  • 缺点: 过期键如果一直不被访问,会长期占用内存,形成“内存泄漏”。

惰性删除是 Redis 过期策略的第一道防线,但仅靠它远远不够。

3.3 定期删除:主动清理

为了解决惰性删除的内存残留问题,Redis 每隔 100ms 执行一次 定期删除 任务。其核心逻辑如下:

  1. 从 expires 字典中随机选取 20 个键。
  2. 删除其中已过期的键。
  3. 如果过期比例超过 25%,重复步骤 1-2。
  4. 每次执行总耗时不超过 25ms,避免阻塞主线程。
关键参数:
  • ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP:默认 20,每次采样数。
  • ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC:默认 25,CPU 时间占比。

定期删除是第二道防线,它保证了过期键不会无限堆积。

3.4 内存淘汰:最后一道防线

当内存使用达到 maxmemory 上限时,Redis 必须主动淘汰键来释放空间。淘汰策略通过 maxmemory-policy 配置。

策略 含义 适用场景
noeviction 不淘汰,写入直接报错 禁止丢失数据的场景
allkeys-lru 全键 LRU 淘汰 通用缓存,无优先级区分
volatile-lru 仅淘汰设置了 TTL 的键 缓存+持久化混合使用
allkeys-lfu 全键 LFU 淘汰 访问频率差异明显的场景
volatile-lfu 仅淘汰设置了 TTL 的键 热点数据保护
allkeys-random 全键随机淘汰 均匀分布,无热点
volatile-ttl 淘汰剩余 TTL 最短的键 优先清理即将过期的数据

3.5 LRU 与 LFU 淘汰机制对比

LRU(Least Recently Used)和 LFU(Least Frequently Used)是两种最常用的淘汰算法。Redis 对它们做了近似实现以节省内存。

LRU vs LFU 淘汰逻辑对比 LRU 淘汰 淘汰最近最少使用的键 基于访问时间排序 Redis 近似 LRU:采样 pool 淘汰 适合:周期性访问模式 LFU 淘汰 淘汰访问频率最低的键 基于访问次数 + 时间衰减 Redis 近似 LFU:Morris 计数器 适合:稳定热点数据 核心差异 • 内存开销:LRU 需记录 24bit 时间戳;LFU 需 16bit 计数器 + 衰减因子 • 扫描污染:LRU 一次全表扫描会提升所有键的“最近使用”状态 • 频率突增:LFU 对突发高频访问不敏感,需配合衰减周期 • 配置建议:缓存层用 allkeys-lru;热点隔离层用 volatile-lfu

3.6 三层防御协作流程

当一个键到达过期时间或被内存淘汰时,Redis 的完整处理流程如下:

Redis 过期与淘汰三层防御流程 客户端请求 惰性删除检查 已过期 未过期 删除键,返回 nil 返回数据 定期删除(后台 100ms) 内存淘汰(maxmemory 触发) 三层防御:惰性删除 → 定期删除 → 内存淘汰,逐级兜底,保证内存安全

3.7 对缓存闭环的影响

缓存雪崩风险: 大量键在同一时间过期,会导致缓存瞬间空洞,请求全部穿透到后端。解决方案:TTL 添加随机偏移(±10%~20%),或使用 EXPIREAT 分散过期时间。
  • 缓存击穿: 热点键过期瞬间,高并发请求直接打到数据库。使用互斥锁或逻辑过期(不设物理 TTL,由业务层控制)来防护。
  • 缓存穿透: 查询一个不存在的键,每次都会穿透。布隆过滤器 + 空值缓存(短 TTL)是标准解法。
  • 淘汰策略选择: 缓存层推荐 allkeys-lru;持久化层推荐 volatile-lru 或 noeviction;热点数据隔离层推荐 volatile-lfu。

3.8 实战配置建议

# redis.conf 关键配置
maxmemory 4gb
maxmemory-policy allkeys-lru
# 定期删除 CPU 占比
active-expire-effort 1   # 1-10,默认 1,越大越积极
# 惰性删除开关(默认开启)
lazyfree-lazy-expire yes
lazyfree-lazy-eviction yes
监控命令:
  • INFO stats 查看 expired_keys、evicted_keys 指标。
  • MEMORY STATS 查看内存分配与碎片率。
  • OBJECT idletime key 查看键的空闲时间(LRU 决策依据)。