1

将Redis用作LRU缓存

当Redis用作缓存时,通常很方便在添加新数据时让它自动逐出旧数据。此行为在开发人员社区中是众所周知的,因为它是流行的内存缓存系统的默认行为 。

LRU实际上只是支持的驱逐方法之一。本页涵盖Redis maxmemory指令的更一般主题,该指令用于将内存使用量限制为固定数量,并且还深入介绍了Redis使用的LRU算法,实际上是确切的LRU的近似值。

从Redis版本4.0开始,引入了新的LFU(最不常用)驱逐策略。本文档的单独部分对此进行了介绍。

Maxmemory配置指令
使用maxmemory配置指令是为了将Redis配置为对数据集使用指定的内存量。可以使用redis.conf文件来设置配置指令,或者稍后在运行时使用CONFIG SET命令来设置。

例如,为了配置100 MB的内存限制,可以在redis.conf文件内部使用以下指令。

maxmemory 100mb
设置maxmemory为零将导致没有内存限制。这是64位系统的默认行为,而32位系统使用3GB的隐式内存限制。

当达到指定的内存量时,可以在不同的行为之间进行选择,这称为策略。Redis只会为可能导致使用更多内存的命令返回错误,或者它可以逐出某些旧数据,以便在每次添加新数据时返回到指定的限制。

驱逐政策
maxmemory使用maxmemory-policy配置指令配置达到限制时,会发生确切的行为Redis 。

可以使用以下策略:

noeviction:在达到内存限制并且客户端尝试执行可能导致使用更多内存的命令时返回错误(大多数写入命令,但DEL和一些其他异常)。
allkeys-lru:通过尝试先删除较新使用的(LRU)键来退出键,以便为添加的新数据腾出空间。
volatile-lru:通过尝试先删除较新使用的(LRU)密钥来退出密钥,但仅在已设置了expire set的密钥之间,以便为添加的新数据腾出空间。
allkeys-random:随机逐出密钥,以便为添加的新数据腾出空间。
volatile-random:随机逐出键,以便为添加的新数据腾出空间,但仅逐出设置了expire set的键。
volatile-ttl:逐出设置了expire的键,并尝试首先逐出具有较短生存时间(TTL)的键,以便为添加的新数据腾出空间。
该政策挥发性-LRU,挥发性随机和挥发性-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算法进行近似处理,方法是对少量密钥进行采样,然后从采样的密钥中驱出最好的(访问时间最长)密钥。

但是,自Redis 3.0起,对该算法进行了改进,使其也可以将大量优秀候选人逐出。这提高了算法的性能,使其能够更接近地逼真的LRU算法的行为。

Redis LRU算法的重要意义在于,您可以通过更改样本数量来检查每次逐出,从而调整算法的精度。此参数由以下配置指令控制:

maxmemory-samples 5
Redis之所以不使用真正的LRU实现,是因为它占用更多内存。但是,近似值实际上与使用Redis的应用程序等效。以下是Redis使用的LRU近似与真实LRU比较的图形比较。

LRU比较

生成以上图形的测试用给定数量的密钥填充了Redis服务器。密钥是从第一个到最后一个访问的,因此第一个密钥是使用LRU算法驱逐的最佳候选者。后来又添加了50%的密钥,以强制淘汰一半的旧密钥。

您可以在图形中看到三种点,形成三个不同的带。

浅灰色带是被逐出的对象。
灰带是未被逐出的对象。
绿带是添加的对象。
在理论上的LRU实现中,我们期望在旧密钥中,前半部分将过期。相反,Redis LRU算法只会概率地使较早的密钥过期。

如您所见,与Redis 2.8相比,Redis 3.0在5个样本上做得更好,但是Redis 2.8仍保留了最新访问的对象中的大多数。在Redis 3.0中使用10的样本大小,近似值非常接近Redis 3.0的理论性能。

请注意,LRU只是预测未来将访问给定密钥的可能性的模型。此外,如果您的数据访问模式与幂律极为相似,则大多数访问将位于LRU近似算法将能够很好处理的密钥集中。

在仿真中,我们发现使用幂定律访问模式,真实LRU和Redis近似之间的差异很小或不存在。

但是,您可以以一些额外的CPU使用为代价将样本大小增加到10,以接近真实的LRU,并检查这是否会导致高速缓存未命中率有所不同。

使用CONFIG SET maxmemory-samples <count>命令以不同的样本量值在生产中进行实验非常简单。

新的LFU模式
从Redis 4.0开始,可以使用新的“ 最少使用”逐出模式。在某些情况下,此模式可能会更好地工作(提供更好的命中率/未命中率),因为使用LFU Redis会尝试跟踪物品的访问频率,因此,极少使用的物品会被驱逐,而经常使用的物品则有更高的机会保留在内存中。

如果您认为在LRU,最近访问过但实际上几乎从未请求过的项目不会过期,因此风险在于逐出将来有更高机会被请求的密钥。LFU没有这个问题,通常应该更好地适应不同的访问模式。

要配置LFU模式,可以使用以下策略:

volatile-lfu 使用具有过期集的密钥在近似LFU中进行驱逐。
allkeys-lfu 使用近似的LFU退出任何密​​钥。
LFU近似于LRU:它使用概率计数器(称为莫里斯计数器),以便仅使用每个对象几个位来估计对象访问频率,并结合一个衰减周期,以便使计数器随时间减少:在某些时候,我们不再希望将密钥视为频繁访问的密钥,即使它们是过去的密钥也是如此,因此该算法可以适应访问模式的转变。

这些信息的采样方式与LRU发生的情况类似(如本文档前面的部分中所述),以选择驱逐候选人。

但是,与LRU不同,LFU具有某些可调参数:例如,如果频繁访问的项目不再被访问,应该将其降低多快?还可以调整Morris计数器范围,以使算法更好地适应特定的用例。

默认情况下,Redis 4.0配置为:

在大约一百万个请求时使计数器饱和。
每隔一分钟使计数器衰减一次。
这些应该是合理的值,并且已经过实验测试,但是用户可能希望使用这些配置设置来选择最佳值。

有关如何调整这些参数的说明,可以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

因此,基本上,因素是在具有较低访问权限的更好区分项与具有较高访问权限的区分项之间进行权衡。示例redis.conf文件自我记录注释中提供了更多信息。

由于LFU是一项新功能,因此与LRU相比,我们将非常感谢您提供有关LFU在您的用例中的性能的反馈。


Yujiaao
12.7k 声望4.7k 粉丝

[链接]