redis 从0到1完整学习 (十六):内存回收之 key 过期处理策略

2024-01-09 15:52:21


1. 引言

前情提要:
《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》
《redis 从0到1完整学习 (六):Hash 表数据结构》
《redis 从0到1完整学习 (七):ZipList 数据结构》
《redis 从0到1完整学习 (八):QuickList 数据结构》
《redis 从0到1完整学习 (九):SkipList 数据结构》
《redis 从0到1完整学习 (十):RedisObject 数据结构》
《redis 从0到1完整学习 (十一):RedisObject 之 String 类型》
《redis 从0到1完整学习 (十二):RedisObject 之 List 类型》
《redis 从0到1完整学习 (十三):RedisObject 之 Set 类型》
《redis 从0到1完整学习 (十四):RedisObject 之 ZSet 类型》
《redis 从0到1完整学习 (十五):RedisObject 之 Hash 类型》

之前我们介绍了很多 redis 的 value 类型,包含 String、Set、Hash、List 等等,本文主要介绍 redis 的 key 过期处理策略,包含惰性清理、定期清理。

2. redis 源码下载

Redis 源码可以点击这里下载,方便查看其中定义的一些数据结构。
在这里插入图片描述

3. redisDb 结构体

Redis 数据库在 Redis 内部实现上是通过 redisDb 结构体来表示的。redisDb 结构体包含了特定数据库实例的所有信息,包括其键值对、过期时间、以及其它与该数据库相关的属性和功能。

源码结构体如下:
在这里插入图片描述

redisDb 结构体详细介绍:

typedef struct redisDb {
    dict *dict; // 一个字典,存储数据库中的所有键值对,键为 SDS 字符串,值为 redisObject 指针
    dict *expires; // 一个字典,存储了具有过期时间的键及其对应的 UNIX 时间戳
    dict *blocking_keys; // (如果支持事务阻塞的话)记录了正处于阻塞状态的键
    dict *watched_keys; // (如果支持 WATCH 命令的话)记录了被客户端监视的键
    int id; // 数据库编号,默认情况下 Redis 有 16 个数据库,编号从 0 到 15
    long long avg_ttl; // 平均剩余生存时间(TTL),用于统计分析和优化定期删除策略
    /* 其他相关字段 */
} redisDb;

具体字段可能会随着 Redis 不同版本而有所增减或调整。每个 redisDb 结构体代表 Redis 中的一个逻辑数据库,并且这些数据库都存放在全局的 redisServer 结构体的 db 数组中。用户可以通过 SELECT 命令切换到不同的数据库进行操作。

4. Redis 过期 key 的处理策略

Redis 源码中对于过期 key 的处理主要包括两种策略:惰性删除(Lazy Expiration)和定期删除(Active Expire)。

4.1 惰性删除 (Lazy Expiration)

当客户端尝试访问一个已过期的 key 时,Redis 会在返回给客户端 key 值之前检查该 key 是否已过期。如果发现 key 已过期,则会立即从数据库中删除该 key,并返回相应的响应(如 nil 表示不存在 key)。这种方式资源消耗少,但会导致过期 key 在被访问前一直占用内存空间。

robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
	   // 检查key是否过期,过期如果是惰性删除策略,则删除
	    expireIfNeeded(db,key);
	    return lookupKey(db,key,flags);
}

int expireIfNeeded(redisDb *db, robj *key) {
	// 没有过期,返回
    if (!keyIsExpired(db,key)) return 0;
	
	...
	// 过期了,删除
    int retval = server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
	...                                  	
}

4.2 定期删除 (Active Expire / Periodic Expiration)*

Redis 通过后台线程周期性地从数据库中随机抽取一定数量的 key 进行检查,以主动清理过期 key。这个过程由 redis.c 文件中的 expireIfNeeded() 函数在每次访问 key 时触发,以及由 db.c 文件中的 activeExpireCycle() 函数按照配置的周期来执行。Redis 使用一个名为“过期字典”(expired dict)的数据结构存储了所有设置了过期时间的 key 和它们对应的过期时间戳。

具体实现如下:

  1. Redis 定义了一个名为 activeExpireCycle 的函数,该函数会周期性地被调用,通常是由 Redis 主进程中的定时任务或者事件驱动触发。

  2. activeExpireCycle 函数会选择一定数量的数据库进行遍历,并对每个数据库中的一部分 key 进行随机抽样检查其过期时间。

  3. 检查过程中,Redis 会根据当前时间和 key 的过期时间戳判断 key 是否已过期。如果发现某个 key 已经过期,则立即将其从数据库中删除。

  4. Redis 会动态调整这个清理过程的速度和范围,以尽量保证不过于频繁地消耗 CPU 资源,同时又能及时清理掉大量过期的 key,避免内存资源浪费。执行周期有两种模式:

    • SLOW 模式规则:
      • 执行频率受 server.hz 影响,默认为10,即每秒执行10次,每个执行周期100ms。
      • 执行清理耗时不超过一次执行周期的25%。默认 slow 模式耗时不超过25ms
      • 逐个遍历 db,逐个遍历 db 中的 bucket,抽取20个 key 判断是否过期
      • 如果没达到时间上限(25ms)并且过期 key 比例大于10%,再进行一次抽样,否则结束
    • FAST 模式规则(过期 key 比例小于10%不执行 ):
      • 执行频率受 beforeSleep() 调用频率影响,但两次FAST模式间隔不低于2ms
      • 执行清理耗时不超过1ms
      • 逐个遍历 db,逐个遍历 db中的 bucket,抽取20个 key 判断是否过期
        如果没达到时间上限(1ms)并且过期 key 比例大于10%,再进行一次抽样,否则结束
  5. 在实际操作中,Redis 也会根据服务器的负载情况、已使用内存与 maxmemory 设置等因素灵活调整清理策略,确保系统性能和资源的有效利用。

总的来看,定期删除是一种主动但又较为温和的过期 key 清理策略,它配合惰性删除共同维护了 Redis 内存的高效管理。
这两种机制结合使用可以确保 Redis 在不过度消耗 CPU 资源的情况下,有效地管理过期 key,从而避免内存浪费。同时,Redis 也会尽可能保证过期数据不会长时间不被清理。

5. 参考

《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》
《redis 从0到1完整学习 (六):Hash 表数据结构》
《redis 从0到1完整学习 (七):ZipList 数据结构》
《redis 从0到1完整学习 (八):QuickList 数据结构》
《redis 从0到1完整学习 (九):SkipList 数据结构》
《redis 从0到1完整学习 (十):RedisObject 数据结构》
《redis 从0到1完整学习 (十一):RedisObject 之 String 类型》
《redis 从0到1完整学习 (十二):RedisObject 之 List 类型》
《redis 从0到1完整学习 (十三):RedisObject 之 Set 类型》
《redis 从0到1完整学习 (十四):RedisObject 之 ZSet 类型》
《redis 从0到1完整学习 (十五):RedisObject 之 Hash 类型》

欢迎关注本人,我是喜欢搞事的程序猿; 一起进步,一起学习;

也欢迎关注我的wx公众号:一个比特定乾坤

文章来源:https://blog.csdn.net/qq_36803941/article/details/135479787
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。