Redis的LRU和LFU浅谈

article/2025/10/16 22:28:25

1.概论      

        Redis中的缓存淘汰算法大体分为两种,volatile-xxx和allkeys-xxx。volatile-xxx 是对带过期时间的 key 进行淘汰,即使用了expire的key;allkeys-xxx 策略会对所有的key 进行淘汰。如果只是用redis做缓存,几乎不使用持久化功能,则使用allkeys-xxx,每个key不带过期时间。而如果要使用持久化,或key的量级特别大的时候使用 volatile-xxx策略,这样可以保留没有设置过期时间的key,它们是永久的 key 不会被淘汰算法淘汰。

        但即使设置了expire,也不是在他过期后直接淘汰,这样需要维护一个轮询的线程,还需要考虑读取个删除线程的冲突,会增加额外复杂度,故redis是在每次获取key时,先去检查是否过期,过期返回(nil)。

      具体的淘汰策略中,比较常用的有LRU和LFU两种:

        LRU(The Least Recently Used,最近最久未使用算法):如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最久没有访问的数据最先被置换(淘汰)。

        LFU(Least Frequently Used ,最近最少使用算法):如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰。

        这是操作系统的课程中内存做换页时的两种经典算法,但实际实现起来非常麻烦,如果使用某些数据结构,但就LRU而言,就将近100行代码。(字节跳动就爱让你手撕这个,我好多同学面试的时候就把他背下来,直接默写)。

        而Redis中的实现比较不同,个人认为最大的区别在于随机数的使用。详情如下

2.LRU 算法

        从LRU的实现上来讲,要么额外存储一个访问时间字段,要么将所有key做成链表,每次读取key,对链表做重新排序。原生的LRU用的就是第二种消耗大量的额外的内存,且需要大量链表操作和排序。而Redis中的实习则很简单,类似于第一种,在现有数据结构的基础上使用随机采样法来淘汰元素,能达到和 LRU算法非常近似的效果(详细效果下面有图可以看到)。 Redis 为实现近似LRU 算法,它给每个 key 增加了一个额外的小字段,这个字段的长度是 24 个 bit,也就是最后一次被访问的时间戳。

        而LRU 淘汰不一样,它的处理方式只有懒惰处理。当 Redis 执行写操作时,发现内存超出 maxmemory(想到要阈值,可以通过参数配置),就会执行一次LRU 淘汰算法。这个淘汰就是我上面说的随机的,随机采样出 5(默认5,可以通过参数配置) 个 key,然后淘汰掉最旧的 key,如果淘汰后内存还是超出 maxmemory,那就继续随机采样淘汰,直到内存低于maxmemory 为止。

        而采样范围就是看maxmemory-policy选择的是allkeys还是volatile。

        但这种随机性肯定会带来一个局部性问题,举例来说就是现在有10个key,由新到旧分别是1,2,3,4,5,6,7,8,9,10,如果现在要淘汰三个,严格的LRU应该淘汰8,9,10,但redis可能存在取样取出来1,2,3,4,5,此时只能淘汰5。这样的随机性可能带来淘汰的不严格,但因为实现简单,不会待读写key造成太大硬性,还是可以接收。

        而且在Redis 3.0中对近似LRU还做了一点小优化,也就是增加了一个数组,叫淘汰池(eviction poo,大小是 maxmemory_samples,可设置),在每一次淘汰循环中,新随机出来的 key 列表会和淘汰池中的 key 列表进行比较和排序,淘汰掉最旧的一个 key 之后,保留剩余较旧的 key 列表放入淘汰池中留待下一个循环。因为随机性可能会使得淘汰的数据过新,上一次淘汰的数远比这次淘汰的数新,使用淘汰池将这个问题解决。注:淘汰池中的key并未清除,仍可读取。

        这个是很小的优化,且内存和时间上的开销并不大,但对算法的提升还是很大的,如图

         图中绿色部分是新加入的 key,深灰色部分是老旧的 key,浅灰色部分是通过 LRU 算法淘汰掉的 key。从图中可以看出采样数量越大,近似 LRU 算法的效果越接近严格 LRU算法。同时 Redis3.0 在算法中增加了淘汰池,进一步提升了近似 LRU 算法的效果。

3.LFU

        LRU即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。所以要存储两个额外字段,一个是时间区间,一个是访问次数。Redis在4.0以后支持,Redis使用使用24bit存储:其中16bit用于表示上一次递减时间 (时间戳的后16位),8bit表示访问次数。注:8位只能代表255,但是redis并没有采用线性上升的方式,而是通过一个复杂的公式,通过配置两个参数来调整数据的递增速度。如在影响因子 lfu-log-factor 为10的情况下,经过1百万次命中才能达到 255。

        每次读取key会检查这24bit,根据当前时间戳key中那24bit存的时间的差值,如果距离现在超过 N 分钟(可配置lfu-decay-time),则递减或者减半,然后通过衰减后的24bit通过LFULogIncr()重新计算后更新这24bit。具体如何减少见代码

