Redis内存策略:「过期Key删除策略」+ 「内存淘汰策略」

Redis之所以性能强,最主要的原因就是基于内存存储,然而单节点的Redis其内存大小不宜过大,否则会影响持久化或主从同步的性能。

Redis内存满了,会发生什么?

  • 在Redis的运行内存达到了某个阈值,就会触发内存淘汰机制  =>  防止把内存撑爆,这个阈值就是我们设置的最大运行内存。 

我们可以通过修改redis.conf配置文件来设置Redis的最大内存,配置项为maxmemory:

  • Redis默认情况下是没有对最大内存大小做限制的,默认情况下Redis就是根据你当前的服务器里面,当前最多可以申请到的内存大小来去做限制! 
# 格式:
# maxmemory <bytes>
# 例如:
maxmemory 1gb

当内存使用达到上限,就无法存储更多数据了,因此,为了解决这个问题,Redis内部会有两套内存回收的策略:

  • 内存过期策略
  • 内存淘汰策略

内存过期策略 - 过期key处理 - 过期删除策略

如何设置过期时间?
  1. expire key n秒     
  2. pexpire key  n毫秒     
  3. set   key  value  ex  n秒   
  4. set  key  value nx  n毫秒

查看某个key剩余的存活时间:TTL  key 

  • 我们可以通过expire / EX命令给Redis的key设置TTL(过期时间 / 存活时间),单位:秒,当key的TTL到期以后,即当过期时间到了以后,再次访问该key时返回的是nil,说明这个Key已经不存在了,对应的内存也得到释放,从而起到内存回收的目的。
# 写入一条数据
set num 123
# 设置20秒过期时间
expire num 20
# 写入一条数据并设置20s过期时间
set num EX 20

Redis是如何知道一个key是否过期呢?

  • Redis本身是一个典型的key-value的键值型内存存储数据库,因此所有的key-value都保存在Dict结构中,在其redisDb结构体中,有两个Dict,也就是哈希表:一个用来记录KEY-VALUE键值对(当然存的不是真正的Key-Value,存储的其实是RedisObject对应的内存地址的指针),另一个用来记录key的TTL。

过期字典存储在 redisDb 结构中,如下: 

来看下redisDb的底层源码:

