当前位置: 首页 > news >正文

Redis的过期删除策略和内存淘汰策略

🤔

  • 过期删除和内存淘汰乍一看很像,都是做删除操作的,这么分有什么意思?

  • 首先,设置过期时间我们很熟悉,过期时间到了,我么的键就会被删除掉,这就是我们常认识的过期删除,但是实际上的过期删除真的是这样吗?

1 过期删除策略

实际上,一般过期之后的键不是立刻删除的,一般过期键的清除策略有三种,分别是定时删除定期删除惰性删除

1.1 定时删除

定时删除时在设置键的过期时间的同时,创建一个定时器,让定时器在键过期时间来临时,立即执行对键的删除操作

定时删除看起来和我们原来想象的一样,这样对内存来说也确实比较友好,但是对CPU不友好,如果某个时间段比较多的key过期的话,可能会影响命令处理性能。

1.2 惰性删除

所谓惰性就是不要那么勤快,随时都盯着,用的时候发现不对再去删就行了,具体就是使用的时候发现key过期了,此时再进行删除。这个策略的思路是对应用而言,只要不访问,过不过期对于业务而言都无所谓,但是这样也是有代价的,就是,如果某些key一直不来访问,那么本该过期的key,就变成常驻的key了,这种策略对CPU友好,对内存不友好

1.3 定期删除

定期删除就是每隔一段时间,程序就对数据库进行一次检查,每次删除一部分过期键。

定时删除实际实现起来非常不容易,主要如果出现了一场,可能会有key遗漏,以及如果程序重启,原来的定时器就随之重启消失了,那就需要在启动时对过期的键进行进行一些操作,可能是重建定时器,这些都是额外的工作,而且会引入多余的复杂度。

从实际的功能而言,其实并不需要那么实时,所以惰性删除是可以考虑的,但是出于应删尽删的考虑,要保证最终没有漏网之鱼,那有没有这样的策略呢?

有的有的,兄弟有的,加上定期删除作为兜底就可以了。所以Redis过期键采用的删除策略是惰性删除+定期删除二者结合的方式进行,这样就可以以一定CPU消耗换取对内存的友好

1.3.1 定期删除需要关注的两个问题:

  1. 定期删除的频率

    1. 这取决于Redis周期任务的执行频率,周期任务里面会做关闭客户端,删除过期key的一系列任务,可以用INFO查看周期任务频率

  2. 每次删除的数量

    1. 随机选取20个key判断是否过期,同时检查过期key数量占比,如果>25%,则再抽20个重复上述流程,这里是一个循环的过程。

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

前面我们说到,过期删除和内存淘汰的区别是什么,都是做删除操作的?

我们先看它们解决的问题分别是什么?过期删除策略解决的是:过期的key怎么删除?内存淘汰策略解决的是:内存满了怎么办?

由此我们就可以推断出,它们在触发条件目标上,存在区别。

过期删除策略内存淘汰策略
触发条件键的过期时间到达(TTL到期)内存使用达到 maxmemory 限制
目标清理明确声明不再需要的数据腾出内存空间以维持服务可用性

2 内存淘汰策略

🤔 我们刚说到,内存淘汰策略是解决内存满了怎么办?Redis可以存多少数据?什么时候算满?

  • 在32位操作系统中,使用maxmemory来设置最大运行内存,默认值是3G,因为32位的机器最大只支持4GB的内存,而系统本身就需要一定的内存资源来支持运行,默认3G相对合理。

  • 在64位操作系统中,maxmemory的默认值是0,表示没有内存大小限制,也可以主动配置maxmemory

  • 当Redis存储超过这个配置值,则触发内存淘汰,所以说,内存满了其实就是达到设置的maxmemory值了

2.1 有哪些内存淘汰策略?

2.1.1 不进行数据淘汰的策略

noeviction(Redis3.0之后,默认的内存淘汰策略):它表示当运行内存超过最大设置内存时,不淘汰任何数据,这时如果有新的数据写入,会报错通知禁止写入,不淘汰任何数据但是如果没用数据写入的话,只是单纯的查询或者删除操作的话,还是可以正常工作。

2.1.2 进行数据淘汰的策略

