Redis的过期键删除策略和数据逐出策略
Redis作为一个高性能的内存NoSQL数据库,其容量受到最大内存限制的限制。
在实际生产环境中使用Redis时,偶然会觉得Redis的内存占用要比自己预想的大。事实上,Redis占用的内存除了保存键值对所需的开销外,还有一些运行时产生的额外内存,包括:
- 过期Key所占空间
- 渐进式Rehash导致未及时删除的空间
- Redis管理数据,包括底层数据结构开销,客户端信息,读写缓冲区等
- 主从复制,bgsave时的额外开销
Redis的渐进式Rehash,在笔者介绍Concurrenthashmap扩容的时候,做了简单的介绍,点击查看
Redis的主从复制,则在笔者的另一篇博客里做了详细的介绍,点击查看
所以本文将主要围绕过期Key的回收问题进行讲解。
过期键的删除策略
如果Redis的一个键是过期的,那它到了过期时间之后并不是马上就从内存中被删除,而是采用了三种不同的删除策略:
- 立即删除
- 惰性删除
- 定时删除
其中第二种为被动删除,第一种和第三种为主动删除,而且第一种实时性更高。
1.立即删除
立即删除是指,在设置键的过期时间时,创建一个回调事件,当过期时间达到时,由时间处理器自动执行键的删除操作。
立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。但是立即删除对cpu是最不友好的。因为删除操作会占用cpu的时间,如果刚好碰上了cpu正在做排序等计算的时候,就会给cpu造成额外的压力。
而且目前redis事件处理器对时间事件的处理方式--无序链表,查找一个key的时间复杂度为O(n),所以并不适合用来处理大量的时间事件。
2.惰性删除
惰性删除是指,某个键值过期后,此键值不会马上被删除,而是等到下次被使用的时候,才会被检查到过期,此时才能得到删除。所以惰性删除的缺点很明显:浪费内存。
举个例子,对于一些按时间点来更新的数据,比如log日志,过期后在很长的一段时间内可能都得不到访问,这样在这段时间内就要浪费这么多内存来存log。
3.定时删除
从上面分析来看,立即删除会短时间内占用大量cpu,惰性删除会在一段时间内浪费内存,所以定时删除是一个折中的办法。
定时删除是指:每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对cpu的影响。另一方面定时删除也有效的减少了因惰性删除带来的内存浪费。
过期Key清理算法
Redis过期Key清理的机制对清理的频率和最大时间都有限制,在尽量不影响正常服务的情况下,进行过期Key的清理,以达到长时间服务的性能最优。
Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。具体的算法如下:
- Redis配置项hz定义了serverCron任务的执行周期,默认为10,即CPU空闲时每秒执行10次;
- 每次过期key清理的时间不超过CPU时间的25%,即若hz=1,则一次清理时间最大为250ms,若hz=10,则一次清理时间最大为25ms;
- 清理时依次遍历所有的db;
- 从db中随机取20个key,判断是否过期,若过期,则逐出;
- 若有5个以上key过期,则重复步骤4,否则遍历下一个db;
- 在清理过程中,若达到了25%CPU时间,退出清理过程;
这是一个基于概率的简单算法,基本的假设是抽出的样本能够代表整个key空间,redis持续清理过期的数据直至将要过期的key的百分比降到了25%以下。
由于算法采用的随机取key判断是否过期的方式,故几乎不可能清理完所有的过期Key。
调高hz参数可以提升清理的频率,过期key可以更及时的被删除,但hz太高会增加CPU时间的消耗。
数据逐出策略
在redis中,允许用户设置最大使用内存大小maxmemory(需要配合maxmemory-policy使用),设置为0表示不限制(默认配置)。
生产环境中需要设置此值,最好不超过内存60%-70%。
当redis内存数据集快到达maxmemory时,redis会实行数据淘汰策略。
Redis提供6种数据淘汰策略。在逐出算法中,根据用户设置的逐出策略,选出待逐出的key,直到当前内存小于最大内存值为止。
可选逐出策略如下:
- volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集中任意选择数据 淘汰
- allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集中任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据
- 在redis2.8中默认策略是volatile-lru
- 在redis3.2和redis4.0中默认策略是no-eviction
如果使用no-eviction时,当内存不足,Redis会返回OOM的错误信息
(error) OOM command not allowed when used memory > 'maxmemory'.
当cache中没有符合清除条件的key时,回收策略 volatile-lru, volatile-random 和volatile-ttl 将会和策略 noeviction 一样直接返回错误。
选择正确的回收策略是很重要的,取决于你的应用程序的访问模式。使用INFO命令输出来监控缓存命中和错过的次数,以调优Redis的配置。
通用规则如下:
- 如果期望用户请求呈现幂律分布(power-law distribution),也就是,期望一部分子集元素被访问得远比其他元素多时,可以使用allkeys-lru策略。
- 如果期望是循环周期的访问,所有的键被连续扫描,或者期望请求符合平均分布(每个元素以相同的概率被访问),可以使用allkeys-random策略。
- 如果期望是让redis使用缓存对象设置的TTL值,确定哪些对象应该是较好的清除候选项,可以使用volatile-ttl策略。