typedef struct redisDb {
    dict dict;                 / The keyspace for this DB , 也就是存放KEY和VALUE的哈希表*/
    dict *expires;              /* 同样是哈希表,但保存的是设置了TTL的KEY,及其到期时间*/
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS /
    int id;                     / Database ID, 0 ~ 15 /
    long long avg_ttl;          / Average TTL, just for stats /
    unsigned long expires_cursor; / Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

每当我们对一个key设置了过期时间后,Redis会把该key带上过期时间存储到一个过期字典(expires dict)中,也就是说「过期字典」保存了数据库中所有 key 的过期时间。 

当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:

  • 如果不在,则正常读取键值;
  • 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期    =>   查询到对应的TTL,加以判断即可。

是不是TTL到期就立即删除了呢? 

TTL(Time To Live)的含义是存活时间。 

  • Redis并不会在Key过期时立刻删除KEY,因为要实现这样的效果就必须给每一个过期Key设置一个定时器,并监控这些Key的过期状态,然后去做判断,在key过期的那一刻给它立刻删掉   =>  定时删除它的优点就是保证过期key会被尽快删除,也就是内存可以尽快得到释放,因此,定时删除对内存是最友好的;
  • 如果说我们的key比较少那还好,但是如果我们的key非常的多,达到数十万甚至数百万,那么这些key如果我们都给它设置这样一个定时器,无论是对CPU还是对内存都会带来极大的负担,这样一来,就会严重影响到Redis服务本身的一个性能,所以说这个是没有办法接受的,因此,我们在实际应用当中,Redis采用的并不是立即删除,而是惰性删除  + 周期删除  =>   Redis 使用的过期删除策略是惰性删除+定期删除这两种策略配和使用。

Redis的过期KEY删除策略有两种:

  • 惰性删除

  • 周期删除或定期删除

惰性删除

  • 顾名思义就是TTL过期后不会立刻删除,惰性删除策略的做法是,不主动删除过期键,而是在访问使用一个key的时候,判断当前key有没有设置TTL过期时间,如果有,则检查该key的存活时间,如果发现key已经过期才执行删除操作。 

惰性删除策略的优缺点:

优点:
  • 因为每次访问时,才会检查该key是否过期,因此惰性删除策略可以节省CPU资源,对CPU时间最友好。
缺点:
  • 如果一个key已经过期,而这个key又仍然保留在数据库中,那么只要这个过期key一致没有被访问,它所占用的内存就不会释放,会造成一定的内存空间浪费,所以惰性删除策略对内存不友好   =>   这不就是会导致内存泄漏吗???

周期删除 / 定期删除策略

  • 顾名思义就是通过一个定时任务,每隔一段时间周期性的从数据库中抽取一定数量的key进行检查,并删除其中的过期key。

Redis默认会每秒进行10次过期检查(此配置可以通过Redis的配置文件redis.conf进行配置,配置键为hz,它的默认值是hz 10),每次检查数据库并不是遍历过期字典中的所有key,而是从数据库中随机抽取一定数量的key进行过期检查:

  1. 从过期字典中随机抽取20个key;
  2. 检查这20个key是否过期,并删除已过期的key;
  3. 如果本轮检查的已过期key的数量,超过5个(5 / 20 = 1 / 4 = 25%),也就是「已过期 key 的数量」占比「随机抽取 key 的数量」大于 25%,则继续随机抽查,重复步骤1;如果已过期的key的比例小于25%,则停止继续删除过期key,退出本轮检查,然后等待下一轮再检查。 

可以看到,定期删除是一个循环的流程

Redis为了保证定期删除不会出现循环过度,导致线程卡死现象,为此增加了定期删除循环流程的时间上限,默认不会超过25ms,超出时间限制则退出。 

定期删除策略的优缺点

优点:
  • 定期删除是Redis的主动删除策略,它可以确保过期key能够及时被删除
缺点:
  • 会占用CPU资源去扫描key,可能会影响到Redis的性能 

可以看到,惰性删除策略和定期删除策略都有各自的优缺点,所以Redis选择「惰性删除+定期删除」这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。 

如果过期键没有被访问,而定期删除又跟不上新键产生的速度,内存不就慢慢耗尽了吗? 

内存淘汰策略

内存淘汰:

  • 就是当Redis的内存使用达到设置的阈值时,Redis就会主动挑选部分key删除以释放更多内存的流程,这就叫做内存淘汰机制。

内存淘汰时机

当内存达到阈值时执行内存淘汰,但问题是Redis什么时候会去判断内存是否达到阈值呢?

  • Redis每次执行任何命令时,都会判断内存是否达到阈值。

当Redis内存不足时会怎么做?

  • 这取决于配置的内存淘汰策略,Redis支持很多种内存淘汰策略,例如LRU、LFU、Random,但默认的策略是直接决绝新的写入请求,而如果设置了其它策略,则会在每次执行命令后判断内存占用是否达到阈值,如果达到阈值则会基于配置的内存淘汰策略尝试进行内存淘汰,直到占用内存小于阈值为止。 

Redis 内存淘汰策略有哪些? 

Redis支持内存淘汰,配置参数maxmemory-policy决定了内存淘汰策略,这个参数一共有8个枚举值,也就是说Redis内存淘汰策略共有8种这八种策略大体分为「不进行数据淘汰」和「进行数据淘汰」两类策略:

1. 不进行数据淘汰的策略

  • noevictionRedis3.0之后,默认的内存淘汰策略) :禁止删除数据,它表示当Redis的运行内存超过最大设置内存时,也就是当Redis内存满时,不淘汰任何键值对数据,而是不再提供服务,不允许写入新数据,Redis的写命令会直接返回错误信息(但是读命令还是可以正常返回)。

 

2. 进行数据淘汰的策略

