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

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 后,判断已释放内存是否满足需要释放的内存,若满足则流程结束;若不满足,重复上述淘汰步骤。
相关推荐
素玥4 分钟前
实训5 python连接mysql数据库
数据库·python·mysql
jnrjian11 分钟前
text index 查看index column index定义 index 刷新频率 index视图
数据库·oracle
瀚高PG实验室29 分钟前
审计策略修改
网络·数据库·瀚高数据库
言慢行善1 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
韶博雅1 小时前
emcc24ai
开发语言·数据库·python
有想法的py工程师1 小时前
PostgreSQL 分区表排序优化:Append Sort 优化为 Merge Append
大数据·数据库·postgresql
迷枫7122 小时前
达梦数据库的体系架构
数据库·oracle·架构
夜晚打字声2 小时前
9(九)Jmeter如何连接数据库
数据库·jmeter·oracle
Chasing__Dreams2 小时前
Mysql--基础知识点--95--为什么避免使用长事务
数据库·mysql
风吹迎面入袖凉2 小时前
【Redis】Redis的五种核心数据类型详解
java·redis