把Redis用作LRU缓存
当Redis被用作缓存时,当你添加新的数据时,通常可以很方便的使它自动的淘汰旧的数据。这个行为在开发者社区中很知名,因为它是流行的memcached系统中一个默认的行为。
LRU实际上只是支持淘汰的方法之一。本页覆盖了更多的Redismaxmemory
指令的通用话题,它用于限制内存使用总数为一个固定的值,并且也深入介绍了Redis中的LRU算法使用,实际上是精确的LRU算法的近似方法。
从Redis4.0版本开始,一个新的LFU(最不经常使用)淘汰政策被引入。它在本文档中一个独立的部分中被介绍。
最大内存配置指令
maxmemory
配置指令被用于配置Redis的数据集使用一个特定的内存总量。可以使用redis.conf
文件来配置指令,或者稍后使用CONFIG SET命令实时的配置。
例如,为了配置内存限制为100MB,可以在redis.conf
文件中使用下面的指令。
maxmemory 100mb
设置maxmemory
为0会导致没有内存限制。在64位系统中默认是没有限制的,但是在32位系统中使用的隐式内存限制是3GB。
当即将到指定的内存时,可以在不同的行为(策略)之间做出选择。对于那些可能会导致更多内存使用的命令Redis可以只返回错误,或者在每次添加新的数据时,可以淘汰一些旧的数据从而让内存回到一个特定的限制值。
回收策略
当maxmemory
限制到达时,Redis执行的准确的行为是使用maxmemory-policy
配置的指令。
下面的策略是可用的:
- 不回收:当到达限制的内存,客户端尝试执行可能会导致使用更多内存的命令(大部分是写命令,但是DEL和其他一些列外)时,返回错误。
- alleys-lru: 尝试先移除最近最少使用(LRU)的键来回收键,为新添加的数据制造内存空间。
- volatile-lru:尝试移除最近最少使用(LRU)的键来回收键,但是仅在有过期时间的键中,为新添加的数据制造内存空间。
- alleys-random: 随机回收键,为新添加的数据制造空间。
- volatile-random:随机回收键,为新添加的数据制造空间,但是仅淘汰拥有过期时间的键。
- volatile-ttl:使用过期时间淘汰键,并且尝试先淘汰具有较短生存期(TTL)的键,为新添加的数据创造空间。
如果没有匹配先决条件的键可以被回收时,策略 volatile-lru, volatile-random 和 volatile-ttl 的行为像noeviction。
挑选合适的回收策略是重要的,依赖于你的应用的访问模式,尽管你可以在应用运行时重新配置策略,并且使用Redis的INfO来监视缓存丢失和命中的数量以便于调整你的设置。
一般来说的经验法则:
- 当你期待受欢迎的请求是幂律分布时,也就是说,你期待元素的子集将会比其他子集被更频繁的访问时使用allkeys-lru策略。当你不确定时,这个一个好的选择。
- 如果你有一个所有的键被连续不断扫描的循环访问时,或者当你期待分布是统一时,使用allkeys-random策略(所有的元素以相同的概率被访问)。
- 如果想要通过在创建缓存对象时使用不同的TTL来提示Redis哪些是过期的候选者,那么使用volatile-ttl。
volatile-lru和volatile-random策略在你想使用一个单实例同时用于缓存和拥有一系列持久化键时非常有用。然而,运行两个Redis实例来解决这个问题通常是一个更好的主意。
值得注意的是,给一个键设置过期时间是需要占用内存的,因而使用一个类似于allkeys-lru的策略更有内存效率,因为在有内存压力时给一个键设置一个过期时间然后回收是不必要的。
回收进程如何工作
重要的是理解回收处理过程是这样运作的:
- 客户端运行了一个新的命令,导致更多的数据被添加。
- Redis检查内存使用量,如果超过了
maxmemory
限制,它就会依照策略回收键。 - 执行一个新的命令,重复以上。
因此我们持续的跨越内存限制的边界,通过超出它,然后通过回收键而回到限制以下。
如果一个键导致大量的内存被使用(就像一个大的集合交叉保存到一个新键上),在一段时间内内存限制可以被显著的超过。
近似LRU算法
Redis的LRU算法不是一个精确的实现。这意味着Redis不能够选出最佳候选人来回收,也就是,过去访问最多的访问权限。相反,它是尝试运行一个近似的LRU算法,通过取样一小部分键,然后回收样本中最合适(拥有最古老访问时间)的那一个。
然而,从Redis3.0版本开始算法已经被改进成候选人池供回收。这提高了算法的性能,使其能够更接近于真实LRU算法的行为。
Redis LRU算法的重要之处是,可以通过改变样本的数量来调整算法的精度,以检查每一次的回收。这个参数受下面的配置指令控制:
maxmemory-samples 5
Redis不用真正的LRU实现的原因是它会消耗更多的内存。然而,这种近似对于使用Redis的应用实际上是等价的。下面就是使用Redis LRU近似算法与使用真实LRU的对比图。
生成上述图像的测试用一个给定数字的键填充了一个Redis服务。这些键从第一个到最后一个被访问,因此,最早的键是LRU算法回收的最好的候选人。稍后,添加了超过50%的键,强迫超过一半的旧的键被回收。
你可以看到图像中有3种类型的点,组成3个明显的区域。
- 浅灰色的区域是被回收的对象。
- 灰色的区域是没有被回收的对象。
- 绿色的区域是被添加的对象。
在理论上LRU实现期待的是,在旧的键中,最早的那一半将会被过期。但Redis的LRU算法将仅有概率的过期旧的键。
如你所见,与Redis2.8相比,Redis3.0使用5个样本时做的要更好,但是大多数最新访问的对象仍然在Redis2.8中保留了下来。Redis3.0使用10个样本时的近似值已经非常接近于Redis3.0的理论上的性能。
注意,LRU仅是一个模型,用于预测一个给定的键将来被访问的可能性。此外,如果你的数据访问模式很接近于幂律,在集合中的键的大部分访问使用LRU近似算法将会处理的很好。
在模拟中我们发现使用幂律访问模式时,真LRU算法和Redis近似算法的差异非常小或者不存在。
然而,为了接近真的LRU,你可以提升样本量到10,这样会使用一点额外的CPU资源,然后检查缓存丢失率会有什么不同。
可以通过CONFIG SET maxmemory-samples <count>
命令,在生产环境中使用不同的样本值做实验,这非常简单。
新的LFU模式
从Redis4.0开始,可以使用一个新的最少使用频率回收模式。在某些下这个模式可以工作的更好(提供一个更好的命中/丢失率),因为使用LFU,Redis将会尝试追踪项目的访问频率,所以使用频率非常小的将会被回收,同时使用非常频繁的会有更多的机会被保存在内存中。
如果你在思考LRU,一个最近访问过但是实际上几乎从未被请求的项目,将不会被过期,因此有一个风险是回收一个未来有很多机会被访问的键。LFU不会有这个问题,并且通常应该对不同的访问模式适配的更好。
配置LFU有以下几种模式:
-
volatile-lfu
使用近似LFU算法回收拥有过期时间的键 -
allkeys-lfu
使用近似LFU算法回收所有键
LFU类似于LRU:它使用一个被称为莫里斯计数器的概率计数器,以便评估对象的访问频率,每个对象仅使用少量的字节,组合一个衰减周期,因此计数器随着时间流逝而减少:在某个时间点我们不再认为这个键是被频繁访问的,即使是在过去,因此这个算法可以适应访问模式的改变。
这些信息的取样过程跟LRU的取样过程(在本文档前面的部分已经解释过)很类似,以便选择一个候选对象回收。
然而,与LRU不同的是,LFU有确定的调节参数:例如,如果一个项目不再被访问,需要以多快的频率降低它的等级?为了更好的适配特定案例的算法,它也可以调节莫里斯计数器的范围。
Redis4.0被默认设置为:
- 让计数器饱和,大概100万请求。
- 每分钟把计数器衰减一次
那些是合理的值并经过测试试验过的,但是用户可能想玩一下配置以选择最佳值。
在源码发行版的redis.conf
文件中可以找到如何调整这些参数的的介绍,不过简而言之,它们是:
lfu-log-factor 10
lfu-decay-time 1
衰减时间是一个很明显的,当采样和发现超过这个值时,它是一个计数器必须衰减的分钟数。一个特别的值0
意味着:每次扫描时计数器都衰减,用途不是很大。
计数器对数因子
改变需要多少次命中才能充满频率计数器,取值范围在0-255. 较高的因子,需要更多的访问以达到最大值。较低的因子,是低访问计数器的更好的解决方案,符合下面的表格:
+--------+------------+------------+------------+------------+------------+
| 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 |
+--------+------------+------------+------------+------------+------------+
因此,基本上来讲,这个因子是分别低访问量 VS 高访问量之间更好的一种权衡。更多的信息可以在redis.conf
文件自身的描述中看到。
因为LFU是一个新的特征,我们都很感激对它在你的使用场景下与LRU相比表现如何的反馈。