针对「进行数据淘汰」这一类策略,又可以细分为「在设置了过期时间的数据中进行淘汰」和「在所有数据范围内进行淘汰」这两类策略。 在设置了过期时间的数据中进行淘汰:

  • volatile-random随机淘汰设置了过期时间的任意键值 - 从已设置过期时间的数据集中任意选择数据淘汰;
  • volatile-ttl从已设置过期时间的数据集中挑选将要过期的数据淘汰:比较key的剩余TTL值,TTL越小越先被淘汰。
  • volatile-lru(Redis3.0 之前,默认的内存淘汰策略):LRU(Least Recently Used),最近最久未使用,利用LRU算法淘汰所有设置了TTL过期时间的键值中,最久未使用的键值;
  • volatile-lfu(Redis 4.0 后新增的内存淘汰策略):LFU(Least Frequently Used),最少频率使用,淘汰所有设置了TTL过期时间的键值中,最少使用的键值;

在所有数据范围内进行淘汰:

  • allkeys-random:对全体key,随机进行淘汰;
  • allkeys-lruLRU(Least Recently Used),最近最久未使用,淘汰全体键值中最久未使用的键值;
  • allkeys-lfuRedis 4.0 后新增的内存淘汰策略)::LFU(Least Frequently Used),最少频率使用,淘汰整个键值中最少使用的键值,即访问频率最低的那个key-value

比较容易混淆的有两个算法:

  • LRU(Least Recently Used),最近最久未使用(根据访问时间淘汰),会选择淘汰最近最少使用的数据,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。
  • LFU(Least Frequently Used),最少频率使用(根据访问频率淘汰),会统计每个key的访问频率,值越小淘汰优先级越高。 

Redis 4.0开始支持基于LFU算法的淘汰策略! 

Redis为什么新增了LFU淘汰策略?

为什么Redis 4.0有了LFU?
  • 比如Redis中的一个键,之前一直都没有被访问过,最近突然被访问了一次,如果使用LRU淘汰策略就很难被淘汰,因为LRU会把它定义为热键;
  • 而使用LFU淘汰策略该key就可能很快被淘汰,因为LRU优先淘汰最近未被使用的,而LFU淘汰的是最近访问频率最低的。
  • LFU比LRU淘汰更精确,有助于提升Redis的缓存命中率。 

Redis怎么知道某个KEY的最后一次访问时间或者是访问频率呢? 

  • redisObject结构体当中的lru就是记录最近一次访问时间和访问频率的,以低8位无符号数字来记录逻辑访问次数。
  • 逻辑访问次数又是怎么回事呢?8位无符号数字最大才255,访问次数超过255怎么办?

逻辑访问次数是如何计算的?

  • 由于记录访问次数的只有8bit,即便是无符号数,最大值只有255,不可能记录真实的访问次数,因此LFU的访问次数之所以叫做逻辑访问次数,是因为并不是每次key被访问都计数,Redis统计的其实是逻辑访问次数,这其中是一个计算公式,会根据当前的访问次数做计算,结果要么是次数 + 1,要么是次数不变,且最大不超过255,除此以外,逻辑访问次数还有一个衰减周期,访问次数会随时间衰减,默认为1分钟,即每隔1分钟逻辑访问次数会 -1,这样逻辑访问次数就能基本反映出一个key的访问热度了。

 显然LFU的基于访问频率的统计更符合我们的淘汰目标,因此官方推荐使用LFU算法。 

内存淘汰用到的是LRU算法吗?

  • 嗯...Redis使用的是近似LRU算法,传统LRU算法的实现需要一个双向链表来记录数据最近被访问的顺序,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的数据即可,因为链表尾部的元素就代表最久未被使用的元素。 
  • 但是Redis中的KEY可能有数百万甚至更多,出于节省内存的考虑,Redis的LRU算法并非完整的实现,Redis的算法并不是真正的LRU,而是一种基于抽样的近似LRU算法!
  • Redis采用的是抽样法,即每次抽样一定数量(maxmemory-samples)的key,然后和目前维持的淘汰候选池综合比较,然后基于内存策略做排序,找出淘汰优先级最高的,删除这个key,这就使得算法的结果更接近于真正的LRU算法了,特别是在抽样值较高的情况下(例如10),可以达到与真正的LRU接近的结果。

