redis的LRU算法(二)
前文再续,书接上一回。上次讲到redis的LRU算法,文章实在精妙,最近可能有机会用到其中的技巧,顺便将下半部翻译出来,实现的时候参考下。
搏击俱乐部的第一法则:用裸眼观测你的算法
Redis2.8的LRU实现已经上线了,在不同的负载环境下经过测试,用户没有抱怨Redis的清理机制。为了继续改进,我希望能观察到算法的性能,同时不会浪费大量CPU,不增加1比特空间占用。
我设计了一个测试用例。导入指定数量的key,然后顺序访问他们,好让他们的最近访问时间顺序递减。再添加50%的key,那么之前的key有50%就会被淘汰掉。理想情况下,被淘汰的应该是前50%的。如下图:
绿色的是新添加的key,灰色的是第一次添加的key,白色表示被移除的。
LRU v2:不要丢掉重要信息
当采用了N-key取样时,默认会建立16个key的池,将里面的key按空闲时间排序。新key只会在池不满或者空闲时间大于池里最小的,才能进池。
这个实现极大的提升了性能,实现又简单,没有大bug。只要一点点性能监控和一些memmove()就完成了。
同时,新的redis-cli模式(-lru-test)支持测试LRU精度,可以更接近真实的负载来看新算法的工况,尝试不同算法的时候,至少可以发现明显的速度退化。
最近最少访问(LFU)
我最近又部分重构了Redis cache的换页算法。这些工作源于一个issue:当你在Redis 3.2有多个数据库的时候,算法总是做局部选择。比如DB 0的所有key都用的很频繁,DB 1的所有key都用的很少。Redis会从每个DB里丢弃一个key。理性的选择应该是先丢弃DB 1的key,丢完以后再丢DB 0的
Redis用作cache的时候,通常不会跟不同DB混合用,但我还是开始着手改进,最后将db的id包括在池里,然后所有DB都共用一个池,这个实现比原始先快20%
这次改进激起了我对Redis这块子系统的好奇心。我花了好些天进行优化,如果我用一个大点的池子,会好点吗?如果选择key的时候考虑了流逝的时间,效果会不会更好?
最后,我终于明白到,LRU算法会受到取样数量限制,只要数量足够,效果就很好,很难再改进。正如上图所示,每次取样10个键,已经和理论上的LRU几乎一样准确了。
因为原始算法难以改进,我开始想其他办法。回顾前文,其实我们真正想要的,是保留未来最有可能访问的key,即是最常访问的key,而不是最新访问的key。这就是LFU算法。理论上LFU的实现很简单,只要给每个key挂一个计数器,我们就可以知道给定的key是不是比另一个key访问更多了。
当然,LFU的实现上有几个通用的难点:
1. LFU里没法使用链表法转移到头部的技巧了。因为完美LFU需要key严格按访问量排序。当访问量一致时,排序算法可能劣化为O(N),即使计数器只变了一点点
2. LFU没法简单的只在访问时对计数器加一。因为访问模式会随着时间发生变化,所以一个高分的key需要随着时间流逝而分数递减。
在Redis里第一个问题不是问题,我们可以沿用LRU的随机取样方法。第二个问题仍然存在,我们需要一个方法来递减分数,或者随着时间流逝将计数器折半。
24bit空间实现的LFU
在Redis里,我们可以用的就是LRU的24bit空间,需要一些奇技淫巧来实现。
在24bit空间里,需要塞下:
1. 某种类型的访问计数器
2. 足够的信息来决定何时折半计数器
我的解决方案如下:
bits bits
+----------------+--------+
+ Last decr time | LOG_C |
+----------------+--------+
8bit用来计数,16bit用来记录上次递减的时间
你可能会认为,8bit计数器很快就会溢出了吧?这就是技巧所在:我用的是对数计数器。具体代码如下:
uint8_t LFULogIncr(uint8_t counter) {
if (counter == ) return ;
double r = (double)rand()/RAND_MAX;
double baseval = counter - LFU_INIT_VAL;
if (baseval < ) baseval = ;
double p = 1.0/(baseval*server.lfu_log_factor+);
if (r < p) counter++;
return counter;
}
计数器的值越大,真正加一的概率越小。上述代码算出一个概率p,介乎0到1之间,计数器越大,p越小。然后生成0-1之间的随机数r,只有r<p的时候,计数器才会加一。
现在我们来看看计数器折半的问题。转成分钟为单位的unix时间,低16位会存在上面保留的16位空间内。当Redis进行随机取样,扫描key空间的时候,所有遇到的key都会被检查是否应该递减。如果上次递减实在N分钟之前(N是可配置的),并且计数器的值是高分值,那计数器就会被折半。如果计数器是低分值,则只会递减。(希望我们可以更好的分辨少访问量的key,因为我们的计数器精度比较低)
还有一个问题,新的key需要一个生存的机会。Redis里新key会从5分开始。上面的递减算法已经考虑到这个分数,如果key分数低于5分,更容易被丢弃(一般是长时间没访问的非活跃key)。
redis的LRU算法(二)的更多相关文章
- redis的LRU算法(一)
最近加班比较累,完全不想写作了.. 刚看到一篇有趣的文章,是redis的作者antirez对redis的LRU算法的回顾.LRU算法是Least Recently Used的意思,将最近最少使用的资源 ...
- Redis的LRU算法
Redis的LRU算法 LRU算法背后的的思想在计算机科学中无处不在,它与程序的"局部性原理"很相似.在生产环境中,虽然有Redis内存使用告警,但是了解一下Redis的缓存使用策 ...
- 【Redis 设置Redis使用LRU算法】
转自:http://ifeve.com/redis-lru/ 本文将介绍Redis在生产环境中使用的Redis的LRU策略,以及自己动手实现的LRU算法(php) 1.设置Redis使用LRU算法 L ...
- Redis内存回收:LRU算法
Redis技术交流群481804090 Redis:https://github.com/zwjlpeng/Redis_Deep_Read Redis中采用两种算法进行内存回收,引用计数算法以及LRU ...
- Redis 为何使用近似 LRU 算法淘汰数据,而不是真实 LRU?
在<Redis 数据缓存满了怎么办?>我们知道 Redis 缓存满了之后能通过淘汰策略删除数据腾出空间给新数据. 淘汰策略如下所示: 设置过期时间的 key volatile-ttl.vo ...
- Redis - 作为 LRU 缓存
一.简介 LRU 实际上是被唯一支持的数据移除方法,同时也是 memcached 默认支持的缓存算法. 二.配置内存大小 在 redis.conf 文件中使用 maxmemory 指令能够配置内存大小 ...
- Redis学习笔记2-使用 Redis 作为 LRU 缓存
当 Redis 作为缓存使用时,当你添加新的数据时,有时候很方便使 Redis 自动回收老的数据.LRU 实际上是被唯一支持的数据移除方法.Redis 的 maxmemory 指令,用于限制内存使用到 ...
- Redis作为lru缓存作用
当 Redis 作为缓存使用时,当你添加新的数据时,有时候很方便使 Redis 自动回收老的数据.LRU 实际上是被唯一支持的数据移除方法.Redis 的 maxmemory 指令,用于限制内存使用到 ...
- 用redis当作LRU缓存
原文地址:https://redis.io/topics/lru-cache Redis可以用来作缓存,他可以很方便的淘汰(删除)旧数据添加新数据,类似memcached.LRU只是其中的一种置换算法 ...
随机推荐
- [LeetCode] 26. Remove Duplicates from Sorted Array ☆(从有序数组中删除重复项)
[LeetCode] Remove Duplicates from Sorted Array 有序数组中去除重复项 描述 Given a sorted array nums, remove the d ...
- crontab 每分钟、每小时、每天、每周、每月、每年执行
每分钟执行 * * * * * 每小时执行 0 * * * * 每天执行 0 0 * * * 每周执行 0 0 * * 0 每月执行 0 0 1 * * 每年执行 0 0 1 1 * 每小时的第3和第 ...
- 【vue学习】vue 2.0版本以上创建项目的的步骤
一.环境准备 1.vue项目依赖 node.js npm,需要先安装node和npm,先检查本地是否安装node.npm 快捷键win+r 输入cmd 弹出操作框,如果电脑已经安装git,直接右 ...
- Myeclipse和 eclipse中的控制台汉字横着显示修改
Myeclipse和 eclipse中的控制台汉字横着显示问题的修改 如图: 同一种字体有两种显示方式,比如微软雅黑和@微软雅黑,前一种汉字是竖着显示,后一种汉字是横着显示. 修改方法: prefer ...
- http短连接与长连接简介
1.HTTP协议与TCP/IP协议的关系 HTTP的长连接和短连接本质上是TCP长连接和短连接.HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议.IP协议主要解决网络路由和寻址问题 ...
- Java代码调用服务器上的Shell脚本
Java代码调用服务器上的Shell脚本 这里主要是因为我们报表平台有用到用户手工录入的数据作为结果数据且需要纳入saiku去展示 如我们所知,saiku不会自动刷新,所以需要在数据更新接口中调用服务 ...
- C#Mvc4.0IIS部署(内网)
安装IIS(百度中搜索教程安装) 在Vs中将项目发布到文件夹 回到桌面,右键计算机=>管理=>服务和应用程序=>Internet信息服务(IIS)管理器 右键添加新网站,输入网站名称 ...
- 小程序获取openid 出现null,{"errcode":40163,"errmsg":"code been used, hints: [ req_id: WNUzlA0105th41 ]"}
//根据微信提供的接口,请求得到openid和session_id public class UserInfoUtils { private String getKeyURL="https: ...
- HFun.快速开发平台(四)=》自定义列表实例(请求参数的处理)
上编自定义列表描述了自定义列表的基本实现功能,本此记录列表的请求过程. 个人比较喜欢对参数进行对象化,方便后续人维护及查看,先上代码: /******************************* ...
- ng-repeat 中的track by
<div style="font-size:15px"ng-repeat="item in groups.items track by $index"&g ...