Redis LFU缓存淘汰算法

前言

Redis 在 4.0 版本之前的缓存淘汰算法,只支持 random 和 lru。random 太简单粗暴了,可能把热点数据给淘汰掉,一般不会使用。lru 比 random 好一点,会优先淘汰最久没被访问的数据,但是它也有一个缺点,就是无法真正表示数据的冷热程度。
如下示例,A 之前被频繁访问,B 在执行 LRU 淘汰前恰巧被访问了一次,记录了最新的时间戳。此时触发 LRU 淘汰算法,反而会把 A 给淘汰掉。但事实是,A 的热度明显比 B 高。
image.png
针对这个问题,Redis 终于在 4.0 版本推出了全新的缓存淘汰策略:LFU。
LFU(Least Frequently Used)也叫 最不频繁使用 算法,它会把访问频率最低的数据给淘汰掉。举个例子:A 十分钟内访问了十次,B 十分钟内访问了五次,哪怕 B 是最后一次被访问的,因为它访问频率低,所以会优先淘汰。
要想启用 LFU 淘汰策略,首先要配置最大内存:

maxmemory 100MB

然后配置淘汰策略:

maxmemory-policy volatile-lfu / allkeys-lfu

有俩选项,它俩区别是:

  • allkeys-lfu:针对所有键值对的 LFU 淘汰策略
  • volatile-lfu:仅针对设置了过期时间的键值对的 LFU 淘汰策略

这样就开启 LFU 淘汰策略了,但是,关于 LFU 还有两个配置你也要关心:

lfu-log-factor 10
lfu-decay-time 1

它们的作用分别是:

  • lfu-decay-time:LFU 计数器衰减的时间单位,默认一分钟
  • lfu-log-factor:LFU 计数器递增的系数,值越大,递增的难度越大

Redis LFU实现

和 LRU 一样,Redis 也没有提供严格的 LFU 实现,因为开销太大了,也是近似 LFU 算法。并且,LFU 算法复用了 LRU 算法用到的 RedisObject 里的 lru 字段。Redis 会根据不同的淘汰策略写入不同的值。

LRU 算法和 LFU 算法不能同时启用

我们知道,Redis 会为键值对创建 RedisObject 对象,并在对象里用 24 Bit 来记录 lru 字段。如果配置的是 LFU 淘汰算法,则写入的是 LFU 相关的信息。
要实现 LFU 算法需要统计两个维度的数据,才能计算访问频率:

  • 访问的次数
  • 访问的时间

但是 Redis 只有一个 lru 字段能用,不得已而为之,Redis 不得不把 lru 拆分成两部分:高 16 位记录时间戳,低 8 位记录计数器。
Redis 先获取 LFU 时间戳,然后左移 8 位,低 8 位用来保存计数器,计数器的默认值是 5。

