百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis 的过期策略是如何实现的?(redis 的过期策略是如何实现的呢)

mhr18 2024-10-26 10:42 23 浏览 0 评论

背景

为了减少占用内存空间,通常会对放到 Redis 中的键通过 expire 设置一个过期时间,那 Redis 是怎么实现对过期键删除的呢?

设置过期时间

设置过期时间的四种方式

# 将 key 的过期时间设置为 ttl 秒

expire <key> <ttl>

# 将 key 的过期时间设置为 ttl 毫秒

pexpire <key> <ttl>

# 将 key 的过期时间设置为 timestamp 指定的秒数时间戳

expire <key> <timestamp>

# 将 key 的过期时间设置为 timestamp 指定的毫秒数时间戳

pexpire <key> <timestamp>

其中前三种方式都会转化为最后一种方式来实现过期时间

保存过期时间

我们看下 redisDb 的结构

typedef struct redisDb {

dict *dict; /* The keyspace for this DB */

dict *expires; /* Timeout of keys with a timeout set */

...

}

可见在 redisDb 结构的 expire 字典(过期字典)保存了所有键的过期时间

过期字典的键是一个指向键空间中的某个键对象的指针

过期字典的值保存了键所指向的数据库键的过期时间

注意

图中过期字段和键空间中键对象有重复,实际中不会出现重复对象,键空间的键和过期字典的键都指向同一个键对象

过期键的判断

通过查询过期字典,检查下面的条件判断是否过期

  1. 检查给定的键是否在过期字典中,如果存在就获取键的过期时间
  2. 检查当前 UNIX 时间戳是否大于键的过期时间,是就过期,否则未过期

过期键的删除策略

惰性删除

在取出该键的时候对键进行过期检查,即只对当前处理的键做删除操作,不会在其他过期键上花费 CPU 时间

缺点:对内存不友好,如果一哥键过期了,但会保存在内存中,如果这个键还不会被访问,那么久会造成内存浪费,甚至造成内存泄露

如何实现?

就是在执行 Redis 的读写命令前都会调用 expireIfNeeded 方法对键做过期检查

如果键已经过期,expireIfNeeded 方法将其删除

如果键未过期,expireIfNeeded 方法不做处理

对应源码 db.c/expireIfNeeded 方法

int expireIfNeeded(redisDb *db, robj *key) {

// 键未过期返回0

if (!keyIsExpired(db,key)) return 0;

// 如果运行在从节点上,直接返回1,因为从节点不执行删除操作,可以看下面的复制部分

if (server.masterhost != NULL) return 1;

// 运行到这里,表示键带有过期时间且运行在主节点上

// 删除过期键个数

server.stat_expiredkeys++;

// 向从节点和AOF文件传播过期信息

propagateExpire(db,key,server.lazyfree_lazy_expire);

// 发送事件通知

notifyKeyspaceEvent(NOTIFY_EXPIRED,

"expired",key,db->id);

// 根据配置(默认是同步删除)判断是否采用惰性删除(这里的惰性删除是指采用后台线程处理删除操做,这样会减少卡顿)

return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :

dbSyncDelete(db,key);

}

补充

我们通常说 Redis 是单线程的,其实 Redis 把处理网络收发和执行命令的操作都放到了主线程,但 Redis 还有其他后台线程在工作,这些后台线程一般从事 IO 较重的工作,比如刷盘等操作。

上面源码中根据是否配置 lazyfreelazyexpire(4.0版本引进) 来判断是否执行惰性删除,原理是先把过期对象进行逻辑删除,然后在后台进行真正的物理删除,这样就可以避免对象体积过大,造成阻塞,后面会在深入研究下 Redis 的 lazyfree 原理 源码位置 lazyfree.c/dbAsyncDelete 方法

定期删除

定期策略是每隔一段时间执行一次删除过期键的操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU 时间的影响,同时也减少了内存浪费

Redis 默认会每秒进行 10 次(redis.conf 中通过 hz 配置)过期扫描,扫描并不是遍历过期字典中的所有键,而是采用了如下方法

  1. 从过期字典中随机取出 20 个键
  2. 删除这 20 个键中过期的键
  3. 如果过期键的比例超过 25% ,重复步骤 1 和 2

为了保证扫描不会出现循环过度,导致线程卡死现象,还增加了扫描时间的上限,默认是 25 毫秒(即默认在慢模式下,如果是快模式,扫描上限是 1 毫秒)

对应源码 expire.c/activeExpireCycle 方法

