Redis 内存回收策略和key失效机制

3,125 阅读6分钟

Redis是基于内存的key-value数据库,内存的大小是有限制的,如果内存满了,Redis会怎么办呢,另外,我们可以通过expire设置key的过期时间,那么key过期失效了, Redis回收机制又是怎么样的呢?

内存回收策略

最大内存配置

Redis的按照目录下的redis.conf配置文件中可以配置redis占用内存的大小

# 设置最大占用内存为100MB
maxmemory 100mb

tips: 如果这里设置为0的话,64位的操作系统默认是没有内存限制,而32位的操作系统隐式限制为3GB

Redis还有两个关于最大内存的命令

// 获取最大内存限制
config get maxmemory
 1)  "maxmemory"
 2)  "0"

// 设置最大的内存限制
config set maxmemory 100mb

内存驱逐策略(Eviction policies)

当达到配置文件最大内存限制的时候,Redis有几种策略来处理这种情况:

如果不懂LRU算法,可以参考一下这篇文章LRU算法

  • noeviction(默认策略):对于写请求不再提供服务,直接返回错误(DEL请求和部分特殊请求除外)

  • allkeys-lru:从所有key中使用LRU算法进行淘汰

  • volatile-lru:从设置了过期时间的key中使用LRU算法进行淘汰

  • allkeys-random:从所有key中随机淘汰数据

  • volatile-random:从设置了过期时间的key中随机淘汰

  • volatile-ttl:在设置了过期时间的key中,根据key的过期时间进行淘汰,越早过期的越优先被淘汰

当使用volatile-lru、volatile-random、volatile-ttl这三种策略时,如果没有key可以被淘汰,则和noeviction一样返回错误

redis 支持利用命令对驱逐策略进行修改

// 获取内存淘汰策略 可以看到默认是noeviction
config get maxmemory-policy
 1)  "maxmemory-policy"
 2)  "noeviction"
 
 // 修改内存驱逐策略
 config set maxmemory-policy allkeys-lru

同样地,也可以在配置文件中指定驱逐策略

maxmemory-policy allkeys-lru

驱逐策略如何生效

  1. 当Redis收到一条会添加数据的命令
  2. Redis检查内存使用情况,是否超过最大内存限制
  3. 超过则执行内存驱逐策略,然后执行命令。

tips:如果某个命令大量使用内存,则占用内存可能会超过最大内存限制


Redis中实现的LRU算法

Redis实现的是一个近似的LRU算法,它跟常规的LRU算法还不太一样。近似LRU算法通过随机采样法淘汰数据,每次随机出5(默认)个key,从里面淘汰掉最近最少使用的key。

Redis为了实现近似LRU算法,给每个key增加了一个额外增加了一个24bit的字段,用来存储该key最后一次被访问的时间。

tips:可以通过设置值maxmemory-samples参数修改采样数量

Redis 3.0对这个近似算法的优化

新算法会维护一个候选池(大小为16),池中的数据根据访问时间进行排序,第一次随机选取的key都会放入池中,随后每次随机选取的key只有在访问时间小于池中最小的时间才会放入池中,直到候选池被放满。当放满后,如果有新的key需要放入,则将池中最后访问时间最大(最近被访问)的移除。

当需要淘汰时,需要从池中捞出最久没被访问的key淘汰掉就行了。

新旧算法的对比

下面的图片是Redis官方文档给出的新旧算法对比结果:

Redis 3.0 与 Redis 2.8 LRU内存驱逐算法对比
  • 浅灰色是被淘汰的数据
  • 灰色是没有被淘汰掉的老数据
  • 绿色是新加入的数据

可以看到3.0的效果明显比2.8的要得多,并且取样数越大,越接近标准的LRU算法


为什么Redis不使用真正的LRU

原因很简单,理论的LRU需要你占用更大的内存(每个key还需要保存前后key的地址), 但你从上图就可以看出Redis 3.0使用的近似LRU算法使用起来的效果几乎与理论的LRU等效了。

新的驱逐策略---LFU

LFU算法是Redis4.0里面新加的一种淘汰策略。它的全称是Least Frequently Used,它的核心思想是根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来。

LFU算法能更好的表示一个key被访问的热度。假如你使用的是LRU算法,一个key很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些key将来是很有可能被访问到的则被淘汰了。如果使用LFU算法则不会出现这种情况,因为使用一次并不会使一个key成为热点数据。

实现主要依靠于两个参数计算器衰减周期计算器取值范围为0-255,访问次数越高对应的取值越高,衰减周期表示每过一定的时间,就对计算器进行减值。有如下两个参数

// 因子越大,改要使频率计数器所需的次数越多
lfu-log-factor 10
// 每隔1分钟计算器减1, 如果是0的话表示每次扫描时总是使计数器衰减
lfu-decay-time 1

LFU一共有两种策略:

  • volatile-lfu:在设置了过期时间的key中使用LFU算法淘汰key
  • allkeys-lfu:在所有的key中使用LFU算法淘汰数据

key失效机制

redis的Key失效机制有两种:被动方式(passive way)主动方式(active way)

被动方式

当客户端尝试访问key时,如果发现key已经失效了,就删除该key,并且告诉客户端该key已经失效了。

主动方式

当然,有被动方式还不够,因为可能会存在一些过期的key却不会被再次访问

那怎么删除这些key呢,定时遍历整个库吗?这样当然不行,数据量大的时候太过于耗费性能了。

Redis主动删除失效key的策略是:随机抽取一部分的key进行校验,如果已经失效,就删除淘汰。

具体措施

Redis每秒执行以下操作10次:

  1. 在有过期时间的key集合中随机抽取20个key。
  2. 删除所有的过期key
  3. 如果过期的key超过25%,重新执行步骤1.

参考:

文章参考自Redis官方文档lru-cacheexpire

掘金用户藕叶的Redis的内存淘汰策略文章