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只是其中的一种置换算法 ...
随机推荐
- Could not autowire. No beans of 'xxxx' type found的错误
在Idea的spring工程里,经常会遇到Could not autowire. No beans of 'xxxx' type found的错误提示.但程序的编译和运行都是没有问题的,这个错误提示并 ...
- 前端必备之Node+mysql+ejs模版如何写接口
前端必备之Node+mysql+ejs模版如何写接口 这星期公司要做一个视频的后台管理系统, 让我用Node+mysql+ejs配合写接口, 周末在家研究了一下, 趁还没来具体需求把研究内容在这里分享 ...
- Win10系列:C#应用控件进阶4
多边形 若要绘制多边形需要用到Polygon元素,并通过定义一系列的点绘制多边形.Polygon类型的对象有Points属性, 这个属性用来定义组成边的点集.在前台代码中,使用空格分隔各个点,然后利用 ...
- 操作日志的设计小结by大熊
一.首先由同事的操作日志说起 同事做了一个这样的操作日志,他定义系统所有发的json加入这两个字段,module和msg,然后在service里面用注解@Log拦截,即可记录对应的操作日志. { mo ...
- RockerMQ实战之快速入门
文章目录 RocketMQ 是什么 专业术语 Producer Producer Group Consumer Consumer Group Topic Message Tag Broker Name ...
- quartz任务调度框架与spring整合
Quartz是什么? Quartz 是一种功能丰富的,开放源码的作业调度库,可以在几乎任何Java应用程序集成 - 从最小的独立的应用程序到规模最大电子商务系统.Quartz可以用来创建简单或复杂的日 ...
- python基础之作业3----三级菜单小练习
data = { "华为技术":{ "产品与解决方案":{ "云核心网":{"云核心网研发管理部","云核心网 ...
- fastJson遇到的问题
概述 现在的代码开发中,json这种数据类型使用的是越来越多,因为它的存取速度都比较快,而且,使用起来非常的简单,今天工作的时候,我就遇到了一个关于json的生产问题,这个问题我之前确实还没有注意过, ...
- while循环与 for循环,函数定义与调用
import turtle turtle.setup(600,400,0,0) turtle.bgcolor('red') turtle.color('yellow') turtle.fillcolo ...
- clusterware启动顺序——CRSD
CRSD层面 1.启动过程 )导致">CRSD无法启动集群的应用程序资源的可能原因有:"> 原因:/etc/oracle/ocr.loc指向了错误的OCR文件 [gri ...