3. Redis 过期策略与淘汰机制

在 Redis 缓存索引设计中,内存是有限且昂贵的资源。当键设置了过期时间,或内存达到上限时,系统必须决定如何回收空间。本章深入剖析 Redis 的两种核心回收机制:过期策略(针对已过期键)与淘汰机制(针对内存压力),并对比 LRU、LFU、TTL 等淘汰算法的适用场景。

3.1 过期策略:三种删除方式

Redis 对设置了 TTL(Time To Live)的键,采用三种策略配合完成过期删除,目标是平衡 CPU 开销与内存浪费。

策略名称 触发时机 优点 缺点 Redis 默认行为
定时删除 创建键时启动定时器,到期立即删除 内存友好,过期即释放 CPU 高,大量定时器占用资源 ❌ 未启用
惰性删除 每次访问键时检查是否过期 CPU 开销极低 过期键可能长期占用内存 ✅ 默认启用
定期删除 Redis 主循环周期性采样检查 CPU 与内存的折中方案 参数调优复杂 ✅ 默认启用
实践提示: 惰性删除 + 定期删除的组合是 Redis 默认配置。定期删除每秒执行 10 次(hz=10),每次随机检查 20 个键,若过期比例超过 25% 则重复执行。这种设计避免了定时删除的 CPU 峰值,同时缓解了惰性删除的内存泄漏风险。

3.2 淘汰机制:六种淘汰策略

当 Redis 内存达到 maxmemory 限制时,淘汰机制被触发。根据业务场景选择正确的淘汰策略,是缓存架构设计的关键决策。

策略 作用范围 淘汰依据 适用场景
noeviction 全部键 不淘汰,直接返回错误 数据库模式,禁止丢失数据
allkeys-lru 全部键 最近最少使用 通用缓存,访问模式较均匀
allkeys-lfu 全部键 最不经常使用 热点集中,冷热分明
allkeys-random 全部键 随机淘汰 访问均匀,无热点
volatile-lru 仅设置了 TTL 的键 最近最少使用 有时效性的缓存
volatile-lfu 仅设置了 TTL 的键 最不经常使用 有时效性 + 热点集中
volatile-ttl 仅设置了 TTL 的键 剩余 TTL 最短 优先淘汰即将过期的键
volatile-random 仅设置了 TTL 的键 随机淘汰 有时效性 + 无热点
领域边界警示: 在 DDD 聚合根缓存设计中,不要使用 allkeys-lru 淘汰聚合根对象。聚合根是领域一致性的边界,部分淘汰可能导致聚合内数据不完整。推荐对聚合根使用 volatile-lru 并设置合理的 TTL,或直接使用 noeviction 配合显式删除。

3.3 LRU 与 LFU 的近似实现

Redis 并未实现精确的 LRU/LFU,而是采用近似算法以节省内存。理解其实现差异,有助于在缓存索引设计中做出正确选择。

  • 近似 LRU:维护一个采样池(默认 5 个键),从池中淘汰最久未使用的键。采样数越大(maxmemory-samples),结果越接近精确 LRU,但 CPU 开销也越高。
  • 近似 LFU:使用莫里斯计数器(Morris Counter)记录访问频率,计数器随时间衰减。LFU 包含两个核心参数:
    • lfu-log-factor:控制计数器增长速率,默认 10。
    • lfu-decay-time:控制计数器衰减速率,单位分钟,默认 1。
# 配置示例:切换到 allkeys-lfu 并调整采样精度
maxmemory-policy allkeys-lfu
maxmemory-samples 10
lfu-log-factor 10
lfu-decay-time 1
性能对比: 在相同内存压力下,LRU 的淘汰决策速度比 LFU 快约 15%-20%,但 LFU 在热点数据识别上更准确。对于缓存索引场景,若索引访问模式存在明显的"冷热交替",优先选择 LFU;若访问模式相对稳定,LRU 更高效。

3.4 过期与淘汰的协同流程

下图展示了 Redis 在键访问和内存检查时,过期策略与淘汰机制的完整协同路径。

Redis 过期与淘汰协同流程图 Redis 过期策略与淘汰机制协同流程 客户端请求键 惰性删除检查 键是否过期? 删除键,返回 nil 返回键值给客户端 内存是否超过 maxmemory? 触发淘汰策略 LRU / LFU / TTL / Random 根据配置策略淘汰一个或多个键 请求结束

3.5 淘汰策略选择决策树

在缓存索引的领域设计中,选择淘汰策略应遵循以下决策逻辑:

  1. 是否允许数据丢失? 否 → noeviction;是 → 进入下一步。
  2. 所有键是否都有 TTL? 是 → 优先使用 volatile-* 系列;否 → 使用 allkeys-* 系列。
  3. 访问模式是否存在明显热点? 是 → 选择 LFU;否 → 选择 LRU。
  4. 是否需要淘汰即将过期的键? 是 → 选择 volatile-ttl。
领域驱动设计建议: 在缓存索引的限界上下文中,建议为不同的聚合根类型配置独立的 Redis 实例或逻辑数据库,并分别设置淘汰策略。例如:用户会话缓存使用 volatile-ttl,商品详情缓存使用 allkeys-lfu,配置信息缓存使用 noeviction。这种隔离避免了单一策略无法满足多领域需求的问题。

3.6 监控与调优

通过以下命令和指标,可以评估当前淘汰策略是否合理:

# 查看当前淘汰策略
CONFIG GET maxmemory-policy

# 查看内存使用与淘汰统计
INFO memory
# 关注指标:
# evicted_keys:被淘汰的键数量(持续增长说明内存不足)
# expired_keys:过期的键数量

# 查看键的 TTL 分布
redis-cli --bigkeys

# 调整采样精度(影响 LRU/LFU 准确性)
CONFIG SET maxmemory-samples 10
监控指标 正常范围 异常信号 建议操作
evicted_keys / sec < 100 > 1000 增加 maxmemory 或优化缓存命中率
expired_keys / sec < 500 > 5000 检查 TTL 设置是否过短
keyspace_hits / misses 命中率 > 90% 命中率 < 80% 检查淘汰策略是否误淘汰热点键
常见陷阱: 不要在生产环境中频繁切换淘汰策略。切换策略会清空所有键的访问历史(LRU/LFU 状态),导致短时间内大量键被误淘汰。如需调整,应在业务低峰期操作,并预热缓存。