Redis key过期删除机制实现分析

news2024/11/30 12:34:30

文章目录

  • 前言
  • 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++;
    else
        server.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.coloradmin.cn/o/1290621.html

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

相关文章

k8s 安装 Longhorn

Longhorn 的 helm 模板官网地址&#xff1a;Longhorn 加入仓库 helm repo add longhorn https://charts.longhorn.iohelm repo update开始部署 helm install longhorn longhorn/longhorn --namespace longhorn-system --create-namespace --version 1.5.3检查pod运行状态是…

STM32——电动车报警器

项目设计 // 如果检测到 PA4 被拉低&#xff08;小偷偷车&#xff09;&#xff0c;并且警报模式打开 // 则将 PB7 拉低&#xff0c;继电器通电&#xff0c;喇叭一直响 // 如果检测到 PA5 被拉高&#xff08;按键 A 按下&#xff09;&#xff0c;设定为开启警报模式 // 则将…

0X05

打开题目 点击完登录和注册都没有什么反应&#xff0c;所以先扫一下看看 在出现admin.php后就截止了&#xff0c;访问看看,进入后台。。 尝试一下弱口令 admin/12345 或者是demo/demo 设计中-自定义->右上角导出主题 找到一个导出的点&#xff0c;下载了一个1.zip压缩包…

多传感器融合SLAM在自动驾驶方向的初步探索的记录

1. VIO的不可观问题 现有的VIO都是解决的六自由度的问题, 但是对于行驶在路面上的车来说, 通常情况下不会有roll与z方向的自由度, 而且车体模型限制了不可能有纯yaw的变换. 同时由于IMU在Z轴上与roll, pitch上激励不足, 会导致IMU在初始化过程中尺度不准以及重力方向估计错误,…

配置CentOS服务器以支持PHP

CentOS是一款优秀的开源服务器操作系统&#xff0c;为各种网络服务提供了强大的支持。为了使CentOS服务器能够支持PHP&#xff0c;我们需要进行一些必要的配置。下面将介绍配置CentOS服务器以支持PHP的关键步骤。 安装PHP 首先&#xff0c;需要安装PHP解释器。在CentOS上&…

设置webstorm和idea符合Alibaba规范

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

用Mnesia为cache增加分布式支持

一&#xff1a;分布式缓存 1.选取通信策略 在设计分布式程序时&#xff0c;可供选择的通信方式主要有两种&#xff1a;异步通信和同步通信。采用异步通信时&#xff0c;发送方无须等待任何确认或应答。而在采用同步通信时&#xff0c;发送方会处于挂起状态&#xff0c;直至收…

万宾科技智能水环境综合治理监测系统效果

水环境综合治理是一项旨在全面改善水环境质量的系统工程。它以水体为对象&#xff0c;综合考虑各种因素&#xff0c;通过科学规划和技术手段&#xff0c;解决水环境污染、生态退化等问题&#xff0c;核心理念是“统一规划、分步实施&#xff1b;标本兼治&#xff0c;重在治本&a…

用C语言实现链栈的基本操作

#include <stdio.h> #include <malloc.h> #define ElemType char//相当于ElemType等同于char类型 //链式结构 数据域指针域 typedef struct LinkStackNode//定义一个链栈的结构体类型 {ElemType data;//ElemType是链栈的元素类型&#xff0c;代表数据域struct Lin…

京东数据运营(京东API接口):10月投影仪店铺数据分析

鲸参谋监测的京东平台10月份投影仪市场销售数据已出炉&#xff01; 10月份&#xff0c;环同比来看&#xff0c;投影仪市场销售均上涨。鲸参谋数据显示&#xff0c;今年10月&#xff0c;京东平台投影仪的销量为16万&#xff0c;环比增长约22%&#xff0c;同比增长约8%&#xff1…

关于前端学习的思考-align-items的用法

垂直对齐只对&#xff0c;显示模式display&#xff1a;flex有效 所以只能是flex 盒子才能应用。 摆四张图片就能清晰认识&#xff0c;不再赘述。

2023 金砖国家职业技能大赛网络安全省赛二三阶段样题(金砖国家未来技能挑战赛)

2023 金砖国家职业技能大赛网络安全省赛二三阶段样题&#xff08;金砖国家未来技能挑战赛&#xff09; 第二阶段&#xff1a; 安全运营 **背景&#xff1a;**作为信息安全技术人员必须能够掌握操作系统加固与安全管控、防火 墙一般配置、常见服务配置等相关技能&#xff0c;利…

【Qt】在表格QTableWidget或者QTableView中,当主键Id存在时更新数据,不存在时添加数据解决方案

问题 有时在开发中&#xff0c;表格需要显示数据&#xff0c;每一行呢&#xff0c;需要记录对应的id。 当更新表格数据时&#xff0c;会根据id进行更新&#xff0c;id存在就更新行数据&#xff0c;不存在就添加一行新数据。 解决方案 如何知道id存在还是不存在呢&#xff1f…

芯片半导体科普

我们在日常工作和生活中&#xff0c;经常会使用到各种各样的电子或电器产品&#xff0c;例如电脑、手机、电视、冰箱、洗衣机等。 这些产品&#xff0c;如果我们把它拆开&#xff0c;都会看到类似下面这样的一块绿色板子。 有时候是蓝色或黑色的 大家都知道&#xff0c;这个绿…

玩转大数据9:机器学习在大数据分析中的应用

1. 引言 在大数据时代&#xff0c;机器学习在大数据分析中扮演着至关重要的角色。本文介绍机器学习在大数据分析中的重要性和应用场景&#xff0c;并探讨Java中可用的机器学习库和框架。 2. 机器学习的基本概念和算法 机器学习是当今人工智能领域的一个关键分支&#xff0c;…

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

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

论文笔记--A Fine-grained Interpretability Evaluation Benchmark for Neural NLP

论文笔记--A Fine-grained Interpretability Evaluation Benchmark for Neural NLP 1. 文章简介2. 文章概括3 文章重点技术3.1 数据收集3.2 数据扰动3.3 迭代标注和检查根因3.4 度量3.4.1 Token F1-score3.4.2 MAP(Mean Average Precision) 4. 文章亮点5. 原文传送门 1. 文章简…

联通宽带+老毛子Padavan固件 开启IP v6

联通宽带开启IP v6 参考&#xff1a; 联通宽带开启 IPV6 的方法_联通ipv6怎么开通-CSDN博客 个人宽带如何开启IPv6网络访问 - 知乎 (zhihu.com) 首先&#xff0c;你要确定当前你所在的地区运营商已经开通了IPV6&#xff0c;可以使用手机流量 IP查询(ipw.cn) | IPv6测试 | IPv…

人工智能学习8(集成学习之xgboost)

编译工具&#xff1a;PyCharm 文章目录 编译工具&#xff1a;PyCharm 集成学习XGBoost(Extreme Gradient Boosting)极端梯度提升树1.最优模型的构建方法XGBoost目标函数案例1&#xff1a;泰坦尼克号案例2&#xff1a;对奥拓集团差评进行正确分类。数据准备&#xff1a;1.第一种…