当Redis作为缓存使用的时候,推荐使用allkeys-lru淘汰策略,该策略会将最近最久未使用的key淘汰,像这种key后期命中的概率也最低,所以将其淘汰。 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java基础之set集合练习(HashSet,LinkedHashSet,TreeSet)

1&#xff1a;完成以下填空&#xff1a; Collection 接口的特点是 存放list与set共性内容 没有直接实现类 没有明确的存储特点要求 ; 2&#xff1a;List 接口的特点是元素 有 &#xff08;有|无&#xff09;顺序&#xff0c; 可 &#xff08;可以|不可以&#…

九、HTML头部<head>

一、HTML头部<head> 1、<title>- 定义了HTML文档的标题 使用 <title> 标签定义HTML文档的标题 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>我的 HTML 的第一页</title> </head><b…

MySQL8.0安装(Linux - centos)

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 其他…

STM32F103GPIO工作模式及原理

目录 GPIO简介GPIO工作模式输入模式输出模式输出速度 GPIO框图和电路解析电路标识电路元件 GPIO工作模式电路解析浮空输入上拉输入下拉输入模拟输入开漏输出推挽输出推挽式复用功能开漏式复用功能 IO工作模式的选取输入模式输出模式 GPIO简介 GPIO&#xff0c;全称为通用输入输…

isaacgym(legged_gym)学习 (二)—— 设置环境地形

isaacgym(legged_gym)学习 &#xff08;二&#xff09;—— 设置环境地形 文章目录 isaacgym(legged_gym)学习 &#xff08;二&#xff09;—— 设置环境地形前言一、了解isaacgym中地形如何构成的二、自定义修改1.代码2.查看script/play.py 总结 前言 如何设置isaacgym中的环…

CISSP 第9章:安全脆弱性、威胁和对策

第九章 安全脆弱性、威胁和对策 9.1 评估和缓解安全脆弱性 9.1 硬件 处理器 执行类型 多任务处理&#xff1a; 同时处理两个或更多任务 多处理&#xff1a; 利用多个处理器完成一个应用程序的处理能力 多程序设计&#xff1a;通过操作系统对单个处理器上的两个任务进行协调&…

NPS 内网穿透安装

NPS 内网穿透安装 NPS分为服务端和客户端&#xff0c;对应的不同操作系统软件可以在GitHub RELEASES自行选择下载。 服务端搭建 由于个人非企业级使用&#xff0c;为了方便直接使用docker安装 1.docker运行 (注意需要提前下载conf文件&#xff0c;或者将镜像中文件copy出来…

.net 开源混淆器 ConfuserEx

官网&#xff1a;ConfuserEx 下载地址&#xff1a;Releases yck1509/ConfuserEx GitHub 使用参考&#xff1a;https://www.cnblogs.com/zhangzhi19861216/p/3937434.html

我是在一片骂声中成长起来的!!

曾几何&#xff0c;我的技术真的很烂&#xff0c;烂到技术主管每次都是点名要Review我的业务代码。 曾几何&#xff0c;我对技术沉淀没有一点自我意识&#xff0c;总是觉得临时抱一下佛脚就可以了。 曾几何&#xff0c;我也觉得技术无用&#xff0c;看看那些业务领导&#xf…

第5章 保护资产安全

5.1 对信息和资产进行识别和分类 数据安全生命周期管理是指数据保护始于数据被首次创建时&#xff0c;一直持续到该数据被销毁时。 实现生命周期保护的第一步是对信息和资产进行识别和分类。 组织常将分类定义纳入安全策略中。然后&#xff0c;人员根据安全策略要求适当地标记…

小黑迈出了应用开发第一步,通过武沛奇老师b站django入门课程了解了django,准备学习前端基础:django入门沛奇老师课程笔记