#define LFU_INIT_VAL 5
robj *createObject(int type, void *ptr) {robj *o = zmalloc(sizeof(*o));o->type = type;o->encoding = OBJ_ENCODING_RAW;o->ptr = ptr;o->refcount = 1;if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {// 当前分钟级时间戳保留低16位 | 默认计数器的值5o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;} else {// 写入LRU时间戳o->lru = LRU_CLOCK();}return o;
}

LFU 时间戳是以 分钟 为单位的,因为只有 16 位,所以最多能表示 65535 分钟,约 45 天,超过这个时间就溢出了。

unsigned long LFUGetTimeInMinutes(void) {return (server.unixtime/60) & 65535;
}

之后每次访问 Key 都会修改 LFU 信息,方法是updateLFU() 。Redis 首先会计算衰减后的计数器值,再判断要不要递增,最后把新值重新写会对象。

void updateLFU(robj *val) {// 衰减并返回计数器unsigned long counter = LFUDecrAndReturn(val);// 根据规则递增计数器 不一定会递增counter = LFULogIncr(counter);// 重新写入时间戳和计数器val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}

衰减并返回计数器的方法是LFUDecrAndReturn() ,为什么计数器还要衰减呢?
因为 LFU 是按照访问频率的高低来淘汰缓存的,当缓存太久没被访问,它的计数器就应该被衰减,这样才能真实反映数据的热度,如果不衰减,不就变回 LRU 算法了嘛。
衰减的策略很简单,计算缓存闲置的以分钟为单位的时间,除以配置的lfu_decay_time 衰减时间单位,结果就是要衰减的值,计数器最多减到 0。默认衰减时间单位是一分钟,例如一小时没被访问,计数器就会衰减 60。

unsigned long LFUDecrAndReturn(robj *o) {// 时间戳unsigned long ldt = o->lru >> 8;// 计数器unsigned long counter = o->lru & 255;// 计算衰减值 lfu_decay_time:衰减时间单位,默认1分钟 闲置时间/lfu_decay_timeunsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;// 返回衰减后的计数器if (num_periods)counter = (num_periods > counter) ? 0 : counter - num_periods;return counter;
}

计数器衰减以后,Redis 接下来要开始对计数器做递增操作了,但是不会轻易递增计数器。
为什么不是每次访问都递增计数器呢?你别忘了,计数器只有 8 Bit,最多也只能表示 255,如果每次访问 Key 都直接递增计数器,很容易一下就打满了,当所有 Key 的计数器都打满了,计数器就没有意义了,无法判断数据的热度了,因为大家都一样热。
所以,Redis 会有一套递增规则,方法是LFULogIncr() 。首先会取一个随机数,然后计算计数器与初始值的一个差值baseval ,用这个差值乘以一个递增系数lfu_log_factor +1 再求倒数记为阈值 p,只有当随机数小于阈值才会递增计数器。

uint8_t LFULogIncr(uint8_t counter) {if (counter == 255) return 255;// 获取随机数double r = (double)rand()/RAND_MAX;// 取一个基数,用来算更新阈值double baseval = counter - LFU_INIT_VAL;if (baseval < 0) baseval = 0;/*** 随机数小于阈值,计数器则加1,否则什么也不做* 1.counter越大,计数器增加的概率就越低* 2.lfu_log_factor越大,计数器增加的概率就越低 人为控制*/double p = 1.0/(baseval*server.lfu_log_factor+1);if (r < p) counter++;return counter;
}

通过源码发现,随着计数器 counter 不断增大,递增的难度也随之增大,也就是越往后越难递增,这样就可以有效避免计数器很快被打满。
同时,Redis 还可以通过配置lfu_log_factor 递增系数,人为控制计数器递增的难度,值越大越难递增。默认系数是 10,大约命中 100 次,计数器加 10;命中 1000 次,计数器加 18。如下是不同系数下,命中率和计数器递增的关系。

+--------+------------+------------+------------+------------+------------+
| factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |
+--------+------------+------------+------------+------------+------------+
| 0      | 104        | 255        | 255        | 255        | 255        |
+--------+------------+------------+------------+------------+------------+
| 1      | 18         | 49         | 255        | 255        | 255        |
+--------+------------+------------+------------+------------+------------+
| 10     | 10         | 18         | 142        | 255        | 255        |
+--------+------------+------------+------------+------------+------------+
| 100    | 8          | 11         | 49         | 143        | 255        |
+--------+------------+------------+------------+------------+------------+

在缓存淘汰的处理上,LFU 和 LRU 的逻辑基本是一致的,Redis 会遍历数据库,然后随机采样一批 Key,衰减并返回计数器的值,以便更加真实的反映缓存的热度,然后按照计数器的值升序填充 EvictionPoolLRU 数组记为候选淘汰的 Key,然后优先淘汰计数器较小的 Key。

尾巴

Redis 支持三类缓存淘汰策略,分别是:随机、LRU 和 LFU。随机太简单粗暴,容易淘汰热数据,一般不用。LRU 只记录访问最新的时间戳,不能真实地反映数据的热度,所以也不能很好的淘汰掉真正冷的数据。LFU 是 LRU 的改进版本,它会按照数据的访问频率来淘汰数据,可以更真实的反映数据的热度。因为底层是复用的 lru 字段,所以 LFU 会把 lru 字段拆分成两部分,高 16 位记录访问时间戳,单位是分钟;低 8 位 记录计数器。因为记录的维度是访问频率,所以 Redis 会对计数器做衰减操作,越久不访问计数器衰减的越严重。同时,因为只有 8 位空间,最多能表示 255,所以 Redis 对计数器的递增策略采用对数递增的方式,不然每次访问都直接递增,计数器很容易一下被打满。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十五章 I/O输入输出

15,1输入输出流 流是一组有序的数据序列&#xff0c;根据操作的类型&#xff0c;可分为输入流和输出流两种。I/O(Input/Output,(输出)流提供了一条通道程序&#xff0c;可以使用这条通道把源中的字节序列送到目的地。虽然 I/O 流疆盘文件存取有关&#xff0c;但是程序的源和目的…

毅速科普课堂丨3D打印随形水路模具制造的一般流程

随形水路模具因其能大幅度提升冷却效率、缩短冷却时间、提升产品良率、提高生产效率的特点受到广泛应用&#xff0c;通常一件3D打印随形水路模具的制造需要经过多个步骤&#xff0c;包括设计、打印、后处理等多个环节&#xff0c;以确保模具的质量和性能符合预期需求。 首先&am…

搭建哨兵架构(windows)

参考文章&#xff1a;Windows CMD常用命令大全&#xff08;值得收藏&#xff09;_cmd命令-CSDN博客 搭建哨兵架构&#xff1a;redis-server.exe sentinel.conf --sentinel 1.在主节点上创建哨兵配置 - 在Master对应redis.conf同目录下新建sentinel.conf文件&#xff0c;名字绝…

PAM从入门到精通(十七)

接前一篇文章&#xff1a;PAM从入门到精通&#xff08;十六&#xff09; 本文参考&#xff1a; 《The Linux-PAM Application Developers Guide》 PAM 的应用开发和内部实现源码分析 先再来重温一下PAM系统架构&#xff1a; 更加形象的形式&#xff1a; 六、整体流程示例 2.…

【FPGA零基础学习之旅#15】串口接收模块设计与验证(工业环境)

&#x1f389;欢迎来到FPGA专栏~串口接收模块设计与验证&#xff08;工业环境&#xff09; ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如…

[云原生1] Docker网络模式的详细介绍

1. Docker 网络 1.1 Docker 网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c; Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c; 同时Docker网桥是每个容器的默认…

Python 框架学习 Django篇 (五) Session与Token认证

我们前面经过数据库的学习已经基本了解了怎么接受前端发过来的请求&#xff0c;并处理后返回数据实现了一个基本的登录登出效果&#xff0c;但是存在一个问题&#xff0c;我们是将所有的请求都直接处理了&#xff0c;并没有去检查是否为已经登录的管理员发送的&#xff0c;如果…

代码随想录算法训练营第二十九天丨 回溯算法part06

回溯总结 对于回溯算法&#xff0c;我们需要知道的是 回溯是递归的副产品&#xff0c;只要有递归就会有回溯&#xff0c;所有回溯法常与二叉树遍历【前中后序遍历】&#xff0c;深搜混在一起&#xff0c;原因是都涉及到的递归。 回溯法 暴力搜索&#xff0c;它的效率并不高&…

从北京到南京:偶数在能源行业的数据迁移实践

能源行业的数字化转型 当前&#xff0c;大数据技术在以电力为代表的能源行业不断推进&#xff0c;同时&#xff0c;分布式能源、储能、电网技术不断改进&#xff0c;电力行业的数字化转型充满了机遇和挑战。 一方面&#xff0c;电力行业本身自动化程度高、信息化基础好、系统…

文件夹图片相似图片检测并删除相似图片

项目开源地址 pip install imagededupgit clone https://github.com/idealo/imagededup.git cd imagededup pip install "cython>0.29" python setup.py installQuick Start from imagededup.methods import PHash phasher PHash()# Generate encodings for all…

macOS Tor 在启动期间退出

macos Tor 在启动期间退出。这可能是您的 torrc 文件存在错误&#xff0c;或者 Tor 或您的系统中的其他程序存在问题&#xff0c;或者硬件问题。在您解决此问题并重新启动 Tor 前&#xff0c;Tor 浏览器将不会启动。退出Tor、卸载Tor、删除目录 TorBrowser-Data、重启电脑 访…

系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第二部分:CI CD、设计模式、数据库

本心、输入输出、结果 文章目录 系统设计 - 我们如何通俗的理解那些技术的运行原理 - 第二部分&#xff1a;CI CD、设计模式、数据库前言CI/CD第 1 部分 - 带有 CI/CD 的 SDLC第 2 部分 - CI 和 CD 之间的区别第 3 部分 - CI/CD 管道 Netflix Tech Stack &#xff08;CI/CD Pip…

在模拟冷藏牛肉加工条件下,冷和酸对荧光假单胞菌和单核细胞增生李斯特菌双菌种生物膜的综合影响

1.1 Title&#xff1a;Combined effects of cold and acid on dual-species biofilms of Pseudomonas fluorescens and Listeria monocytogenes under simulated chilled beef processing conditions 1.2 分区/影响因子&#xff1a;Q1/5.3 1.3 作者&#xff1a;Zhou Guanghui…

哪个牌子的台灯对孩子的视力好?对孩子视力好的台灯推荐分享

现在市面上台灯品牌众多&#xff0c;价格不一&#xff0c;品质更是参差不齐&#xff0c;所以要学会如何选择适合孩子的台灯。光源质量是重要因素&#xff0c;光源是直接影响到孩子的视力&#xff0c; 一般来说&#xff0c;光源质量主要看照度、亮度和均匀度、显色指数等&#x…

大语言模型在推荐系统的实践应用

本文从应用视角出发&#xff0c;尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数)&#xff0c;训练和推理的时间、空间开销较小&#xff0c;也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…

softplc windows 安装测试

下载 .NET 6.0 (Linux、macOS 和 Windows) 安装后 在这里 The command could not be loaded, possibly because: * You intended to execute a .NET application: The application restore does not exist. * You intended to execute a .NET SDK command: No…

海外版知乎Quora,如何使用Quora进行营销?

想必大家对知乎非常熟悉&#xff0c;而Quora作为海外最受欢迎的网站之一&#xff0c;是与知乎功能与性质非常相似的一个平台&#xff0c;靠回答别人的问题获得关注&#xff0c;是引流最快的一个平台。对于做跨境电商、独立站的商家来说&#xff0c;这是一个绝佳的免费引流广告工…

小程序canvas层级过高真机遮挡组件的解决办法

文章目录 问题发现真机调试问题分析问题解决改造代码效果展示 问题发现 在小程序开发中需要上传图片进行裁剪&#xff0c;在实际真机调试中发现canvas层遮挡住了生成图片的按钮。 问题代码 <import src"../we-cropper/we-cropper.wxml"></import> <…

2023.10.19 关于 单例模式 详解

目录 引言 单例模式 饿汉模式 懒汉模式 懒汉模式线程安全问题 分析原因 引言 设计模式为编写代码的 约定 和 规范 阅读下面文章前建议点击下方链接明白 对象 和 类对象 对象和类对象 单例模式 单个实例&#xff08;对象&#xff09;在某些场景中有特定的类&#xff0c;…

IntelliJ IDEA 2020.2.1白票安装使用方法

先安装好idear Plugins 内手动添加第三方插件仓库地址&#xff1a;https://plugins.zhile.io 搜索&#xff1a;IDE Eval Reset插件进行安装 输入https://plugins.zhile.io 手动安装离线插件方法 安装包可以去笔者的CSDN资源库下载 安装mybaties插件