Redis 键驱逐
参考资料:Using Redis as an LRU cache
最大内存配置
最大内存限制由配置项 maxmemory 控制。当 maxmemory 为 0 时没有内存限制。在 32 位系统则隐式限制为 3GB 。
当达到最大内存限制,根据键驱逐策略不同,会返回错误提示或是进行数据驱逐。
驱逐策略
通过配置项 maxmemory-policy 对驱逐策略进行配置,有以下驱逐策略:
- noeviction : 不会对键进行驱逐,当达到内存限制时,添加数据的命令将会返回错误。
- allkeys-lru : 当达到内存限制时,驱逐最近最少使用的数据(LRU)。
- volatile-lru : 只对设置了过期时间的键进行 LRU 驱逐。如果没有键设置了过期时间,将会返回错误。
- allkeys-random : 对所有键进行随机驱逐。
- volatile-random : 只对设置过期时间的键进行随机驱逐。如果没有键设置了过期时间,将会返回错误。
- volatile-ttl : 只对设置了过期时间的键进行驱逐,优先驱逐即将过期的键。同样如果没有键设置了过期时间,将会返回错误。
驱逐执行
- 客户端发起将会导致数据量增加的命令
- Redis 检查内存使用量,如果超过限制即通过驱逐策略驱逐键
- Redis 服务执行命令
LRU 实现
Redis 的 LRU 实现是近似实现,这意味着 Redis 的 LRU 算法并不能准确的找到最近最少的键使用并将其驱逐。
Redis 的 LRU 具体实现是对数据集进行抽样,从样本中选出最近最少使用的键并将其驱逐。
这样的实现方式比起完整的 LRU 实现更加节约资源。如果想要更加准确的通过 LRU 驱逐键,可以调整配置项 maxmemory-samples ,该配置项控制了样本量。
虽然 Redis 对 LRU 的实现不严谨,但是大部分的对数据集的访问都符合幂次模式(二八定律),在这种情况下,近似 LRU 实现并没比严谨的 LRU 实现差多少。
LFU 实现
Redis 4.0 后添加了 LFU 模式,即驱逐访问频率少的键的驱逐模式。LFU 模式下有两种策略可选:
- allkeys-lfu : 所有键进行 LFU 驱逐
- volatile-lfu : 设置了过期时间的键进行 LFU 驱逐
LRU 有以下的问题:最近被访问过,但是总体上访问次数很少的键不会被驱逐,反而会导致之后可能会被大量访问的键被驱逐。
LFU 则是用于解决上述问题的一种解决方案,通过键的访问频率进行键的筛选驱逐。
LFU 的实现也是近似的,利用 Morris 计数器进行实现。
对于每个对象,只需要一个很少量的空间进行访问计数,且该计数将会随着时间而衰减,即如果一个键之后没有被访问,其计数将会随着时间减少。
如何进行计数以及如何进行衰减在 Redis 中都可以进行配置,相关的默认配置项为:
lfu-log-factor 10
lfu-decay-time 1
以上的两个配置代表的意义为:
- 计数器是能够被打满的,满时值为 255 ,即其计数范围为 0 ~ 255 。factor 为控制多少次访问将打满计数器的配置项。默认 factor 为 10 ,大约 100 万次的访问将打满计数器。
- decay-time 为衰减周期,默认为 1 ,即每分钟进行一次衰减。
以下为不同 factor 下,访问次数与计数器的关系:
factor | 100 hits | 1000 hits | 10K 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 |
可以看出 factor 越大,想要打满计数器所需要的访问次数也就越多,访问频率也就分布得越细。当 factor 为 0 时,访问 1000 次与访问 1 百万次在 Redis 看来,它们的访问频率是一样的。