Redis 过期删除策略

在介绍 Redis 的过期删除机制之前,需要先说明:
定时删除、惰性删除、定期删除是对过期数据清理思路的抽象划分,用于分析不同策略在 CPU 开销与内存占用之间的取舍。实际的高性能缓存系统通常不会采用其中某一种,而是进行组合设计。

常见的过期删除策略

1. 定时删除

定时删除指的是:
当键到达过期时间的瞬间立即删除该键。

其实现通常需要:

  • 为每一个设置了过期时间的 key 维护独立的定时事件(如定时器或最小堆)
  • 到期后触发删除回调

优点:

  • 过期键能够被及时清理
  • 内存利用率高

缺点:

  • 定时事件数量与过期 key 数量近似线性相关
  • 当过期 key 较多时,定时回调会占用大量 CPU
  • 严重影响主线程吞吐量

因此,Redis 并未采用这种高精度但高成本的策略。

2. 惰性删除

惰性删除的核心思想是:
不主动删除过期 key,仅在访问时检查其是否过期。

执行流程:

  • 客户端访问 key
  • Redis 判断 key 是否已过期
  • 若已过期,立即删除并返回 key 不存在

优点:

  • 对 CPU 非常友好
  • 不做无意义的主动扫描

缺点:

  • 如果某些 key 长期不被访问,即使已经过期,也会一直占用内存
  • 单独使用会造成内存浪费

3. 定期删除(主动过期)

定期删除是 Redis 的后台主动清理机制:

  • Redis 后台周期性执行过期扫描
  • 随机抽样一部分设置了过期时间的 key
  • 删除其中已经过期的 key
  • 执行时间受 hz 配置与时间片限制

需要注意:

  • 并非全量扫描
  • 并非严格“到点即删”
  • 本质是一种近似清理机制

Redis 的过期删除策略总结

Redis 并未采用定时删除,而是通过以下组合方式清理过期 key:

惰性删除 + 定期删除(主动过期)

该组合策略在保证访问语义正确性的前提下:

  • 避免定时删除带来的高 CPU 开销
  • 防止惰性删除导致的大量过期 key 长期占用内存

Redis 的过期删除机制几乎不可精调,只能通过 hzactive-expire-effort 间接控制后台主动过期的执行频率和强度;惰性删除行为本身不可配置。

Redis 缓存淘汰策略

缓存淘汰策略与过期删除策略的目标和触发条件不同,二者不能混淆。

1
2
3
4
5
6
7
# 所有实践省流如下:
CONFIG GET maxmemory-policy # 1. 命令行查看当前缓存淘汰策略
CONFIG SET maxmemory-policy <policy> # 2. 修改运行时缓存淘汰策略
maxmemory-policy <policy> # 3. 修改配置文件,使得缓存淘汰策略重启后仍生效
CONFIG GET maxmemory-samples # 4. 查看当前 LRU / LFU 采样数量
CONFIG SET maxmemory-samples <N> # 5. 修改运行时采样数量(立即生效,重启失效)
maxmemory-samples <N> # 6. 修改配置文件,使采样数量重启后仍生效

与过期删除策略的区别

1. 作用范围不同

  • 过期删除策略:
    仅作用于设置了过期时间且已经过期的 key
  • 缓存淘汰策略:
    作用于尚未过期但需要被移除的 key
    可仅限于设置了过期时间的 key,或作用于所有 key

已经过期的 key 不会进入缓存淘汰流程,而是直接被删除。

2. 触发时机不同

  • 过期删除策略:
    • 访问 key 时触发(惰性删除)
    • 后台周期性触发(定期删除)
  • 缓存淘汰策略:
    • 仅在配置了 maxmemory
    • 且内存使用达到上限时触发

Redis 支持的内存淘汰策略

在 64 位系统上,Redis 默认不限制内存(maxmemory=0),因此不会触发内存淘汰机制。
只有在显式设置 maxmemory 后,Redis 才可能在内存达到上限时,根据配置的淘汰策略执行缓存淘汰。

Redis 提供以下内存淘汰策略:

  • noeviction(默认)
    不进行内存淘汰,写命令在内存不足时直接返回错误,读操作不受影响
  • volatile-random
    从设置了过期时间的 key 中随机淘汰
  • volatile-ttl
    优先淘汰剩余 TTL 较小的 key(近似实现)
  • volatile-lru
    从设置了过期时间的 key 中,淘汰最久未使用的 key(近似 LRU)
  • volatile-lfu
    从设置了过期时间的 key 中,淘汰使用频率最低的 key(近似 LFU)
  • allkeys-random
    从所有 key 中随机淘汰
  • allkeys-lru
    从所有 key 中淘汰最久未使用的 key(近似 LRU)
  • allkeys-lfu
    从所有 key 中淘汰使用频率最低的 key(近似 LFU)

查看与修改内存淘汰策略

查看当前淘汰策略:

1
CONFIG GET maxmemory-policy

修改方式:

  1. 运行时修改(立即生效,重启失效):
1
CONFIG SET maxmemory-policy <policy>
  1. 修改配置文件(重启后生效):
1
maxmemory-policy <policy>

运行时配置用于即时调整行为,配置文件用于持久化配置,二者配合可保证实例行为一致。

Redis 中 LRU 的实现机制

传统 LRU 通常通过双向链表实现,能够精确淘汰最久未使用的元素,但需要额外维护链表结构,成本较高。

Redis 并未维护全局 LRU 链表,而是采用近似 LRU 算法

  • 每个 key 记录最近一次访问时间
  • 内存淘汰时,随机抽取 N 个 key
  • 从样本中淘汰访问时间最早的 key

采样数量由参数 maxmemory-samples 控制:

  • 样本数越大,淘汰结果越接近真实 LRU
  • 默认值为 5,可按需调整

Redis 中 LFU 的实现机制

LFU 用于淘汰使用频率最低的 key。

Redis 的 LFU 实现同样是近似算法:

  • 每个 key 维护访问频率计数
  • 计数并非简单递增,而是概率性增长
  • 同时引入时间衰减机制,避免历史热点永久占优
  • 内存淘汰时,采用“随机抽样 + 比较频率”的方式选择淘汰目标

总结

  • Redis 通过惰性删除 + 定期删除清理过期 key(过期删除策略)
  • 缓存淘汰仅在配置 maxmemory 且内存达到上限时触发、有八种(缓存淘汰策略)
  • 过期删除针对过期键、缓存淘汰针对未过期键但设置了过期时间的键+未设置过期时间的键
    • tips:如果缓存淘汰时采样到的数据中有已经过期的键呢?那么算是缓存淘汰吗?
    • 不是!会直接删除,不属于缓存淘汰的范围中。
  • LRU / LFU 在 Redis 中均为近似实现,可通过采样参数进行调优