2.1.2.1 在设置了过期时间的数据中进行淘汰
  1. volatile-random:随机淘汰设置了过期时间的任意键值

  2. volatile-ttl:优先淘汰更早过期的键值

  3. volatile-lru(Redis3.0之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值

  4. volatile-lfu(Redis4.0后新增的内存淘汰策略):淘汰了所有设置过期时间的键值中,最少使用的键值。

2.1.2.2 在所有数据范围内进行淘汰
  • allkeys-random:随机淘汰任意键值

  • allkeys-lru:淘汰整个键值中最久未使用的键值

  • allkeys-lfu(Redis4.0后新增的内存淘汰策略):淘汰整个键值中最少使用的键值

2.1.3 内存淘汰算法LRU

🤔 什么是LRU算法?

LRU全称是Least Recently Used,翻译为最近最少使用,会选择淘汰最近最少使用的数据。

传统LRU算法的实现是基于“链表”结构,链表中的元素按照操作顺序从前往后排列,最新操作的键会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可,因为链表尾部的元素就代表最久未被使用的元素

Redis并没有使用这样的方式实现LRU算法,因为传统的LRU算法存在两个问题:

  • 需要用链表管理所有的缓存数据,这会带来额外的空间开销

  • 当用数据被访问时,需要在链表上把该数据移动到头端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低Redis缓存性能

Redis是如何实现LRU算法的?

  • Redis实现的是一种近似LRU算法,目的是为了更好的节约内存,它的实现方式是在Redis的对象结构体中添加一个额外的字段lru,用于记录此数据的最后一次访问时间

  • 当Redis进行内存淘汰时,会使用随机采样的方式来淘汰数据,它是随机取5个值(这个值可以进行配置),然后淘汰最久没有使用的哪个

  • Redis实现LRU算法的优点:

    • 不用为所有的数据维护一个大链表,节约了空间占用

    • 不用在每次数据访问时都移动链表项,提升了缓存的性能

  • 但是LRU算法有一个问题:无法解决缓存污染问题:

    • 当应用一次性加载大量仅访问一次的数据时:

      • 这些数据的“最后一次访问时间”非常新,会挤占缓存空间

      • 即使他们是“一次性”的,LRU也会认为它们“最近被使用过”,而淘汰真正有价值但最后一次访问较早的热点数据

      • 即短期批量操作干扰长期热点数据的保留

就像你学习了一天,刚刚打开手机看了一眼,你的家长回家,说,怎么就知道玩手机,不学习

补充:Redis的对象结构体

Redis中的key和value都被封装成redisObject结构体,key的类型只能是字符串类型,而value的类型可以是任意的Redis数据类型。

typedef struct redisObject {unsigned type:4;          // 对象类型(如字符串、哈希等)unsigned encoding:4;     // 对象编码(底层实现方式)unsigned lru:LRU_BITS;   // LRU时间戳 或 LFU计数器(内存淘汰策略相关)24bitint refcount;            // 引用计数器(内存回收)void *ptr;               // 指向实际数据的指针
} robj;

🤔 如何解决缓存污染的问题呢?

Redis4.0之后引入了LFU算法来解决这个问题。

2.1.4 内存淘汰算法LFU

LFU全称是Least Frequently Used翻译为最近最不常用,LFU是根据数据访问次数来淘汰数据的,它的核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。

所以LFU算法会记录每个数据的访问次数。当一个数据被再次访问时,就会增加该数据的访问次数。这样就解决了偶尔被访问一次之后,数据留存在缓存中很长一段时间的问题

Redis是如何实现LFU算法的?

  • LFU算法相比于LRU算法的实现,多记录了数据的访问频次的信息。

  • LRU和LFU并不会同时开启,基于这个情况,加上节约内存的考虑,Redis在LFU策略下复用lru字段,用它来表示LFU的信息

  • 将24bits的lru字段分成两段来存储,高16bit存储ldt(Last Decrement Time),低8bit存储logc(Logitstic Counter)

    • ldt是用来记录key的访问时间戳

    • logc是用来记录key的访问频次,它的值越小表示使用的频率越低,越容易淘汰,每个新加入的key的logc初始值为5

    • 注:logc并不是单纯的访问次数,而是访问频次(访问频率),因为logc会随时间推移而衰减。

      • 如果上一次访问时间很久,那么访问频次就会衰减,比如一个key,它原来的logc是255,夸张一点,一年没访问了,不该衰减吗

Redis访问key时,logc的变化:

  1. 先按照上次访问距离当前时长,来对logc进行衰减

  2. 然后,再按照一定概率增加logc的值

redis.conf提供了两个配置项,用于调整LFU算法从而控制logc的增长和衰减:

  • lfu-decay-time用于调整logc的衰减速度,它是一个以分钟为单位的数值,默认值为1,lfu-decay-time值越大,衰减越慢

  • lfu-log-factor用于调整logc的增长速度,lfu-log-factor值越大,logc增长越慢

相关文章:

  • Spring MVC HandlerAdapter 的作用是什么? 为什么 DispatcherServlet 不直接调用 Controller 方法?
  • YOLOv8融合CPA-Enhancer【提高恶略天气的退化图像检测】
  • oracle 锁的添加方式和死锁的解决
  • Yocto meta-toradex-security layer 创建独立数据分区
  • MongoDB副本集搭建与核心机制
  • 【回眸】香橙派Zero2(全志H616)初探
  • 2026届华为海思秋暑期IC实习秋招笔试真题(2025.04.23更新)
  • 函数的多种参数使用形式
  • 驱动开发系列53 - 一个OpenGL应用程序是如何调用到驱动厂商GL库的
  • 基于Python爬虫的音乐歌手的歌名和歌词信息爬取(可以输入歌手名字,然后爬取到该歌手的全部歌名和歌词信息)
  • Ubuntu主机上通过WiFi转有线为其他设备提供网络连接
  • 【蓝桥杯】产值调整
  • 基于大模型的结肠癌全病程预测与诊疗方案研究
  • Android插拔U盘导致黑屏问题排查
  • macOS 连接远程服务器的推荐方法和工具
  • Kingbase性能优化浅谈
  • 《深入理解计算机系统》阅读笔记之第一章 计算机系统漫游
  • SVN 右键不显示clean up的解决方法
  • java—11 Redis
  • vxe-table封装表头
  • 长三角数智文化产业基金意向签约会成功举办
  • 政治局会议:持续稳定和活跃资本市场
  • 高糖高脂食物可能让你 “迷路”
  • 牧原股份一季度归母净利润44.91亿元,同比扭亏为盈
  • 肖扬任武钢集团董事长、党委书记
  • 全国双拥模范城(县)名单