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.mzph.cn/news/203073.shtml

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

相关文章

设置webstorm和idea符合Alibaba规范

只格式化自己更改的代码 ctrlShiftAltL 插件建议 Alibaba Java Coding Guidelines&#xff08;新版本的idea不支持&#xff0c;有其他同名的非官方版可代替&#xff09;&#xff0c;使用方法在此不赘述 1、设置webstorm 包含 设置两个空格缩进&#xff0c;去掉行尾分号&#…

HarmonyOS/OpenHarmony应用开发-Stage模型应用/组件级配置

在开发应用时&#xff0c;需要配置应用的一些标签&#xff0c;例如应用的包名、图标等标识特征的属性。本文描述了在开发应用需要配置的一些关键标签。图标和标签通常一起配置&#xff0c;可以分为应用图标、应用标签和入口图标、入口标签&#xff0c;分别对应app.json5配置文件…

Xcode15 Library ‘iconv.2.4.0‘ not found

Xcode 15运行老代码报错&#xff1a;Library iconv.2.4.0 not found 解决&#xff1a; TARGETS-->Bulid Phases --> Link Binary With Libraries 添加一个“Libiconv.tbd”, 同时把原来的 “libiconv.2.4.0.tbd”删除&#xff08;一定要删除&#xff0c;不然运行还是…

「X」Embedding in NLP|Token 和 N-Gram、Bag-of-Words 模型释义

ChatGPT&#xff08;GPT-3.5&#xff09;和其他大型语言模型&#xff08;Pi、Claude、Bard 等&#xff09;凭何火爆全球&#xff1f;这些语言模型的运作原理是什么&#xff1f;为什么它们在所训练的任务上表现如此出色&#xff1f; 虽然没有人可以给出完整的答案&#xff0c;但…

案例059:基于微信小程序的在线投稿系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

uniapp 设置内容超长时的省略样式

需求 在uniapp中&#xff0c;页面在展示搜索历史的时候&#xff0c;需要对内容过长的进行处理&#xff0c;也就是文本超出我的最大长度时&#xff0c;不允许换行&#xff0c;且末尾为省略 期望的效果如下 思路 使用 官网 text-overflow 可选值俩个 clip 修建文本ellipsi…

PyLMKit(6):大模型使用(API型和本地开源模型)

日期&#xff1a;2023-12-6 PyLMKit目前集成了LLM模型有两种类型&#xff1a; API付费调用型本地开源模型下载部署 1.API型LLM模型使用教程 1.1.申请 API KEY 根据你想使用的大模型的官网&#xff0c;注册账号&#xff0c;并申请API KEY&#xff0c;如果需要付费调用&…

PyTorch机器学习与深度学习实践技术应用

近年来&#xff0c;随着AlphaGo、无人驾驶汽车、医学影像智慧辅助诊疗、ImageNet竞赛等热点事件的发生&#xff0c;人工智能迎来了新一轮的发展浪潮。尤其是深度学习技术&#xff0c;在许多行业都取得了颠覆性的成果。另外&#xff0c;近年来&#xff0c;Pytorch深度学习框架受…

html通过CDN引入Vue组件抽出复用

html通过CDN引入Vue组件抽出复用 近期遇到个需求&#xff0c;就是需要在.net MVC的项目中&#xff0c;对已有的项目的首页进行优化&#xff0c;也就是写原生html和js。但是咱是一个写前端的&#xff0c;写html还可以&#xff0c;.net的话&#xff0c;开发也不方便&#xff0c;还…

idea开发环境配置

idea重新安装后&#xff0c;配置的东西还挺多的&#xff0c;这里简单记录一下。 1、基础配置 1.1、主题、背景、主题字体大小 1.2、默认字体设置 控制台默认编码设置&#xff1a; 全局文件默认编码设置&#xff1a; 2、构建、编译、部署配置 说明&#xff1a;本地装了JD…

谷歌刚刚发布了Gemini 1.0,采用了OpenAI的GPT4

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 对于谷歌和安卓来说&#xff0c;这是一个重要时刻。谷歌刚刚发布了 Gemini 1.0&#xff0c;这是其最新的LLM&#xff0c;它采用了 OpenAI 的 GPT4。 共有三种不同…

go语言 | etcd源码导读(一)

参考 本文参考https://zhuanlan.zhihu.com/p/600893553 https://www.topgoer.com/%E6%95%B0%E6%8D%AE%E5%BA%93%E6%93%8D%E4%BD%9C/go%E6%93%8D%E4%BD%9Cetcd/etcd%E4%BB%8B%E7%BB%8D.html 前沿etcd 与 raft etcd是使用Go语言开发的一个开源的、高可用的分布式key-value存储系…

Android--Jetpack--Databinding详解

不经一番寒彻骨&#xff0c;怎得梅花扑鼻香 一&#xff0c;定义 DataBinding, 又名数据绑定&#xff0c;是Android开发中非常重要的基础技术&#xff0c;它可以将UI组件和数据模型连接起来&#xff0c;使得在数据模型发生变化时&#xff0c;UI组件自动更新。是 MVVM 模式在 An…

Linux-centos上如何配置管理NFS服务器?

Linux/centos上如何配置管理NFS服务器&#xff1f; 1 NFS基础了解 NFS&#xff08;Network File System&#xff09;即文件操作系统&#xff1b;NFS允许网络中不同计算机相互之间共享资源。 1.1 NFS概述 1980年由SUN发展出来的在UNIX&Linux系统间实现文件共享的一种方法…

计算机组成原理-数据寻址-(相对寻址 基址寻址 变址寻址 )

文章目录 指令寻址vs数据寻址总览偏移寻址基址寻址基址寻址的作用变址寻址变址寻址的作用基址&变址复合寻址相对寻址相对寻址的作用 总结硬件如何实现数的比较 指令寻址vs数据寻址 总览 偏移寻址 变址寄存器&#xff1a;IX 基址寄存器&#xff1a;BR 基址寻址 没有基址…

GitHub工业级开源软件:基于网络的过程可视化(SCADA/HMI/仪表板)

GitHub工业级开源软件:基于网络的过程可视化(SCADA/HMI/仪表板)  作者:本站编辑  2023-11-25 06:52:35  117 大家好,我是 Fun-Fun君,每天介绍github上最有价值的开源项目 今天介绍 FUXA 基于网络的过程可视化(SCADA/HMI/仪表板)软件 github地址:…

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

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

Camunda 7.x 系列【60】流程分类

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 2.7.9 本系列Camunda 版本 7.19.0 源码地址:https://gitee.com/pearl-organization/camunda-study-demo 文章目录 1. 前言2. 案例演示2.1 后端2.2 前端2.3 测试1. 前言 钉钉中的OA审批分类: 企业级的业务…

UDP多人群聊

一.创建类 二.类 1.Liao类 import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.net.*; import java.io.IOException; import java.lang.String;public class Liao extends JFrame{private static final int DEFAULT_PORT8899;private JLabel st…

【musl-pwn】msul-pwn 刷题记录 -- musl libc 1.2.2

前言 本文不分析 musl libc 相关源码&#xff0c;仅仅为刷题记录&#xff0c;请读者自行学习相关知识&#xff08;看看源码就行了&#xff0c;代码量也不大&#xff09; starCTF2022_babynote 保护&#xff1a;保护全开 程序与漏洞分析&#xff1a; 程序实现了一个菜单堆&…