Redis key过期删除机制实现分析
文章目录
- 前言
- Redis key过期淘汰机制
- 惰性删除机制
- 定时扫描删除机制
前言
当我们创建Redis key时,可以通过expire
命令指定key的过期时间(TTL),当超过指定的TTL时间后,key将会失效。
那么当key失效后,Redis会立刻将其删除么?如果不会,那么何时Redis才将其真正的删除呢?我们来一起一探究竟。
Redis key过期淘汰机制
Redis中的key过期淘汰机制是由两种方式实现:
- 惰性删除机制
- 定时扫描删除机制
两种模式都不会在key达到过期时间后,第一时间删除key,而是等待特定的时机触发淘汰机制,这个很好理解,如果每一个key到达过期时间后,redis都需要第一时间检测到,并将其删除,那么将会消耗大量的资源,去实时的扫描全部key值,这显然是不合理的。
下面我们来看一下两种方式的具体实现机制。
惰性删除机制
惰性删除很简单,就是当有客户端的请求查询该 key 的时候,检查下 key 是否过期,如果过期,则删除该 key。
在此种模式下,触发key淘汰的时机,是将删除过期数据的主动权交给了每次访问请求。
那么Redis具体是如何实现的,我们来一起看一下源码实现。
淘汰删除的具体实现,在db.c
的#expireIfNeeded()
:
int expireIfNeeded(redisDb *db, robj *key) {/* 通过调用getExpire函数获取key的过期时间。*/mstime_t when = getExpire(db,key);mstime_t now;/* 当过期时间小于0时,表示key没有设置过期时间,直接返回0 */if (when < 0) return 0; /* No expire for this key *//* 如果Redis正在进行数据加载,直接返回0,不进行后续的过期检查。 */if (server.loading) return 0;/* 获取当前时间,如果当前是在执行Lua脚本中,使用server.lua_time_start作为当前时间;否则,使用系统当前时间mstime作为当前时间 */now = server.lua_caller ? server.lua_time_start : mstime();/* 如果Redis是主从复制模式,并且当前节点是从节点,则直接返回当前时间是否大于过期时间,不进行后续的过期操作 */if (server.masterhost != NULL) return now > when;/* 如果当前时间小于等于过期时间,则直接返回0,表示key还没有过期 */if (now <= when) return 0;/* Delete the key *//* 增加已过期key的数量统计 */server.stat_expiredkeys++;/* 向从节点发送key过期的命令,保证从节点也能及时删除过期的key */propagateExpire(db,key);/* 向Redis的事件通知机制发送key过期的事件通知 */notifyKeyspaceEvent(REDIS_NOTIFY_EXPIRED,"expired",key,db->id);/* 删除已过期的key,并返回1表示删除成功 */return dbDelete(db,key);
}/* Delete a key, value, and associated expiration entry if any, from the DB */
int dbDelete(redisDb *db, robj *key) {/* Deleting an entry from the expires dict will not free the sds of* the key, because it is shared with the main dictionary. */if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);if (dictDelete(db->dict,key->ptr) == DICT_OK) {return 1;} else {return 0;}
}
上面的源码即Redis执行key淘汰删除的核心过程,具体操作可以参见注释,通过方法名字expireIfNeeded()
这是一个检查类型的方法,那么说明是在进行key操作时,会触发该方法进行检查key是否需要进行淘汰删除,那么其调用时机在何时呢?
在db.c
的#lookupKeyRead()与lookupKeyWrite()
:
robj *lookupKeyRead(redisDb *db, robj *key) {robj *val;expireIfNeeded(db,key);val = lookupKey(db,key);if (val == NULL)server.stat_keyspace_misses++;elseserver.stat_keyspace_hits++;return val;
}robj *lookupKeyWrite(redisDb *db, robj *key) {expireIfNeeded(db,key);return lookupKey(db,key);
}robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) {robj *o = lookupKeyRead(c->db, key);if (!o) addReply(c,reply);return o;
}robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) {robj *o = lookupKeyWrite(c->db, key);if (!o) addReply(c,reply);return o;
}
上面的代码是调用expireIfNeeded()
的上游function,通过名字可以看出,#lookupKeyRead()与lookupKeyWrite()
是读取和写入key的方法(不得不说,redis的代码命名非常的优秀,值得我们学习),那么调用该方法的一定就是执行获取key的地方,这里我们以最简单的string
的get
命令为例:
在t_string.c
的#getCommand()
:
/* string的get命令 */
void getCommand(redisClient *c) {getGenericCommand(c);
}int getGenericCommand(redisClient *c) {robj *o;/* 通过调用lookupKeyReadOrReply函数查找指定key的值,如果key不存在,则向客户端返回空值并返回REDIS_OK;如果查找到了key的值,则将值保存到变量o中,继续后续的操作 */if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)return REDIS_OK;/* 判断获取到的值的类型是否为字符串类型 *//* 如果值的类型不是字符串类型,向客户端返回错误响应,并返回REDIS_ERR表示获取失败 */if (o->type != REDIS_STRING) {addReply(c,shared.wrongtypeerr);return REDIS_ERR;} else {/* 如果值的类型是字符串类型,向客户端返回获取到的字符串值,并返回REDIS_OK表示获取成功 */addReplyBulk(c,o);return REDIS_OK;}
}
上述就是string get
命令的执行过程,我们可以清晰的看到,redis是如何实现惰性淘汰删除机制,其他的数据结构,例如Hash
、List
、Set
、Zset
也是如此,这里就不一样贴出源码进行举例说明了,感兴趣的读者可以翻阅redis源码。
这里我们用一张string get
命令的时序图,总结一下get
命令的执行流程:
定时扫描删除机制
上面部分我们了解了惰性淘汰删除机制,但是仅仅靠客户端访问来判断 key 是否过期才执行删除肯定不够,因为有的 key 过期了,但未来再也没人访问,那岂不是GG,这些数据要怎么删除呢?
Redis在后台,会启动一个定时任务,定期扫描数据库中的所有key,检查它们的过期时间是否已到期。但是这里需要注意,定时任务并不是一次运行就检查所有的库,所有的键,而是随机检查一定数量的键。
为什么是随机抽查,而不是全量从头到尾扫描一遍?
很好理解,如果redis中的key特别多,如果进行全量扫描,那对redis的性能会存在巨大的影响,如果有一个亿的key,每次定时任务执行都进行全量扫描,CPU岂不是爆炸。
上图的流程图,简单的描述了定时任务的执行逻辑(实际上会复杂很多),还是老规矩,不多逼逼,上源码,Redis具体是如何实现的,我们来一起看一下源码实现。
定时任务的实现在redis.c
的#activeExpireCycle()
:
void activeExpireCycle(int type) {....此处省略部分前置逻辑/* 循环redis全部的db */for (j = 0; j < dbs_per_call; j++) {....此处省略部分前置逻辑do {unsigned long num, slots;long long now, ttl_sum;int ttl_samples;/* 获取dict中设置了TTL的key集合中,计算集合的数量 */if ((num = dictSize(db->expires)) == 0) {db->avg_ttl = 0;break;}....此处省略部分前置逻辑/* 如果过期的key数量,超过了20,那么扫描数量设置为20 */if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP; /* ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP = 20 *//* 循环处理 */while (num--) {dictEntry *de;long long ttl;/* 获取dict中设置了TTL的key集合中,随机获取一个key */if ((de = dictGetRandomKey(db->expires)) == NULL) break;/* 计算TTL剩余时间 */ttl = dictGetSignedIntegerVal(de)-now;/* 如果当前的key已经过期,则执行删除操作,并将过期key的数量加1 */if (activeExpireCycleTryExpire(db,de,now)) expired++;if (ttl < 0) ttl = 0;ttl_sum += ttl;ttl_samples++;}....此处省略部分后置逻辑/* 如果过期的key数量,超过阈值的25%,继续循环,否则退出扫描 *//* 这也就意味着在任何时候,过期 key 的最大数量等于每秒最大写入操作量除以4 = 5*/ } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);}
}
上面的代码就是定时任务扫描过期key的执行流程,笔者删除了部分代码,仅保留的核心执行部分,方便读者阅读,核心执行逻辑可以参见注释部分
如果用一句话概括说明定时任务的流程,那么可以总结为:
定时任务循环扫描每个redis数据库,从设置了TTL的key的集合中,随机挑选N个key进行检查,如果过期,干掉,否则跳过,直到过期key的数量小于25%,退出扫描
以上,就是Redis删除过期key的两种实现方式,由于笔者对C的理解很有限,因此仅仅截取了部分源码进行解读,也可能有很多解读不对的地方,望读者见谅。
事实上,仅仅通过惰性删除+定时任务扫描,仍会可能存在很多“漏网之鱼”,毕竟定时任务删除,并非全量扫描,那么如果Redis的使用容量达到了最大内存,Redis会如何操作?
这就涉及到了Redis的key淘汰策略,本篇的内容就此为止,关于Redis的淘汰策略解读,我们下次再聊。
相关文章:

