Redis内存回收:过期策略与淘汰策略

发布于:2025-09-14 ⋅ 阅读:(26) ⋅ 点赞:(0)

Redis 数据库结构

Redis 作为典型的键值内存存储数据库,所有的键值对(key - value)都存储在之前学习过的 Dict 结构里。在其database结构体(redisDb)中,包含多个重要的 Dict 以及其他属性:

  • dict *dict:用于存放所有的 key 及对应的 value,这部分也被称为 keyspace
  • dict *expires:专门存放每一个设置了 TTL(存活时间)的 key 及其对应的 TTL 存活时间
  • dict *blocking_keys:记录有客户端在等待数据(比如通过 BLPOP 命令)的 key。
  • dict *ready_keys:存储接收到 PUSH 操作的被阻塞的 key。
  • dict *watched_keys:为 MULTI/EXEC(事务)以及 CAS(比较并交换)操作所监控的 key。
  • int id:数据库的 ID,范围是 0 到 15。
  • long long avg_ttl:用于记录平均 TTL 时长。
  • unsigned long expires_cursor:在进行 expire 检查时,在 dict 中抽样的索引位置。
  • list *defrag_later:等待进行碎片整理的 key 列表。

过期策略

惰性删除

并非在 TTL(存活时间)到期后就立即删除 key,而是在访问一个 key 的时候,检查该 key 的存活时间,如果已经过期才执行删除操作

具体执行

在查找一个 key 执行写操作(lookupKeyWriteWithFlags 函数)和读操作(lookupKeyReadWithFlags 函数)时,都会调用 expireIfNeeded 函数来检查 key 是否过期expireIfNeeded 函数的逻辑是:先判断 key 是否过期,如果未过期直接结束并返回 0;如果过期,则删除过期的 key 并进行相关传播操作,然后返回 1。

周期删除

通过一个定时任务,周期性地抽样部分过期的 key,然后执行删除操作。执行周期有两种。

执行周期

  • Redis 服务初始化函数 initServer() 中设置定时任务:按照 server.hz 的频率来执行过期 key 清理,模式为 SLOW。
  • Redis 的每个事件循环前调用 beforeSleep() 函数:执行过期 key 清理,模式为 FAST。

具体执行

  • 在 initServer 函数中,会创建定时器,关联回调函数 serverCron,处理周期取决于 server.hz(默认 10)。
  • serverCron 函数会更新 lruclock 到当前时间,为后期的 LRU(最近最少使用)和 LFU(最不经常使用)淘汰策略做准备,还会执行数据库的数据清理,比如过期 key 处理(调用 databasesCron 函数)。
  • databasesCron 函数会尝试清理部分过期 key,清理模式默认为 SLOW(调用 activeExpireCycle 函数并传入 ACTIVE_EXPIRE_CYCLE_SLOW)。
  • 在 beforeSleep 函数中,尝试清理部分过期 key,清理模式默认为 FAST(调用 activeExpireCycle 函数并传入 ACTIVE_EXPIRE_CYCLE_FAST)。

SLOW 模式

  • 执行频率受 server.hz 影响,默认 server.hz 为 10,即每秒执行 10 次,每个执行周期 100ms。
  • 执行清理耗时不超过一次执行周期的 25%,默认 slow 模式耗时不超过 25ms。
  • 逐个遍历数据库(db),逐个遍历 db 中的桶(bucket),抽取 20 个 key 判断是否过期。
  • 如果没达到时间上限(25ms)并且过期 key 比例大于 10%,再进行一次抽样,否则结束。

FAST 模式

  • 执行频率受 beforeSleep() 调用频率影响,但两次 FAST 模式间隔不低于 2ms
  • 执行清理耗时不超过 1ms。
  • 逐个遍历 db,逐个遍历 db 中的 bucket,抽取 20 个 key 判断是否过期。
  • 如果没达到时间上限(1ms)并且过期 key 比例大于 10%,再进行一次抽样,否则结束。

淘汰策略

当Redis内存使用达到设置的阈值时,Redis主动挑选部分Key删除

会在处理客户端命令的方法processCommand()中尝试做内存淘汰

支持8种策略来选择要删除的key:

  • noeviction:不淘汰任何 key,但是内存满时不允许写入新数据,默认就是这种策略。
  • volatile-ttl:对设置了 TTL 的 key,比较 key 的剩余 TTL 值,TTL 越小越先被淘汰
  • allkeys-random:对全体 key,随机进行淘汰。也就是直接从 db->dict 中随机挑选
  • volatile-random:对设置了 TTL 的 key,随机进行淘汰。也就是从 db->expires 中随机挑选。
  • allkeys-lru:对全体 key,基于 LRU 算法进行淘汰(最少未使用,它认为在最近一段时间内使用频率低或者没有被使用的 key 是最有可能不再被使用的。)
  • volatile-lru:对设置了 TTL 的 key,基于 LRU 算法进行淘汰
  • allkeys-lfu:对全体 key,基于 LFU 算法进行淘汰(最不经常使用,将访问次数最少的 key 视为最不经常被使用的,从而优先淘汰。)
  • volatile-lfu:对设置了 TTL 的 key,基于 LFI 算法进行淘汰

淘汰流程

Redis 内存淘汰流程如下:

  1. 首先判断内存是否充足,若充足则流程结束。
  2. 若内存不充足,判断内存策略是否为NO_EVICTION,若是则流程结束。
  3. 若不是NO_EVICTION,判断是否为AllKeys模式:
    • 若是,从db->entriesdb->dict中淘汰。
    • 若不是,进入后续内存策略判断。
  4. 判断内存策略:
    • 若为LRULFUTTL策略:
      • 准备一个eviction_pool
      • 获取下一个 DB,随机挑选maxmemory_samples数量的 key。
      • 根据内存策略,分别用maxTTL - TTLTTL策略)、nowTime - LRU值LRU策略)、255 - LFU计数LFU策略)作为idleTime
      • 判断是否可以存入eviction_pool,若可以则按idleTime升序存入。
      • 若有下一个 DB,重复上述步骤;若没有,倒序从eviction_pool中获取一个 key 删除。
    • 若为RANDOM策略:遍历 DB,随机挑选一个 key 删除。
  5. 删除 key 后,判断已释放内存是否满足需要释放的内存,若满足则流程结束;若不满足,重复上述淘汰步骤。