课程来源: https://www.bilibili.com/video/BV1NL41157ph/?p71&spm_id_from333.880.my_history.page.click 课程链接与学习资料推荐 django入门课程 https://www.bilibili.com/video/BV1NL41157ph/?p71&spm_id_from333.880.my_history.page.click django入门项目…

c语言-库函数qsort()初识

目录 前言一、qsort()的介绍及使用1.1 qsort()的介绍1.2 qsort()的使用1.2.1 使用qsort|()对整型数组按照升序排序1.2.2 使用qsort()对整型数组按照降序排序1.2.3 使用qsort()对结构体数组数据进行排序 二、利用冒泡排序模拟实现对任何数据进行排序2.1 冒泡排序2.2 模仿qsort()…

BurpSuite信息收集篇

BurpSuite信息收集篇 1.填充目标站点地图2.使用 Burp Suite 自动发现内容3.使用 Burp Suite 枚举子域1.填充目标站点地图 演示站点 ginandjuice.shop 1、抓取目标的请求包 2、在不关闭浏览器的情况下,转到目标>站点地图。请注意,已自动添加一个节点来表示目标域 3、右键…

【EasyExcel】导出excel冻结表头和冻结指定列并支持筛选器

需求背景&#xff1a; 导出excel的同时冻结表头和前两列基础信息&#xff0c;方便导出后用户查看信息。 一、技术选型&#xff1a; easyExcel的自定义写策略处理&#xff1a;SheetWriteHandler 二、方案设计&#xff1a;&#xff08;基于实现 SheetWriteHandler 接口&#xf…

RabbitMQ(九)死信队列

目录 一、简介1.1 定义1.2 何时进入死信队列&#xff1f;1.3 死信消息的变化1.4 死信队列的应用场景1.5 死信消息的生命周期 二、代码实现2.1 死信队列的配置步骤2.2 配置类2.3 配置文件2.4 生产者2.5 业务消费者2.6 死信消费者2.7 测试结果 三、总结 RabbitMQ 是流行的开源消息…

【EI会议征稿通知】第三届城市规划与区域经济国际学术会议(UPRE 2024)

第三届城市规划与区域经济国际学术会议&#xff08;UPRE 2024&#xff09; 2024 3rd International Conference on Urban Planning and Regional Economy 第三届城市规划与区域经济国际学术会议&#xff08;UPRE 2024&#xff09;于2024年4月19-21日在泰国曼谷举行。会议旨在…

Qt/QML编程学习之心得:Linux下读写文件File(24)

在Linux嵌入式系统中,经常会使用Qt来读写一个文件,判断一个文件是否存在,具体如何实现呢? 首先,要使用linux系统中相关的头文件: #include <unistd.h> #include <stdio.h> #include <stdlib.h> 其次,判断路径是否存在, if(!dir.exists()){mkdir(…

智能驾驶打响“进阶战”:高、中、低阶赛道如何突围?

伴随智能驾驶普及进入新周期&#xff0c;高、中、低阶市场呈现不同的突围方式。 根据高工智能汽车研究院监测数据&#xff0c;2023年1-9月&#xff0c;中国市场&#xff08;不含进出口&#xff09;乘用车新车销售标配搭载L2&#xff08;含L2&#xff09;的渗透率达到36.31%&am…

QT常用控件使用及布局

QT常用控件使用及布局 文章目录 QT常用控件使用及布局1、创建带Ui的工程2、ui界面介绍1、界面设计区2、对象监视区3、对象监属性编辑区4、信号与槽5、布局器6、控件1、Layouts1、布局管理器2、布局的dome 2、Spacers3、Buttons4、项目视图组(Item Views)5、项目控件组(Item Wid…

【sgPasswordInput】自定义组件:带前端校验密码强度的密码输入框,能够提供密码强度颜色提示和文字提示

特性&#xff1a; 有密码强度颜色提示密码强度进度条提示支持设置默认输入提示和密码长度 sgPasswordInput源码 <template><div :class"$options.name" style"width: 100%"><el-inputstyle"width: 100%"ref"psw"type&…