redis(十二:淘汰机制)
MySQL 中有 1TB 的数据,如果我们使用 Redis 把这 1TB 的数据都缓存起来,虽然应用都能在内存中访问数据了,但是,这样配置并不合理,因为性价比很低。
一方面,1TB 内存的价格大约是 3.5 万元,而 1TB 磁盘的价格大约是 1000 元。另一方面,数据访问都是有局部性的,也就是我们通常所说的“八二原理”,80% 的请求实际只访问了 20% 的数据。所以,用 1TB 的内存做缓存,并没有必要。
在实践过程中,我看到过的缓存容量占总数据量的比例,从 5% 到 40% 的都有。这个容量规划不能一概而论,是需要结合应用数据实际访问特征和成本开销来综合考虑的。建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。
Redis 缓存有哪些淘汰策略
eviction
默认情况下,Redis 在使用的内存空间超过 maxmemory 值时,并不会淘汰数据,也就是设定的 noeviction 策略。对应到 Redis 缓存,也就是指,一旦缓存被写满了,再有写请求来时,Redis 不再提供服务,而是直接返回错误。
过期时间相关的淘汰机制
我们使用 EXPIRE 命令对一批键值对设置了过期时间后:1,这些键值对的过期时间是快到了;2,Redis 的内存使用量达到了 maxmemory 阈值。(volatile-开头的策略 ,如果一个key还未过期,还是可能被删的)
Redis 都会进一步按照 volatile-ttl、volatile-random、volatile-lru、volatile-lfu 这四种策略的具体筛选规则进行淘汰。
1,volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
2,volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
3,volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。
4,volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。
allkeys淘汰机制-无视过期时间
allkeys-lru、allkeys-random、allkeys-lfu 这三种淘汰策略的备选淘汰数据范围,就扩大到了所有键值对,无论这些键值对是否设置了过期时间。它们筛选数据进行淘汰的规则是:
1,allkeys-random 策略,从所有键值对中随机选择并删除数据。
2,allkeys-lru 策略,使用 LRU 算法在所有数据中进行筛选。
3,allkeys-lfu 策略,使用 LFU 算法在所有数据中进行筛选。
以上的淘汰机制都是定时删除没扫描到的老赖,当内存不足时,进行淘汰。
LRU (Least Recently Used)算法
volatile-lru 和 allkeys-lru 策略都用到的 LRU 算法。LRU 算法按照最近最少使用的原则来筛选数据,最不常用的数据会被筛选出来,而最近频繁使用的数据会留在缓存中。
LRU 会把所有的数据组织成一个链表,链表的头和尾分别表示 MRU 端和 LRU 端,分别代表最近最常使用的数据和最近最不常用的数据。
LRU 算法背后的想法非常朴素:它认为刚刚被访问的数据,肯定还会被再次访问,所以就把它放在 MRU 端;长久不访问的数据,肯定就不会再被访问了,所以就让它逐渐后移到 LRU 端,在缓存满时,就优先删除它。
不过,LRU 算法在实际实现时,需要用链表管理所有的缓存数据,这会带来额外的空间开销。而且,当有数据被访问时,需要在链表上把该数据移动到 MRU 端,如果有大量数据被访问,就会带来很多链表移动操作,会很耗时,进而会降低 Redis 缓存性能。
Redis 中简化的LRU 算法
在 Redis 中,LRU 算法被做了简化,以减轻数据淘汰对缓存性能的影响。
具体来说,Redis 默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)。然后,Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。(抽样-》比较-》淘汰)
当需要再次淘汰数据时,Redis 需要挑选数据进入第一次淘汰时创建的候选集合。这儿的挑选标准是:能进入候选集合的数据的 lru 字段值必须小于候选集合中最小的 lru 值。
lru也是有局限性的,比如一个冷门数据被刚刚访问后,得隔一段时间才会被淘汰(内存污染)。
LFU (Least Frequently Used)算法
LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。
当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
LFU 策略使用衰减因子配置项 lfu_decay_time 来控制访问次数的衰减。
简单举个例子,假设 lfu_decay_time 取值为 1,如果数据在 N 分钟内没有被访问,那么它的访问次数就要减 N。如果 lfu_decay_time 取值更大,那么相应的衰减值会变小,衰减效果也会减弱。所以,如果业务应用中有短时高频访问的数据的话,建议把 lfu_decay_time 值设置为 1,这样一来,LFU 策略在它们不再被访问后,会较快地衰减它们的访问次数,尽早把它们从缓存中淘汰出去,避免缓存污染。
淘汰策略使用建议
1,优先使用 allkeys-lru 策略。这样,可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。如果你的业务数据中有明显的冷热数据区分,我建议你使用 allkeys-lru 策略。
2,如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用 allkeys-random 策略,随机选择淘汰的数据就行。
3,如果你的业务中有置顶的需求,比如置顶新闻、置顶视频,那么,可以使用 volatile-lru 策略,同时不给这些置顶数据设置过期时间。这样一来,这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据 LRU 规则进行筛选。
Redis采用惰性删除+定期删除
redis有一个定时任务每秒执行10次。值得注意的是,这里的扫描并不是扫描全部带有过期时间的key,更像是一种抽查。
第一步,从有失效机制的key中随机取出20个key。
第二步,删除已经过期的key。
第三部,判断下是否超过1/4的key已经失效了,如果没有执行步骤第一步。
这样就可以保证在任何时间,过期的key占用的内存空间的最大值 是 每秒写操作 key/4
惰性删除策略。当一个数据的过期时间到了以后,并不会立即删除数据,而是等到再有请求来读写这个数据时,对数据进行检查,如果发现数据已经过期了,再删除这个数据。
需注意的是从库本身不会执行删除操作,如果客户端在从库中访问留存的过期数据,从库并不会触发数据删除。可能读到过期数据
在 3.2 版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免了客户端读到过期数据。所以,在应用主从集群时,尽量使用 Redis 3.2 及以上版本。(主从机器时钟不一致也可能读到过期数据)