3. Redis 过期策略:定时删除、惰性删除、定期删除、内存淘汰机制

Redis 作为内存数据库,其所有数据都存储在内存中。为了在有限的内存资源下高效运行,Redis 提供了两套清理机制:过期删除策略(针对设置了 TTL 的 Key)和内存淘汰机制(针对内存使用达到上限时的全局清理)。理解这两套机制是构建缓存一致性方案的基础。

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

当 Key 设置了过期时间(EXPIRE、SETEX 等命令),Redis 需要在过期后将其删除。Redis 并未采用单一的删除方式,而是结合了以下三种策略。

策略名称 触发时机 CPU 开销 内存开销 主要风险
定时删除 Key 过期时立即触发 高(频繁创建定时器) 低(及时释放) CPU 压力大,影响吞吐量
惰性删除 访问 Key 时检查是否过期 低(仅访问时检查) 高(过期 Key 堆积) 内存浪费,可能产生大量过期 Key
定期删除 Redis 主循环周期性执行 中等(可配置频率) 中等(分批清理) 清理不及时,需配合惰性删除

3.1.1 定时删除(Timed Deletion)

Redis 为每个设置了过期时间的 Key 创建一个定时器。当 Key 到期时,定时器立即触发删除操作。这是最“主动”的方式,能保证过期 Key 被立即清理,内存释放最及时。但 Redis 是单线程模型,大量定时器会严重抢占 CPU 资源,降低服务吞吐量。因此,Redis 并未默认采用纯定时删除,而是将其作为辅助手段。

3.1.2 惰性删除(Lazy Deletion)

这是 Redis 默认且最重要的过期删除策略。当客户端请求访问某个 Key 时,Redis 会先检查该 Key 是否已过期。如果过期,则立即删除该 Key,并返回 nil(或空结果)。这种方式对 CPU 最友好,因为只在实际访问时做检查。但缺点是:如果过期 Key 长时间不被访问,它们会一直占用内存,形成“内存泄漏”。

实战案例: 假设缓存中存储了 100 万个 5 分钟过期的 Session Key。如果用户不再活跃,这些 Key 将永远不会被访问。惰性删除无法清理它们,导致内存持续增长。此时必须依赖定期删除或内存淘汰机制。

3.1.3 定期删除(Periodic Deletion)

为了解决惰性删除的内存堆积问题,Redis 引入了定期删除。Redis 每隔 100ms(可配置)会随机抽取一部分设置了过期时间的 Key,检查并删除其中已过期的 Key。这是一个折中方案:

  • 频率控制: 每次运行时间不超过 25ms,避免阻塞主线程。
  • 自适应调整: 如果每次抽样的过期比例超过 25%,则继续执行删除,直到比例低于 25% 或超时。
  • 随机抽样: 从 expires 字典中随机抽取 20 个 Key 进行检查。

最佳实践: 定期删除 + 惰性删除是 Redis 默认的组合策略。定期删除负责主动清理,惰性删除负责兜底。对于大多数业务场景,这种组合已经足够。如果内存压力极大,则需要配合内存淘汰机制。

3.2 内存淘汰机制(Eviction Policy)

当 Redis 使用的内存达到 maxmemory 配置的上限时,Redis 会根据配置的淘汰策略,主动删除一些 Key 来释放内存。这并非针对过期 Key,而是针对所有 Key(包括未设置 TTL 的)。

Redis 8 种淘汰策略可分为三类:

类别 策略名称 说明 适用场景
不淘汰 noeviction 不淘汰任何 Key,写入操作直接返回错误 纯读缓存或不允许丢失数据的场景
allkeys-lru 从所有 Key 中淘汰最近最少使用的 通用缓存,对访问模式无特殊要求
针对所有 Key allkeys-lfu 从所有 Key 中淘汰最不经常使用的 热点数据访问频率差异极大的场景
allkeys-random 从所有 Key 中随机淘汰 数据访问均匀分布,无热点
volatile-lru 从设置了 TTL 的 Key 中淘汰最近最少使用的 缓存数据都有过期时间,且希望优先淘汰冷数据
仅针对设置了 TTL 的 Key volatile-lfu 从设置了 TTL 的 Key 中淘汰最不经常使用的 带过期时间的缓存,且访问频率差异大
volatile-random 从设置了 TTL 的 Key 中随机淘汰 带过期时间的缓存,数据均匀访问
volatile-ttl 从设置了 TTL 的 Key 中淘汰即将过期的(剩余 TTL 最短) 优先淘汰即将过期的数据,减少无效缓存

重要提示: 如果使用 volatile-* 系列策略,但所有 Key 都未设置 TTL,则 Redis 会退化为 noeviction 行为,导致写入失败。因此,使用 volatile-* 策略时,务必确保缓存数据都设置了合理的过期时间。

3.3 过期策略与淘汰机制的关系

下图展示了 Redis 在收到写入请求时,如何协同使用过期删除和内存淘汰机制:

Redis 写入流程:过期删除 + 内存淘汰协同 收到写入请求 检查当前内存是否超过 maxmemory 是否超过? 是 触发淘汰策略 否 正常写入 定期删除(后台异步执行)

3.4 实战配置建议

在构建缓存一致性方案时,推荐以下配置组合:

# 设置最大内存,建议为物理内存的 60%-80%
maxmemory 4gb

# 淘汰策略:优先使用 allkeys-lru,保证热点数据留存
maxmemory-policy allkeys-lru

# 定期删除频率(hz 越大,CPU 消耗越高,清理越及时)
hz 10

# 惰性删除默认开启,无需额外配置
# 定期删除默认开启,无需额外配置

选型建议:

  • 通用缓存场景: allkeys-lru 是最安全的选择,能自动保留热点数据。
  • 带 TTL 的 Session 缓存: volatile-ttl 或 volatile-lru,优先淘汰即将过期的数据。
  • 数据不可丢失场景: noeviction,配合监控告警,防止内存写满。

3.5 总结:过期与淘汰的闭环

Redis 的过期删除和内存淘汰机制共同构成了内存管理的闭环:

  • 主动层: 定期删除(定时扫描过期 Key)
  • 被动层: 惰性删除(访问时检查过期)
  • 兜底层: 内存淘汰(内存超限时全局清理)