void activeExpireCycle(int type) {

...

do {

...

if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)

// 选过期键的数量,为 20

num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;

while (num--) {

dictEntry *de;

long long ttl;

// 随机选 20 个过期键

if ((de = dictGetRandomKey(db->expires)) == NULL) break;

...

// 尝试删除过期键

if (activeExpireCycleTryExpire(db,de,now)) expired++;

...

}

...

// 只有过期键比例 < 25% 才跳出循环

} while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);

}

...

}

补充

因为 Redis 在扫描过期键时,一般会循环扫描多次,如果请求进来,且正好服务器正在进行过期键扫描,那么需要等待 25 毫秒,如果客户端设置的超时时间小于 25 毫秒,那就会导致链接因为超时而关闭,就会造成异常,这些现象还不能从慢查询日志中查询到,因为慢查询只记录逻辑处理过程,不包括等待时间。

所以我们在设置过期时间时,一定要避免同时大批量键过期的现象,所以如果有这种情况,最好给过期时间加个随机范围,缓解大量键同时过期,造成客户端等待超时的现象

Redis 过期键删除策略

Redis 服务器采用惰性删除和定期删除这两种策略配合来实现,这样可以平衡使用 CPU 时间和避免内存浪费

AOF、RDB 和复制功能对过期键的处理

RDB

生成 RDB 文件

在执行 save 命令或 bgsave 命令创建一个新的 RDB文件时,程序会对数据库中的键进行检查,已过期的键就不会被保存到新创建的 RDB文件中

载入 RDB 文件

主服务器:载入 RDB 文件时,会对键进行检查,过期的键会被忽略

从服务器:载入 RDB文件时,所有键都会载入。但是会在主从同步的时候,清空从服务器的数据库,所以过期的键载入也不会造成啥影响

AOF

AOF 文件写入

当过期键被惰性删除或定期删除后,程序会向 AOF 文件追加一条 del 命令,来显示的记录该键已经被删除

AOF 重写

重启过程会对键进行检查,如果过期就不会被保存到重写后的 AOF 文件中

复制

从服务器的过期键删除动作由主服务器控制

主服务器在删除一个过期键后,会显示地向所有从服务器发送一个 del 命令,告知从服务器删除这个过期键

从服务器收到在执行客户端发送的读命令时,即使碰到过期键也不会将其删除,只有在收到主服务器的 del 命令后,才会删除,这样就能保证主从服务器的数据一致性

疑问点?

  1. 如果主从服务器链接断开怎么办?
  2. 如果发生网络抖动,主服务器发送的 del 命令没有传递到从服务器怎么办?

其实上面两个问题 Redis 开发者已经考虑到了,只是主从复制涉及到的知识点还挺多,下面我就简单的说下解决的思路,后面会再分享一篇主从复制的文件

首先看疑问点1-如果主从服务器链接断开怎么办?

Redis 采用 PSYNC 命令来执行复制时的同步操作,当从服务器在断开后重新连接主服务器时,主服务器会把从服务器断线期间执行的写命令发送给从服务器,然后从服务器接收并执行这些写命令,这样主从服务器就会达到一致性,那主服务器如何判断从服务器断开链接的过程需要哪些命令?主服务器会维护一个固定长度的先进先出的队列,即复制积压缓冲区,缓冲区中保存着主服务器的写命令和命令对应的偏移量,在主服务器给从服务器传播命令时,同时也会往复制积压缓冲区中写命令。从服务器在向主服务器发送 PSYNC 命令时,同时会带上它的最新写命令的偏移量,这样主服务器通过对比偏移量,就可以知道从服务器从哪里断开的了

然后,我们再来看疑问点2-如果发生网络抖动,主服务器发送的 del 命令没有传递到从服务器怎么办?

其实主从服务器之间会有心跳检测机制,主从服务器通过发送和接收 REPLCONF ACK 命令来检查两者之间的网络连接是否正常。当从服务器向主服务器发送 REPLCONF ACK 命令时,主服务器会对比自己的偏移量和从服务器发过来的偏移量,如果从服务器的偏移量小于自己的偏移量,主服务器会从复制积压缓冲区中找到从服务器缺少的数据,并将数据发送给从服务器,这样就达到了数据一致性

小结

本文主要分析了 Redis 的过期策略是采用惰性删除和定期删除两种策略配合完成,然后简单看了两种策略的源码和是怎么实现的。最后介绍了 Redis 在进行 RDB 、 AOF 和主从复制操作时,如何对过期键进行处理,特别介绍了主从复制在发生主从链接断开和网络抖动命令丢失是如何处理的,希望大家看完能有收获

参考资料

《Redis设计与实现》第二版.黄健宏

