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的地方,这里我们以最简单的stringget命令为例:

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是如何实现惰性淘汰删除机制,其他的数据结构,例如HashListSetZset也是如此,这里就不一样贴出源码进行举例说明了,感兴趣的读者可以翻阅redis源码。

这里我们用一张string get命令的时序图,总结一下get命令的执行流程:

img

定时扫描删除机制

上面部分我们了解了惰性淘汰删除机制,但是仅仅靠客户端访问来判断 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的淘汰策略解读,我们下次再聊。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/211688.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

敏捷:应对软件定义汽车时代的开发模式变革

随着软件定义汽车典型应用场景的落地&#xff0c;汽车从交通工具转向智能移动终端的趋势愈发明显。几十年前&#xff0c;一台好车的定义主要取决于高性能的底盘操稳与动力系统&#xff1b;几年前&#xff0c;一台好车的定义主要取决于智能化系统与智能交互能否满足终端用户的用…

LLM;超越记忆《第 2 部分 》

一、说明 在这篇博客中&#xff0c;我深入研究了将大型语言模型&#xff08;LLM&#xff09;提升到基本记忆之上的数学框架。我们探索了动态上下文学习、连续空间插值及其生成能力&#xff0c;揭示了 LLM 如何理解、适应和创新超越传统机器学习模型。 LLM代表了人工智能的重大飞…

Linux常用指令详解

目录 前言&#xff1a; Linux的目录结构 Linux常用指令简介 whoami指令 ls指令 pwd指令 cd指令 tree指令 touch指令 mkdir指令 rmdir指令与rm指令 man指令 cp&#xff08;copy&#xff09;指令 mv&#xff08;move&#xff09;指令 cat指令 重定向及重定向的类型…

Redis缓存问题

在实际的业务场景中&#xff0c;Redis 一般和其他数据库搭配使用&#xff0c;用来减轻后端数据库的压力&#xff0c;比如和关系型数据库 MySQL 配合使用。 Redis 会把 MySQL 中经常被查询的数据缓存起来&#xff0c;比如热点数据&#xff0c;这样当用户来访问的时候&#xff0c…

12月8日Workshop预告|理解Sui上对象所有权转移

在本workshop中&#xff0c;我们将介绍一些Sui上对象所有权转移&#xff08;transfer-to-object&#xff09;功能的示例&#xff0c;并讨论这个新功能在构建Sui上新的抽象化概念时的用途。 我们将通过几个示例来演示transfer-to-object功能。此外&#xff0c;我们将介绍如何使…

时间序列预测 — GRU实现多变量多步光伏预测(Tensorflow)

目录 1 数据处理 1.1 数据集简介 1.2 导入库文件 1.3 数据集处理 1.4 训练数据构造 2 模型训练与预测 2.1 模型训练 2.2 模型多步预测 2.3 预测可视化 1 数据处理 1.1 数据集简介 实验数据集采用数据集7&#xff1a;常州普利司通光伏数据集&#xff08;下载链接&…

动态规划学习——最长回文子序列,让字符串变成回文串的最小插入次数

一&#xff0c;最长回文串 1.题目 给你一个字符串 s &#xff0c;找出其中最长的回文子序列&#xff0c;并返回该序列的长度。 子序列定义为&#xff1a;不改变剩余字符顺序的情况下&#xff0c;删除某些字符或者不删除任何字符形成的一个序列。 示例 1&#xff1a; 输入&…

搞程序权益系统v1.1

继1.0出来后我就把antdui换成elem 新增号卡功能现在只支持对接号氪系统 大家问我这个程序到底有什么用&#xff0c;我这边已经在写和WordPress对接文件&#xff0c;到时候在WordPress网站打开该程序就可以把订单同步到你的程序里面去&#xff0c;当然自己有集成能力也可以到小…

R语言学习

Part1阶段1&#xff1a;入门基础 1安装R和RStudio&#xff1a; 下载并安装R&#xff1a;https://cran.r-project.org/ 下载并安装RStudio&#xff1a;https://www.rstudio.com/products/rstudio/download/ 2Hello World&#xff1a; 学习如何在R中输出"Hello, World!"…