Redis key过期删除机制实现分析
文章目录 前言Redis key过期淘汰机制惰性删除机制定时扫描删除机制 前言 当我们创建Redis key时,可以通过expire命令指定key的过期时间(TTL),当超过指定的TTL时间后,key将会失效。 那么当key失效后,Redis会立刻将其删除么&#…...
ElasticSearch 谈谈分词与倒排索引的原理
ElasticSearch是一个基于Lucene的搜索服务器。Lucene是Java的一个全文检索工具包,而ElasticSearch则是一个分布式搜索和分析引擎。下面,我们将详细讨论ElasticSearch中的分词和倒排索引的原理。 分词: 在ElasticSearch中,分词是…...
【Java】Java8重要特性——Lambda函数式编程以及Stream流对集合数据的操作
【Java】Java8重要特性——Lambda函数式编程以及Stream流对集合数据的操作 前言Lambda函数式编程Stream流对集合数据操作(一)创建Stream流(二)中间操作之filter(三)中间操作之map(四)…...

大话数据结构-查找-散列表查找(哈希表)
注:本文同步发布于稀土掘金。 8 散列表查找(哈希表) 8.1 定义 散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给…...

持续集成交付CICD:Sonarqube自动更新项目质量配置
目录 一、实验 1.Sonarqube手动自定义质量规则并指定项目 2.Sonarqube自动更新项目质量配置 一、实验 1.Sonarqube手动自定义质量规则并指定项目 (1)自定义质量规则 ①新配置 ②更多激活规则③根据需求激活相应规则④已新增配置 ⑤ 查看 &#x…...
Linux设置Docker自动创建Nginx容器脚本
文章目录 前言一、本地新建脚本二、复制本地脚本到服务器三、执行服务器脚本总结如有启发,可点赞收藏哟~ 前言 一、本地新建脚本 在本地新建nginx-generator.sh脚本文件,并保存以下内容 主要动态定义两个变量(容器名称/服务器本地文件名、端…...

技术博客:Vue中各种混淆用法汇总
技术博客:Vue中各种混淆用法汇总 摘要 本文主要介绍了在Vue中使用的一些常见混淆用法,包括new Vue()、export default {}、createApp()、Vue.component、Vue3注册全局组件、Vue.use()等,以及如何使用混淆器对代码进行加固,保护应…...

【python】Python生成GIF动图,多张图片转动态图,pillow
pip install pillow 示例代码: from PIL import Image, ImageSequence# 图片文件名列表 image_files [car.png, detected_map.png, base64_image_out.png]# 打开图片 images [Image.open(filename) for filename in image_files]# 设置输出 GIF 文件名 output_g…...

python/matlab图像去雾/去雨综述
图像去雾和去雨是计算机视觉领域的两个重要任务,旨在提高图像质量和可视化效果。本文将综述图像去雾和去雨的算法、理论以及相关项目代码示例。 一、图像去雾算法 基于暗通道先验的方法: 这是广泛应用于图像去雾的经典算法之一。该方法基于一个观察&…...

Docker+jenkins+gitlab实现持续集成
1.安装环境 服务器ip虚拟机版本192.168.5.132centos7.6192.168.5.152centos7.6 2. 安装docker 安装必要的一些系统工具 yum install -y yum-utils device-mapper-persistent-data lvm2添加软件源信息,要确保centos7能上外网 yum-config-manager --add-repo http:…...

Web前端JS如何获取 Video/Audio 视音频声道(左右声道|多声道)、视音频轨道、音频流数据
写在前面: 根据Web项目开发需求,需要在H5页面中,通过点击视频列表页中的任意视频进入视频详情页,然后根据视频的链接地址,主要是 .mp4 文件格式,在进行播放时实时的显示该视频的音频轨道情况,并…...

MySQL生成UUID并去除-
uuid()函数 uuid() 函数可以使mysql生成uuid,但是uuid中存在-,如下图: 去除uuid的- 默认生成的uuid含有-,我们可以使用replace函数替换掉-,SQL如下 select replace(uuid(),"-","") as uuid;Insert语句中使用UUID 如果…...

包与字符串
包是分类管理的需要,建立包用:package,包中类的引用import 学习使用javaAPI中的字符串类String,学会其成员方法的使用 (必看)eclipse包的分层等级结构设置 因为eclipse的包的结构默认是平行等级的,所以要手…...

【Gradle】mac环境安装Gradle及配置
官网安装说明:Gradle | Installation 由于Gradle运行依赖jvm,所以事先需要安装jdk,并确认你的jdk版本和gradle版本要求的对应关系,这个官网上有说明,但是我试了一下不太准确,供参考,链接如下&a…...

使用C语言操作kafka ---- librdkafka
1 安装librdkafka git clone https://github.com/edenhill/librdkafka.git cd librdkafka git checkout v1.7.0 ./configure make sudo make install sudo ldconfig 在librdkafka的examples目录下会有示例程序。比如consumer的启动需要下列参数 ./consumer <broker> &…...

误用STM32串口发送标志位 “USART_FLAG_TXE” “USART_FLAG_TC”造成的BUG
当你使用串口发送数据时是否出现过这样的情况: 1.发送时第一个字节丢失。 2.发送时出现莫名的字节丢失。 3.各种情况字节丢失。 1.先了解一下串口发送的流程图(手动描绘): 可以假想USART_FLAG_TXE是用于检测"弹仓"&…...

指针(三)
函数指针 定义:整型指针是指向整形的指针,数组指针式指向数组的指针,其实函数指针就是指向函数的指针。 函数指针基础: ()优先级要高于*;一个变量除去了变量名,便是它的变量类型;一个指针变量…...
labelimg遇到的标签修改问题:修改一张图像的标签时,保存后导致classes.txt改变
问题描述:修改一张图像的标签时候, classes.txt 会同步更新,导致重新生成了 classes.txt 但是这个 classes.txt 只有你现在写的那个类别名,以前的没有了。 解决:设置一个 predefined_classes.txt,内容和模…...
Spring Cloud Gateway使用和配置
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关ÿ…...
RT-Thread 时钟管理
时钟管理 时钟是非常重要的概念,和朋友出去游玩需要约定时间,完成任务也需要花费时间,生活离不开时间。 操作系统也一样,需要通过时间来规范其任务的执行,操作系统中最小的时间单位是时钟节拍(OS Tick&…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...

NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...

【深度学习新浪潮】什么是credit assignment problem?
Credit Assignment Problem(信用分配问题) 是机器学习,尤其是强化学习(RL)中的核心挑战之一,指的是如何将最终的奖励或惩罚准确地分配给导致该结果的各个中间动作或决策。在序列决策任务中,智能体执行一系列动作后获得一个最终奖励,但每个动作对最终结果的贡献程度往往…...

C++_哈希表
本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...
WEB3全栈开发——面试专业技能点P7前端与链上集成
一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染(SSR)与静态网站生成(SSG) 框架,由 Vercel 开发。它简化了构建生产级 React 应用的过程,并内置了很多特性: ✅ 文件系…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...