文章目录
- 1. 引言
- 2. redis 源码下载
- 3. redisDb 结构体
- 4. Redis 过期 key 的处理策略
- 4.1 惰性删除 (Lazy Expiration)
- 4.2 定期删除 (Active Expire / Periodic Expiration)*
- 5. 参考
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 和它们对应的过期时间戳。
具体实现如下:
-
Redis 定义了一个名为
activeExpireCycle
的函数,该函数会周期性地被调用,通常是由 Redis 主进程中的定时任务或者事件驱动触发。 -
activeExpireCycle
函数会选择一定数量的数据库进行遍历,并对每个数据库中的一部分 key 进行随机抽样检查其过期时间。 -
检查过程中,Redis 会根据当前时间和 key 的过期时间戳判断 key 是否已过期。如果发现某个 key 已经过期,则立即将其从数据库中删除。
-
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%,再进行一次抽样,否则结束
- SLOW 模式规则:
-
在实际操作中,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公众号:一个比特定乾坤