【智能家居】一、工厂模式实现继电器灯控制

用户手册对应的I/O 工厂模式实现继电器灯控制 代码段 controlDevice.h&#xff08;设备设备&#xff09;main.c&#xff08;主函数&#xff09;bathroomLight.c&#xff08;浴室灯&#xff09;bedroomLight.c&#xff08;卧室灯&#xff09;restaurantLight.c&#xff08;餐厅…

mac电池最大充电限制工具 AlDente Pro中文 for Mac

Pro 版特有功能 热保护&#xff1a;在电池温度较高时为电池充电会导致电池老化更快。启用热保护后&#xff0c;当电池温度过高时&#xff0c;充电将自动停止。 航行模式&#xff1a;通常情况下&#xff0c;即使激活了最大电池充电&#xff0c;您的 MacBooks 电池也会始终稍微充…

Guava中的函数式编程

第1章&#xff1a;引言 大家好&#xff01;今天小黑要和咱们聊聊&#xff0c;在Java中使用Guava来进行函数式编程。首先&#xff0c;让我们来聊聊什么是函数式编程。简单来说&#xff0c;函数式编程是一种编程范式&#xff0c;它将计算视为函数的评估&#xff0c;避免使用程序…

ChatGPT能帮助--掌握各种AI绘图工具,随意生成各类型性图像

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

西南科技大学C++程序设计实验六( 继承与派生一)

一、实验目的 1. 理解不同继承属性对派生类访问基类成员的区别 2. 掌握单继承程序编写 二、实验任务 1、调试下列程序,并在对程序进行修改后再调试,指出调试中的出错原因(该题中A为基类,B为派生类,B以public方式继承A) 重点:理解不同继承方式数据的访问权限,派生类…

【力扣】——可获得的最大点数(滑动窗口)

几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#xff0c;最终你必须正好拿 k 张卡牌。 你的点数就是你拿到手中的所有卡牌的点数之和。 给你一个整数数组 cardPoi…

HarmonyOS4.0从零开始的开发教程04 初识ArkTS开发语言(下)

HarmonyOS&#xff08;二&#xff09; 初识ArkTS开发语言&#xff08;下&#xff09;之TypeScript入门 声明式UI基本概念 应用界面是由一个个页面组成&#xff0c;ArkTS是由ArkUI框架提供&#xff0c;用于以声明式开发范式开发界面的语言。 声明式UI构建页面的过程&#xff…

gitLab 和Idea分支合并

以下二选1即可完成分支合并建议第一种简单有效 Idea合并方式 切换到被合并的分支&#xff0c;如我想把0701的内容合并到dev&#xff0c;切换到dev分支&#xff0c;然后再点击merge然后选择要合并的分支&#xff0c;即可,此时git上的代码没有更新只是把代码合到本地需要pull才…

Kafka使用指南

Kafka简介架构设计Kafka的架构设计关键概念Kafka的架构设计关键机制 Partition介绍Partition工作机制 应用场景ACK机制介绍ACK机制原理ACK机制对性能的影响ACK控制粒度Kafka分区数对集群性能影响调整分区优化集群性能拓展Kafka数据全局有序 Kafka简介 Kafka是由Apache软件基金…

【数据结构】动态规划(Dynamic Programming)

一.动态规划&#xff08;DP&#xff09;的定义&#xff1a; 求解决策过程&#xff08;decision process&#xff09;最优化的数学方法。 将多阶段决策过程转化为一系列单阶段问题&#xff0c;利用各阶段之间的关系&#xff0c;逐个求解。 二.动态规划的基本思想&#xff1a; …

【CentOS8】使用 Tomcat 部署 Java Web 项目(使用 sdkman)

文章目录 配置 Tomcat将 Tomcat 启动命令设置为 Linux 自定义服务给 Tomcat 设置管理员账号密码IDEA 打包 Java web 项目 我是使用 sdkman 下载的 jdk 和 tomcat&#xff0c;所以接下来的部署配置都是在 sdkman 构建的环境的。想要知道如何下载 sdkman 可以看看这篇文章 —…