原文链接

—————-

使用Redis作为LRU缓存

当Redis被用作缓存时,当你添加新数据时很容易的驱逐旧数据。这个行为在开发者社区很著名,因为这是流行的_memcached_系统的默认行为。

LRU实际上只是支持的驱逐策略的一种。这篇文章包含了更多的Redis的maxmemory配置的主题,maxmemory配置是为了限制内存到指定数量。同时这篇文章还深入介绍了被Redis使用的LRU算法,实际上是exact LRU的近似值。

maxmemory配置指令

maxmemory配置指令用作配置Redis为数据集使用指定数量的内存。可以通过设置redis.conf文件配置或稍后运行时使用CONFIG SET命令。

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

maxmemory 100mb

设置maxmemory为0标明没有内存限制。这是64-bit系统的默认限制,但是32-bit系统使用3G的隐式内存限制。

当达到指定的内存限制时,redis可能会选择多个不同的行为,叫做策略。当达到内存达到设置的阈值时,Redis可以仅仅返回错误,或者Redis可以驱逐一些旧的数据,以便在新增新数据时候,不会达到指定的限制。

驱逐策略

当达到maxmemory限制时,Redis的行为是由maxmemory-policy指令配置的。

如下策略可用:

  • noeviction: 当达到内存限制,客户端又尝试执行新的命令,而该命令又会增加Redis内存时(大多数写命令,除了DEL命令和一些其他的例外),返回错误。
  • allkeys-lru: 为了给新数据提供空间,通过首先移除全部的最近最少使用(LRU)的键值。
  • volatile-lru: 为了给新数据提供空间,通过首先移除有过期时间的键值集合中的最近最少使用(LRU)的键值。
  • allkey-random: 为了给新数据提供空间,随机驱除所有key中的键值。
  • volatile-random: 为了给新数据提供空间,随机驱除所有存在过期时间的key中的键值。
  • volatile-ttl: 为了给新数据提供空间,驱除存在过期时间中的的键值中TTL最小的键值。

volatile-lru,volatile-randomvolatile-ttl策略,如果没有先决条件条件匹配的驱逐健时,就像noevictiond的行为一样。

根据应用程序的接入类型选择驱逐策略是很重要的。然而你可以在运行时重新配置Redis的配置策略,并通过INFO命令监视缓存击中和错失的次数,已调整设置。

一般来说的经验法则:

  • 通常当你希望在你的受欢迎程度的请求上有幂律分布(power-law)的情况下,使用allkeys-lru,也就是你期望一部分子键值的访问频率要高于其他子键值。如果不确定推荐选择这种策略
  • 当循环扫描所有键值时采用allkey-random策略,或者你希望均匀分布的策略(所有的以相同的概率接入)。
  • 当创建你的缓存对象时,能明确知道该缓存对象的生存周期,使用不同的值,这种前提下可以采用volatile-ttl

volatile-lruvolatile-random策略主要是用在你希望用一个Redis实例来同时用作缓存和存储。但是更好的方式是使用两个实例来解决这个问题。 同样需要注意的是,给一个键值设置过期时间会消耗内存,所以在有内存压力情况下,使用allkey-lru的内存使用率会更好。

驱逐策略怎么样工作

理解驱逐策略很重要:

  • 客户端执行一个新命令,导致内存增加。
  • Redis检查内存使用率,如果比maxmemory更大,根据驱逐策略对键值进行驱逐。
  • 新的命令得以执行,如此循环往复。

因此我们执行命令使内存持续的在边界徘徊,执行命令增加内存,驱逐键值减少内存。

如果一个commond导致很多内存的使用(比如大集合的交集存储到新的键值),一定时间范围内可以观察到明显的内存超限。

近似LRU算法

Redis LRU算法并非一个LRU的准确实现。这意味着Redis不能挑选__最合适__的驱逐key。近似的他运行了一个近似的LRU算法,通过key的采样数量,驱除采样key中最合适的(最老的接入时间)。

然而在Redis3.0中算法提高了,通过驱逐的一个合适的键值池子。提高了算法效率以更加拟合真实的LRU算法。

可以通过设置采样数精度来提高LRU算法。配置参数指令如下:

maxmemory-samples 5

Redis不使用真实LRU算法原因在于其更加占用内存。但是近似LRU基本上近似等于真实的LRU。如下是一个图表展示了Redis使用的近似LRU和真实LRU间的比较。 compare lru alg

  • 在 Redis 中填充满指定数目的数据,顺序访问所有的键,在LRU下,第一个键是最佳逐出对象。然后增加50%的键值,逐出一半的老数据。
  • 图中的三种点形成了三条不同的条纹
    • 浅灰色表示已经逐出的
    • 灰色表示未被逐出的
    • 绿色表示新增的
  • 理论LRU的结果完全符合预期,前一半的老数据逐出。Redis LRU 则是概率上的逐出老数据。
  • 可以看到,取样数为5时,Redis 3.0 比 Redis 2.8 效果要好很多,2.8逐出了不少刚刚被访问过的数据。取样数为10时,Redis 3.0 的表现跟理论LRU就非常接近了。
  • 如果请求符合长尾法则,那么真实LRU与Redis LRU之间表现基本无差异。
  • 你可以在增加一定CPU消耗的情况下,提高取样数,然后检查命中率是否有变化
  • 在生产环境,通过 CONFIG SET maxmemory-samples 指令可以方便的设置取样数。

新的LFU模式

从Redis4.0开始,Least Frequently Used eviction mode可用了。这种模式在特定模式下更有优势(hits/missing比更好),由于使用LFU会跟踪最近使用频繁的条目,所以经常使用的键值会保留在内存中,而很少使用的键值会被驱逐。

如果您认为在 LRU,最近访问过但实际上几乎从未被请求过的键值不会过期,因此有驱逐将来有更高机会被请求的key。LFU不存在这样的问题,通常对不同的接入模式适配的更好。 为了配置LFU模式,可以使用下面的策略。

  • volatile-lfu:在有过期时间的键值中使用近似LFU。
  • allkesy-lfu:近似LFU中驱逐所有的key。

LFU 类似于 LRU:它使用一个概率计数器,称为莫里斯计数器,以便仅使用每个对象的几位来估计对象访问频率,并结合衰减周期,以便计数器随着时间的推移而减少:在某些时候,我们不再希望将Key视为经常访问的Key,即使它们过去是这样,以便算法可以适应访问模式的转变。

这些信息的采样与 LRU 发生的情况类似(如本文档的前一节所述),以便选择驱逐的候选人。

然而,与 LRU 不同的是,LFU 具有某些可调参数:例如,如果不再访问频繁项,它的排名应该以多快的速度降低? 还可以调整 Morris 计数器范围,以便更好地使算法适应特定用例

默认的,Redis 4.0被配置为:

  • 大概100w次时使计数器饱和。
  • 每分钟衰减一次计数器。 这些应该是合理的值并经过实验测试,但用户可能希望使用这些配置设置以选择最佳值。 怎样触发这些参数能够在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 相比它在您的用例中的表现的任何反馈。