void updateLFU(robj *val) {//衰减unsigned long counter = LFUDecrAndReturn(val);//根据衰减后的counter重新计算新的countercounter = LFULogIncr(counter);val->lru = (LFUGetTimeInMinutes()<<8) | counter; 
}unsigned long LFUDecrAndReturn(robj *o) {unsigned long ldt = o->lru >> 8;unsigned long counter = o->lru & 255; unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;//server.lfu_decay_time默认为1,每经过一分钟counter衰减1if (num_periods)counter = (num_periods > counter) ? 0 : counter - num_periods;//如果需要衰减,则计算衰减后的值return counter;
}uint8_t LFULogIncr(uint8_t counter) {if (counter == 255) return 255;//counter最大只能存储到255,到达后不再增加double r = (double)rand()/RAND_MAX;//算一个随机的小数值
//新加入的key初始counter设置为LFU_INIT_VAL,默认5double baseval = counter - LFU_INIT_VAL;if (baseval < 0) baseval = 0;double p = 1.0/(baseval*server.lfu_log_factor+1);//server.lfu_log_facotr默认为10
//counter越大,则p越小,随机值r小于p的情况就越少,counter增加得越来越慢if (r < p) counter++;return counter;
}unsigned long LFUGetTimeInMinutes(void) {return (server.unixtime/60) & 65535;//获取分钟级别的时间戳
}

        lfu随着分钟数对counter做衰减是基于一个原理:过去被大量访问的key不一定现在仍然会被访问。相当于除了计数,给时间也增加了一定的权重进行递减。

        而默认情况下新写的key的后8位计数器的值为5(可配置),防止因为访问频率过低而直接被删除。

        删除时,还是随机的,随机采样N个key(与LRU一样),淘汰25bit最小的。

参考文献:

[1] 官方文档,Key eviction。

[2] 钱文品,Redis深度历险:核心原理和应用实践,书籍。

[3]知乎:张老师,Redis中的LRU算法,一篇文章彻底搞懂。


http://chatgpt.dhexx.cn/article/tbdNilWX.shtml

相关文章

LFU五种实现方式,从简单到复杂

前言 最近刷力扣题&#xff0c;对于我这种 0 基础来说&#xff0c;真的是脑壳疼啊。这个月我估计都是中等和困难题&#xff0c;没有简单题了。 幸好&#xff0c;力扣上有各种大牛给写题解。看着他们行云流水的代码&#xff0c;真的是羡慕不已。让我印象最深刻的就是人称 “甜…

LRU和LFU

LRU 根据题意&#xff0c;使用过的数据放在链表的尾部&#xff0c;容量满了就删除链表的头&#xff0c;使用的数据结构是LinkedHashMap。 146. LRU 缓存(中等) class LRUCache {private int cap;private LinkedHashMap<Integer, Integer> list;public LRUCache(int…

LFU缓存详解

LFU缓存详解 文章目录 LFU缓存详解一、LFU概念二、分析方法一&#xff1a;哈希表 平衡二叉树方法二&#xff1a;双哈希表 一、LFU概念 LRU详解&#xff1a;https://blog.csdn.net/wolfGuiDao/article/details/105862106 LRU 算法相当于把数据按照时间排序&#xff0c;这个需求…

LRU和LFU算法

LRU淘汰算法 LRU&#xff1a;Least Recently Used&#xff0c;最近最少使用。即淘汰掉最近最少使用的数据。 以时间轴作为走向&#xff1a; 如果数据A刚被访问&#xff0c;那么A就应该调至头部&#xff1a; 假如内存容量已满&#xff0c;现在新数据E被插入&#xff0c;需要淘…

FIFO、LRU、LFU的含义和原理

FIFO&#xff1a;First In First Out&#xff0c;先进先出LRU&#xff1a;Least Recently Used&#xff0c;最近最少使用 LFU&#xff1a;Least Frequently Used&#xff0c;最不经常使用 以上三者都是缓存过期策略。 原理和实现&#xff1a; 一、FIFO按照“先进先出&#…

LFU算法

LFU&#xff08;LeastFrequently Used&#xff09;&#xff0c;即最近最多使用算法。它是基于“如果一个数据在最近一段时间内使用次数很少&#xff0c;那么在将来一段时间内被使用的可能性也很小”的思路。LFU算法需要维护一个队列记录所有数据的访问记录&#xff0c;每个数据…

Redis精通系列——LFU算法详述(Least Frequently Used - 最不经常使用)

本文已收录于专栏 ❤️《Redis精通系列》❤️ 上千人点赞收藏&#xff0c;全套Redis学习资料&#xff0c;大厂必备技能&#xff01; 目录 1、简介 2、实现方式 2.1 LRU实现方式 2.2 LFU实现方式 3、LFU使用 3.1 配置文件开启LFU淘汰算法 1、简介 LRU有一个明显的缺点&…

什么是LRU?什么是LFU

