首页 > 数据库 > 数据库开发技术 > 重新认识redis(九)





地球人都知道,内存相对于磁盘很贵,因此在用redis作缓存时,缓存的空间容量必然小于后端数据库的数据总量,因此就引出了接下来需要学习的模块,redis的淘汰策略,缓存污染问题以及淘汰策略涉及到的两大算法,LRU算法和LFU算法,淘汰策略那么多,该如何选择呢?可以通过下图加深理解他们之间的关系。




Redis缓存使用内存来保存数据以提升应用的响应速度,避免业务直接从后端数据库中读取数据,但是出于成本考虑,不可能将数据库中所有的数据的数据都放到缓存中。因此,当缓存空间中数据量越来越大以至于最后被写满了而导致新来的数据无法写入。对此,redis提供了淘汰机制来解决这个问题。
Redis4.0之前共提供了6种内存淘汰策略,4.0后又增加了2种,因此共计8种,这8种淘汰策略根据是否进行数据淘汰可分为两类,而其中对数据进行淘汰的策略又可根据淘汰候选集的范围分为设置过期时间的数据集和全量数据集。

具体如下:
noeviction策略:当redis使用的内存空间超过maxmemory的值时,不淘汰数据,当再有写请求时,redis不再提供服务而是直接返回错误,实际生产中不应用。
volatile-ttl策略:对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
volatile-random策略:对设置了过期时间的键值对进行随机删除。
volatile-lru策略:对设置了过期时间的键值对进行LRU算法筛选删除。
volatile-lfu策略:对设置了过期时间的键值对进行LFU算法筛选删除。
allkeys-random策略:从所有键值对中随机选择并删除数据。
allkeys-lru策略:使用LRU算法在所有数据中进行筛选数据。
allkeys-lfu策略:使用LFU算法在所有数据中进行筛选。
对于设置了过期时间的键值对,无论是达到过期时间还是被删除策略选中都会进行删除。
redis淘汰策略中的volatile和allkeys-lru策略中使用了LRU算法,但是与传统的LRU算法又有差别。传统的LRU算法会将所有的数据组织成一个链表,链表的头(MRU)和尾(LRU)分别代表最近最常使用的数据和最近最不常用的数据。当有新数据到来后,新数据会被放到MRU端,原来的数据对应地后移一位,然后删除LRU端数据。但是链表和链表移动会带来额外的空间开销,并且还会降低Redis的性能。
Redis针对LRU算法进行了简化,Redis使用数据结构中的lru字段积累每个数据的最近一次访问的时间戳,当Redis进行数据淘汰时,第一次会随机选出N个数据,将其作为一个候选集合,然后选择这N个数据的lru字段,将lru字段值最小的数据淘汰出去,N值可通过maxmemory-samples参数设置。当需要再次淘汰数据时,Redis挑选lru值小于候选集合中最小的lru值的键值对进行第一次淘汰时创建的候选集合。
对于淘汰策略,如果业务数据中有明显的冷热数据区分,建议使用allkeys-lru策略。如果业务中数据访问频率相差不大,建议使用allkeys-random策略,如果业务中有置顶的需求,则建议使用volatile-lru策略,同时对这些置顶数据不设置过期时间。


了解缓存污染,可以从以下几个方面来进行学习。
1.缓存污染的定义。
2.LFU算法的实现思想。
3.缓存污染的解决方法。
1.缓存污染的定义:
使用redis作为缓存的初衷是为了加速业务应用的访问,但是在某些场景下,部分数据被访问的次数很少,当这些数据被访问完后依然留在缓存中就会浪费缓存空间,即缓存污染。如果只是少量污染数据占据缓存空间,对缓存影响不会很大,但是如果出现大量污染数据,则会严重影响缓存应用的性能。
2.LFU算法的实现思想:
LFU缓存策略是在LRU缓存策略的基础上进行优化的,LRU缓存策略是根据访问时间进行数据淘汰,适用于数据频繁访问的业务场景,但是如果发生扫描式单次查询操作,即应用对大量的数据进行一次全体读取,并且只会读取一次,LRU策略便会影响性能。因为这些数据最近被访问,因此lru字段值很大,会被留存在缓存中较长时间,而由于数据量很大,会导致缓存空间占满,却又不会服务新的缓存请求,当新数据写入缓存时,则需要先进行数据替换。LFU策略在LRU策略的基础上增加了访问次数计数器,当进行数据淘汰时,先根据访问次数进行筛选,优先淘汰访问次数低的数据,如果数据访问次数相同,则对比访问时间,优先淘汰上一次访问时间更久的数据。
3.缓存污染的解决方法:
解决缓存污染问题,关键是根据不同的业务场景选择合适的缓存淘汰机制。


在LFU策略中,将原来的24bit大小的lru字段拆分为两部分,前16bit表示数据的访问时间戳,后8bit表示数据的访问次数,在候选集合中,根据数据lru字段的后8bit选择访问次数最少的数据进行淘汰,当访问次数相同时候,再根据lru字段的前16bit值大小选择访问时间最久远的数据进行淘汰,对于记录访问次数的8bit值,redis并不是采用数据每访问一次就对当前值加一的计数规则,而是采用如下规则:
redis中访问次数计算规则关键在于lfu_log_factor和lfu_decay_time两个参数,其中,lfu_log_factor用来控制计数器值增长的速度,lfu_decay_time用来控制访问次数的衰减。数据被访问时,会先用计数器当前的值乘以lfu_log_factor值+1,然后取其倒数,得到的值与一个取值范围在(0,1)之间的随机数相比,只有大于随机数时计数器值才进行加一操作。一般建议将lfu_log_factor值设置为10或者更大,另外,为了防止某些数据在短时间内被大量访问之后就不会被访问而造成缓存污染,LFU策略使用lfu_decay_time参数来控制访问次数的衰减,LFU策略会计算当前时间和数据最近一次被访问时间的差值并换算成分钟单位,然后除以lfu_decay_time值以得到要衰减的值,因为lfu_decay_time值越大,衰减值越小,衰减效果越差,所以如果业务中有短时高频访问的数据,建议设置lfu_decay_time值为1。
总而言之,需要合理根据业务场景设置lfu_log_factor和lfu_decay_time两个参数,以达到更好的访问性能,当然这可能需要大量的测试和验证才能得出。
如何根据数据库数据量的大小预设缓存的容量大小呢,这里,可以参考二八定理,即将缓存空间容量设置为总数据量的20%左右,即可拦截80%左右的访问,但是在某些业务场景中,20%的数据无法贡献80%的访问,因此实际生产中需要根据性能和成本两方面考虑,一般建议设置为总容量的15%-30%,另外,可以通过info memory命令监控查看一段时间内的内存使用情况进行合理的预估。
事无绝对,例如,如果一个数据短时间内被高频访问,而此时虽然使用了LFU策略,但是由于lfu_decay_time参数设置不合理,导致计数器值衰减很慢,就会导致该数据滞留在缓存中较长时间,因此,使用LFU策略时需要合理设置lfu_log_factor和lfu_decay_time两个参数。
缓存根据淘汰策略淘汰数据后,这些数据去了哪呢,如果是干净数据,即与数据库现有数据一致,则直接删除,如果是脏数据,即缓存中数据为最新数据,则需要写回到数据库。这又可以和只读缓存与读写缓存相关联了。

最近学习的内容是redis的数据淘汰策略以及缓存污染及相关联的LRU算法,LFU算法。随着学习的深入,你是不是感觉对redis对于你也不再神秘了呢。

END





原文作者:DBA修行之路;如有侵权,请联系管理员删除。