《Redis深度历险:核心原理与深度实战》.老钱

本文由博客一文多发平台 https://openwrite.cn 发布!

相关推荐

如何检查 Linux 服务器是物理服务器还是虚拟服务器?

在企业级运维、故障排查和性能调优过程中,准确了解服务器的运行环境至关重要。无论是物理机还是虚拟机,都存在各自的优势与限制。在很多场景下,尤其是当你继承一台服务器而不清楚底层硬件细节时,如何快速辨识它是...

第四节 Windows 系统 Docker 安装全指南

一、Docker在Windows上的运行原理(一)架构限制说明Docker本质上依赖Linux内核特性(如Namespaces、Cgroups等),因此在Windows系统上无法直...

C++ std:shared_ptr自定义allocator引入内存池

当C++项目里做了大量的动态内存分配与释放,可能会导致内存碎片,使系统性能降低。当动态内存分配的开销变得不容忽视时,一种解决办法是一次从操作系统分配一块大的静态内存作为内存池进行手动管理,堆对象内存分...

Activiti 8.0.0 发布,业务流程管理与工作流系统

Activiti8.0.0现已发布。Activiti是一个业务流程管理(BPM)和工作流系统,适用于开发人员和系统管理员。其核心是超快速、稳定的BPMN2流程引擎。Activiti可以...

MyBatis动态SQL的5种高级玩法,90%的人只用过3种

MyBatis动态SQL在日常开发中频繁使用,但大多数开发者仅掌握基础标签。本文将介绍五种高阶技巧,助你解锁更灵活的SQL控制能力。一、智能修剪(Trim标签)应用场景:动态处理字段更新,替代<...

Springboot数据访问(整合Mybatis Plus)

Springboot整合MybatisPlus1、创建数据表2、引入maven依赖mybatis-plus-boot-starter主要引入这个依赖,其他相关的依赖在这里就不写了。3、项目结构目录h...

盘点金州勇士在奥克兰13年的13大球星 满满的全是...

见证了两个月前勇士与猛龙那个史诗般的系列赛后,甲骨文球馆正式成为了历史。那个大大的红色标志被一个字母一个字母地移除,在周四,一切都成为了过去式。然而这座,别名为“Roaracle”(译注:Roar怒吼...

Mybatis入门看这一篇就够了(mybatis快速入门)

什么是MyBatisMyBatis本是apache的一个开源项目iBatis,2010年这个项目由apachesoftwarefoundation迁移到了googlecode,并且改名为M...

Springboot数据访问(整合druid数据源)

Springboot整合druid数据源基本概念SpringBoot默认的数据源是:2.0之前:org.apache.tomcat.jdbc.pool.DataSource2.0及之后:com.z...

Linux 中的 &quot;/etc/profile.d&quot; 目录有什么作用 ?

什么是/etc/profile.d/目录?/etc/profile.d/目录是Linux系统不可或缺的一部分保留配置脚本。它与/etc/profile文件相关联,这是一个启动脚本,该脚...

企业数据库安全管理规范(企业数据库安全管理规范最新版)

1.目的为规范数据库系统安全使用活动,降低因使用不当而带来的安全风险,保障数据库系统及相关应用系统的安全,特制定本数据库安全管理规范。2.适用范围本规范中所定义的数据管理内容,特指存放在信息系统数据库...

Oracle 伪列!这些隐藏用法你都知道吗?

在Oracle数据库中,有几位特殊的“成员”——伪列,它们虽然不是表中真实存在的物理列,但却能在数据查询、处理过程中发挥出意想不到的强大作用。今天给大家分享Oracle伪列的使用技巧,无论...

Oracle 高效处理数据的隐藏神器:临时表妙用

各位数据库搬砖人,在Oracle的代码世界里闯荡,处理复杂业务时,是不是总被数据“搅得头大”?今天给大家安利一个超实用的隐藏神器——临时表!当你需要临时存储中间计算结果,又不想污染正式数据表...

Oracle 数据库查询:多表查询(oracle多表关联查询)

一、多表查询基础1.JOIN操作-INNERJOIN:返回两个表中满足连接条件的匹配行,不保留未匹配数据。SELECTa.emp_id,b.dept_nameFROMempl...

一文掌握怎么利用Shell+Python实现多数据源的异地备份程序

简介:在信息化时代,数据安全和业务连续性已成为企业和个人用户关注的焦点。无论是网站数据、数据库、日志文件,还是用户上传的文档、图片等,数据一旦丢失,损失难以估量。尤其是当数据分布在多个不同的目录、服务...

取消回复欢迎 发表评论: