Redis的LRU算法

Redis的LRU算法

LRU算法背后的的思想在计算机科学中无处不在,它与程序的"局部性原理"很相似。在生产环境中,虽然有Redis内存使用告警,但是了解一下Redis的缓存使用策略还是很有好处的。下面是生产环境下Redis使用策略:最大可用内存限制为4GB,采用 allkeys-lru 删除策略。所谓删除策略:当redis使用已经达到了最大内存,比如4GB时,如果这时候再往redis里面添加新的Key,那么Redis将选择一个Key删除。那如何选择合适的Key删除呢?

CONFIG GET maxmemory
1) "maxmemory"
2) "4294967296"

CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"

在官方文档Using Redis as an LRU cache描述中,提供了好几种删除策略,比如 allkeys-lru、volatile-lru等。在我看来按选择时考虑三个因素:随机、Key最近被访问的时间 、Key的过期时间(TTL)

比如:allkeys-lru,"统计一下所有的"Key历史访问的时间,把"最老"的那个Key移除。注意:我这里加了引号,其实在redis的具体实现中,要统计所有的Key的最近访问时间代价是很大的。想想,如何做到呢?

evict keys by trying to remove the less recently used (LRU) keys first, in order to make space for the new data added.

再比如:allkeys-random,就是随机选择一个Key,将之移除。

evict keys randomly in order to make space for the new data added.

再比如:volatile-lru,它只移除那些使用 expire 命令设置了过期时间的Key,根据LRU策略来移除。

evict keys by trying to remove the less recently used (LRU) keys first, but only among keys that have an expire set, in order to make space for the new data added.

再比如:volatile-ttl,它只移除那些使用 expire 命令设置了过期时间的Key,哪个Key的 存活时间(TTL KEY 越小)越短,就优先移除。

evict keys with an expire set, and try to evict keys with a shorter time to live (TTL) first, in order to make space for the new data added.

既然有这么多策略,那我用哪个好呢?这就涉及到Redis中的Key的访问模式了(access-pattern),access-pattern与代码业务逻辑相关,比如说符合某种特征的Key经常被访问,而另一些Key却不怎么用到。如果所有的Key都可能机会均等地被我们的应用程序访问,那它的访问模式服从均匀分布;而大部分情况下,访问模式服从幂指分布(power-law distribution),另外Key的访问模式也有可能是随着时间变化的,因此需要一种合适的删除策略能够catch 住 (捕获住)各种情形。而在幂指分布下,LRU是一种很好的策略:

While caches can’t predict the future, they can reason in the following way: keys that are likely to be requested again are keys that were recently requested often. Since usually access patterns don’t change very suddenly, this is an effective strategy.

Redis中LRU策略的实现

最直观的想法:LRU啊,记录下每个key 最近一次的访问时间(比如unix timestamp),unix timestamp最小的Key,就是最近未使用的,把这个Key移除。看下来一个HashMap就能搞定啊。是的,但是首先需要存储每个Key和它的timestamp。其次,还要比较timestamp得出最小值。代价很大,不现实啊。

第二种方法:换个角度,不记录具体的访问时间点(unix timestamp),而是记录idle time:idle time越小,意味着是最近被访问的。

The LRU algorithm evicts the Least Recently Used key, which means the one with the greatest idle time.

比如A、B、C、D四个Key,A每5s访问一次,B每2s访问一次,C和D每10s访问一次。(一个波浪号代表1s),从上图中可看出:A的空闲时间是2s,B的idle time是1s,C的idle time是5s,D刚刚访问了所以idle time是0s

这里,用一个双向链表(linkedlist)把所有的Key链表起来,如果一个Key被访问了,将就这个Key移到链表的表头,而要移除Key时,直接从表尾移除。

It is simple to implement because all we need to do is to track the last time a given key was accessed, or sometimes this is not even needed: we may just have all the objects we want to evict linked in a linked list.

但是在redis中,并没有采用这种方式实现,它嫌LinkedList占用的空间太大了。

By modifying a bit the Redis Object structure I was able to make 24 bits of space. There was no room for linking the objects in a linked list (fat pointers!)

毕竟,并不需要一个完全准确的LRU算法,就算移除了一个最近访问过的Key,影响也不太。

To add another data structure to take this metadata was not an option, however since LRU is itself an approximation of what we want to achieve, what about approximating LRU itself?

最初Redis是这样实现的:

随机选三个Key,把idle time最大的那个Key移除。后来,把3改成可配置的一个参数,默认为N=5:maxmemory-samples 5

when there is to evict a key, select 3 random keys, and evict the one with the highest idle time

就是这么简单,简单得让人不敢相信了,而且十分有效。但它还是有缺点的,但它还是有缺点的:每次随机选择的时候,并没有利用历史信息。在每一轮移除(evict)一个Key时,随机从N个里面选一个Key,移除idle time最大的那个Key;下一轮又是随机从N个里面选一个Key...有没有想过:在上一轮移除Key的过程中,其实是知道了N个Key的idle time的情况的,那我能不能在下一轮移除Key时,利用好上一轮知晓的一些信息?

However if you think at this algorithm across its executions, you can see how we are trashing a lot of interesting data. Maybe when sampling the N keys, we encounter a lot of good candidates, but we then just evict the best, and start from scratch again the next cycle.

start from scratch太傻了。于是Redis又做出了改进:采用缓冲池(pooling)

当每一轮移除Key时,拿到了这个N个Key的idle time,如果它的idle time比 pool 里面的 Key的idle time还要大,就把它添加到pool里面去。这样一来,每次移除的Key并不仅仅是随机选择的N个Key里面最大的,而且还是pool里面idle time最大的,并且:pool 里面的Key是经过多轮比较筛选的,它的idle time 在概率上比随机获取的Key的idle time要大,可以这么理解:pool 里面的Key 保留了"历史经验信息"。

Basically when the N keys sampling was performed, it was used to populate a larger pool of keys (just 16 keys by default). This pool has the keys sorted by idle time, so new keys only enter the pool when they have an idle time greater than one key in the pool or when there is empty space in the pool.

至此,基于LRU的移除策略就分析完了。Redis里面还有一种基于LFU(访问频率)的移除策略,后面有时间再说。

参考链接:

最近,在生产环境上对某一微服务部署了多份,多个client并发访问同一份数据,造成了数据的重复计算,需要分布式锁解决这个问题。看了下redis 的 transaction documention 以及redis distlock,在结尾发现《design data intensive applications》的作者写了一篇文章 how-to-do-distributed-locking分析了使用redis实现分布式锁的缺陷:严重依赖于计算机时钟的"准确性",而在分布式环境下,本地时钟是不可靠的,因此使用redis作为分布式锁在某些条件下(比如网络延时)违反了锁的一个性质:安全性。作者给出的解决方案是每次修改数据时比较 fencing token,这种使用 fencing token 的思路非常类似于MySQL里面的"乐观锁"实现方式,即:都是基于"数据版本号"判断是否发生了冲突。此外,martin kleppmann 还建议如果是使用分布式锁保证安全性,那么最好使用基于Zookeeper实现分布式锁,由zookeeper的高可用性保证分布式锁的可用性。
有趣的是,redlock的作者也写了一篇文章 Is Redlock safe?作为回应martin kleppmann的质疑,读完之后相信对分布式锁会有一个更好的理解。不禁感叹读官方文档、英文原文可以极大提高理解技术本质的效率,一篇好的文章真的能节约很多时间。好了,不说了,说多了我就怀疑自己写的东西了^~^。

最后,再扯一点与本文不相关的东西:关于数据处理的一些思考:
5G有能力让各种各样的节点都接入网络,就会产生大量的数据,如何高效地处理这些数据、挖掘数据(机器学习、深度学习)背后的隐藏的模式是一件非常有趣的事情,因为这些数据其实是人类行为的客观反映。举个例子,微信运动的计步功能,会让你发现:哎,原来每天我的活动量大概保持在1万步左右。再深入一步,以后各种工业设备、家用电器,都会产生各种各样的数据,这些数据是社会商业活动的体现,也是人类活动的体现。如何合理地利用这些数据呢?现有的存储系统能支撑这些数据的存储吗?有没有一套高效的计算系统(spark or tensorflow...)分析出数据中隐藏的价值?另外,基于这些数据训练出来的算法模型应用之后会不会带来偏见?有没有滥用数据?网上买东西有算法给你推荐,刷朋友圈会给你投针对性的广告,你需要借多少钱,贷多少款也会有模型评估,看新闻、看娱乐八卦也有算法推荐。你每天看到的东西、接触的东西,有很多很多都是这些"数据系统"做的决策,它们真的公平吗?在现实中如果你犯了错,有老师、朋友、家人给你反馈、纠正,你改了,一切还可以从头开始。可在这些数据系统之下呢?有纠错反馈机制吗?会不会刺激人越陷越深?就算你很正直、有良好的信用,算法模型也可能存在概率偏差,这是不是影响人类"公平竞争"的机会?这些都是数据开发人员值得思考的问题。