缓存算法是指令的一个明细表&#xff0c;用于决定缓存系统中哪些数据应该被删去。 常见类型包括LFU、LRU、ARC、FIFO、MRU。 最不经常使用算法&#xff08;LFU&#xff09;&#xff1a; 这个缓存算法使用一个计数器来记录条目被访问的频率。通过使用LFU缓存算法&#xff0c;…

LRU算法和LFU算法

文章目录 1、LRU算法2、LFU算法 1、LRU算法 LRU是Least Recently Used的缩写&#xff0c;即最近最少使用&#xff0c;是一种常用的页面置换算法&#xff0c;选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段&#xff0c;用来记录一个页面自上次被访问以来所…

LRU算法与LFU算法

LRU算法 LRU是Least Recently Used的缩写&#xff0c;即最近最少使用算法&#xff0c;是操作系统中页面置换算法的一种&#xff0c;由于内存中存放的页数有限&#xff0c;所以将最近没有使用到的算法会移出内存。 LRU原理&#xff1a; LRU原理如下图所示&#xff1a; 它会将最…

【算法】LFU及其优化

文章目录 什么是LFU&#xff1f;设计思路代码实现&#xff08;基础版本&#xff09;参考论文代码实现&#xff08;优化版本&#xff09;区别 什么是LFU&#xff1f; LRU及其实现 上文讲解了LRU&#xff0c;他是一个基于最近是否被访问来做缓存淘汰的策略。 那么今天介绍一个新…

LRU和LFU算法解析

文章目录 LRU和LFU算法解析LRULRU概念LRU算法实现LRU算法描述LRU算法图示LRU C代码代码测试 LFULFU概念LFU算法实现LFU算法描述LFU算法图示LFU C代码代码测试 总结 LRU和LFU算法解析 LRU LRU概念 LRU&#xff08;The Least Recently Used&#xff0c;最近最少未使用&#xf…

LRU和LFU的区别

对于web开发而言&#xff0c;缓存必不可少&#xff0c;也是提高性能最常用的方式。无论是浏览器缓存(如果是chrome浏览器&#xff0c;可以通过chrome:://cache查看)&#xff0c;还是服务端的缓存(通过memcached或者redis等内存数据库)。缓存不仅可以加速用户的访问&#xff0c;…

LFU算法族:LFU算法

LFU算法族相关文章目录汇总&#xff1a; LFU算法&#xff08;本文&#xff09;​​​​​​​ LFU-Aging算法 window-LFU算法 1、原理 LFU&#xff08;Least Frequently Used&#xff09;算法&#xff0c;即最少访问算法&#xff0c;根据访问缓存的历史频率来淘汰数据&…

LFU算法详解

LFU算法&#xff1a;least frequently used&#xff0c;最近最不经常使用算法 对于每个条目&#xff0c;维护其使用次数 cnt、最近使用时间 time。 cache容量为 n&#xff0c;即最多存储n个条目。 那么当我需要插入新条目并且cache已经满了的时候&#xff0c;需要删除一个之…

算法题就像搭乐高:手把手带你拆解 LFU 算法

f学算法认准labuladong 东哥带你手把手撕力扣???? 点击下方卡片即可搜索???? PS&#xff1a;以后每篇文章最后&#xff0c;labuladong 都会推荐一些自己学过的优质技术专栏&#xff0c;供读者参考。 上篇文章 算法题就像搭乐高&#xff1a;手把手带你拆解 LRU 算法 写了…

LRU LFU 概念、底层原理及其实现 超详细~

0. 前置提要 本篇约为8650字&#xff0c;阅读完需要约40~60分钟。主要介绍页面置换算法,LRU和LFU的原理及其实现&#xff0c;对应leetcode140和460&#xff0c;如果能给个赞就更好了^-^。 1.从内存置换算法说起 计算机的运行的程序和数据保存在内存中&#xff0c;内存的空间是有…

如何实现LFU缓存(最近最少频率使用)

目录 1.什么是LFU缓存&#xff1f; 2.LFU的使用场景有哪些&#xff1f; 3.LFU缓存的实现方式有哪些&#xff1f; 4.put/get 函数实现具体功能 1.什么是LFU缓存&#xff1f; LFU缓存是一个具有指定大小的缓存&#xff0c;随着添加元素的增加&#xff0c;达到容量的上限&…

LFU缓存策略算法

在之前的文章中&#xff0c;我们介绍了如何设计一个LRU算法–如何设计LRU Cache算法&#xff0c;今天我们再聊一聊另一种缓存策略LFU。目前博主个人博客已经搭建发布&#xff0c;后期相关文章也会发布在上面&#xff0c;大家有兴趣可以去上面学习&#xff0c;点击即可前往文青乐…

国内编程学习网站

在本文中&#xff0c;我们介绍了来自两岸三地的编程学习网站&#xff0c;通过它们&#xff0c;不仅可以一窥国内App开发的发展现状&#xff0c;而且这些网站各有特点&#xff0c;无论是主打游戏学习还是视频学习&#xff0c;对于想要自学的开发者而言&#xff0c;都是个好去处。…