原文:https://www.cnblogs.com/hapjin/p/10933405.html

原文地址:https://www.cnblogs.com/hapjin/p/10933405.html

时间: 2024-09-29 16:09:08

Redis的LRU算法的相关文章

redis的LRU算法(一)

最近加班比较累,完全不想写作了.. 刚看到一篇有趣的文章,是redis的作者antirez对redis的LRU算法的回顾.LRU算法是Least Recently Used的意思,将最近最少使用的资源丢掉.Redis经常被用作cache,如果能够将不常用的key移除,尽量保留常用的,那内存的利用率就相当高了.当然,LRU也有弱点,考虑下面一种情况: ~~~~~A~~~~~A~~~~~A~~~~A~~~~~A~~~~~A~~| ~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~B~~

redis的LRU算法(二)

前文再续,书接上一回.上次讲到redis的LRU算法,文章实在精妙,最近可能有机会用到其中的技巧,顺便将下半部翻译出来,实现的时候参考下. 搏击俱乐部的第一法则:用裸眼观测你的算法 Redis2.8的LRU实现已经上线了,在不同的负载环境下经过测试,用户没有抱怨Redis的清理机制.为了继续改进,我希望能观察到算法的性能,同时不会浪费大量CPU,不增加1比特空间占用. 我设计了一个测试用例.导入指定数量的key,然后顺序访问他们,好让他们的最近访问时间顺序递减.再添加50%的key,那么之前的k

探究redis和memcached的 LRU算法--------redis的LRU的实现

一直对这redis和memcached的两个开源缓存系统的LRU算法感兴趣.今天就打算总结一下这两个LRU算法的实现和区别. 首先要知道什么是LRU算法:LRU是Least Recently Used 近期最少使用算法.相关的资料网上一大堆.http://en.wikipedia.org/wiki/Cache_algorithms#LRU   redis的六种策略 rewriteConfigEnumOption(state,"maxmemory-policy",server.maxme

LRU算法的Python实现

LRU:least recently used,最近最少使用算法.它的使用场景是:在有限的空间中存储对象时,当空间满时,会一定的原则删除原有的对象,常用的原则(算法)有LRU,FIFO,LFU等.在计算机的Cache硬件,以及主存到虚拟内存的页面置换,还有Redis缓存系统中都用到了该算法.我在一次面试和一个笔试时,都遇到过这个问题. LRU的算法是比较简单的,当对key进行访问时(一般有查询,更新,增加,在get()和set()两个方法中实现即可)时,将该key放到队列的最前端(或最后端)就行

Redis的LRU机制(转)

原文:Redis的LRU机制 在Redis中,如果设置的maxmemory,那就要配置key的回收机制参数maxmemory-policy,默认volatile-lru,参阅Redis作者的原博客:antirez weblog >> Redis as an LRU cache 原文中写得很清楚: Another way to use Redis as a cache is the maxmemory directive, a feature that allows specifying a m

关于LRU算法(转载)

原文地址: http://flychao88.iteye.com/blog/1977653 http://blog.csdn.net/cjfeii/article/details/47259519 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是"如果数据最近被访问过,那么将来被访问的几率也更高". 1.2.实现 最常见的实现是使用一个链表保存缓存数据,详细算法实现如下: 1. 新数据插入到链表头部: 2. 每当缓存命

redis(7)LRU缓存

一.LRU简介 LRU是Least Recently Used的缩写,即:最近最少使用. 它是内存管理中的一种页面置换算法,对于在内存中但是又不用的数据块,操作系统会根据哪些数据属于LRU而将其移除内存而腾出空间来加载另外的数据. 二.redis LRU 官方文章:https://redis.io/topics/lru-cache#using-redis-as-an-lru-cache redis常常被用作缓存,当有新的数据进来的时候会自动驱逐旧的数据.LRU是memcached 默认的驱逐策略

LRU算法原理解析

LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的. 现代操作系统提供了一种对主存的抽象概念虚拟内存,来对主存进行更好地管理.他将主存看成是一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在主存和磁盘之间来回传送数据.虚拟内存被组织为存放在磁盘上的N个连续的字节组成的数组,每个字节都有唯一的虚拟地址,作为到数组的索引.虚拟内存被分割为大小固定的数据块虚拟页(Virtual Page,VP),这些数据块作为主

Redis的逐出算法

Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足.如果内存不满足新加入数据的最低存储要求, redis要临时删除一些数据为当前指令清理存储空间.清理数据的策略称为逐出算法.注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行.当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息. (error) OOM command not allowed when used memory >'max