Redis(六)-缓存方案-淘汰/过期

概述

本节学习下Redis作为缓存时,几种常见的情形及其解决解决方案

1. 缓存淘汰

1. 缓存写满:为了保证较高的性价比,缓存的空间容量必然要小于后端数据库的数据总量。但是内存大小毕竟有限,随着要缓存的数据量越来越大,有限的缓存空间不可避免地会被写满;
2. 缓存污染:留存在缓存中的数据,实际不会被再次访问了,但是又占据了缓存空间,这种数据量比较大甚至会占满缓存;

缓存写满后就会涉及缓存淘汰的问题。

1.1 淘汰策略

Redis4.0以后共8种淘汰策略:
1.:在设置了过期时间的数据中进行淘汰
volatile-random: 随机
volatile-ttl: 过期时间的先后
volatile-lru: 最近最少使用
volatile-lfu: 最近使用次数最少

2.:在所有数据中进行淘汰
allkeys-lru: 最近最少使用
allkeys-random: 随机
allkeys-lfu: 最近使用次数最少

3.:不进行淘汰
noeviction: 不进行淘汰

1.2 LRU策略

传统LRU实现方式使用链表实现(访问后就移动到队头,从队尾直接淘汰),需要额外的空间开销,同时移动节点也会消耗性能;

1.2.1 Redis对LRU进行了优化:

1.在RedisObject中的lru字段记录最近一次访问的时间戳
2.决定淘汰数据时,第一次会随机选出N个数据作为一个候选集合,把lru字段值最小的数据淘汰出去
3.准备好候选集等待被删除,即挑选lru字段值小于候选集合中最小的lru值的数据放进候选集
4.决定淘汰数据时,直接从候选集中淘汰lru字段小的数据

1.2.2 Mysql BufferPool对LRU进行了优化:

Redis(六)-缓存方案-淘汰/过期_第1张图片
lru.png

在 InnoDB 实现上,按照 5:3 的比例把整个 LRU 链表分成了 young 区域和 old 区域。图中 LRU_old 指向的就是 old 区域的第一个位置,是整个链表的 5/8 处。也就是说,靠近链表头部的 5/8 是 young 区域,靠近链表尾部的 3/8 是 old 区域。
改进后的 LRU 算法执行流程变成了下面这样。
1.图中状态 1,要访问数据页 P3,由于 P3 在 young 区域,因此和优化前的 LRU 算法一样,将其移到链表头部,变成状态 2。
2.之后要访问一个新的不存在于当前链表的数据页,这时候依然是淘汰掉数据页 Pm,但是新插入的数据页 Px,是放在 LRU_old 处。
3.处于 old 区域的数据页,每次被访问的时候都要做下面这个判断:
若这个数据页在 LRU 链表中存在的时间超过了 1 秒,就把它移动到链表头部;如果这个数据页在 LRU 链表中存在的时间短于 1 秒,位置保持不变。1 秒这个时间,是由参数 innodb_old_blocks_time 控制的。其默认值是 1000,单位毫秒。

这个策略,就是为了处理类似全表扫描的操作量身定制的。以扫描 200G 的历史数据表为例,改进后的 LRU 算法的操作逻辑:
1.扫描过程中,需要新插入的数据页,都被放到 old 区域 ;
2.一个数据页里面有多条记录,这个数据页会被多次访问到,但由于是顺序扫描,这个数据页第一次被访问和最后一次被访问的时间间隔不会超过 1 秒,因此还是会被保留在old 区域;
3.再继续扫描后续的数据,之前的这个数据页之后也不会再被访问到,于是始终没有机会移到链表头部(也就是 young 区域),很快就会被淘汰出去。
可以看到,这个策略最大的收益,就是在扫描这个大表的过程中,虽然也用到了 BufferPool,但是对 young 区域完全没有影响,从而保证了 Buffer Pool 响应正常业务的查询命中率。

1.2.3 LRU存在的问题

无法解决在处理扫描式(例如范围查询或keys *等)单次查询操作时的缓存污染问题

1.2.4 使用建议

1.优先使用allkeys-lru策略
2.如果业务应用中的数据访问频率相差不大,没有明显的冷热数据区分,建议使用allkeys-random策略,随机选择淘汰的数据就行
3.如果业务中有置顶的需求(置顶新闻、置顶视频等),可以使用volatile-lru策略,同时不给这些置顶数据设置过期时间。这样这些需要置顶的数据一直不会被删除,而其他数据会在过期时根据LRU规则进行筛选

1.3 LFU策略

LFU缓存策略是在LRU策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用LFU策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。

1.3.1 对LRU做的优化

1.把原来24bit大小的lru字段拆成,前16bit数据的访问时间戳(分钟为单位),后8bit数据的访问次数(8位最多到255)
2.淘汰数据时从候选集中先选访问次数最少的,访问次数相等时再选时间戳最小的
3.为了解决访问次数过大的问题,redis采用非现线递增方式(递增频率通过lfu_log_factor控制),并不是每访问一次就加1
4.为了解决有些数据短时间内被多次访问,但是后续不再被访问的场景,redis采用衰减机制(通过lfu_decay_time控制访问次数衰减幅度)

Redis(六)-缓存方案-淘汰/过期_第2张图片
lfu_log_factor不同值-不同访问次数时counter数值

1.3.2 使用建议

1.LRU策略更加关注数据的时效性,而LFU策略更加关注数据的访问频次。通常情况下实际应用的负载具有较好的时间局部性,所以LRU策略的应用会更加广泛。但是,在扫描式查询的应用场景比较多时,建议优先使用LFU策略,可以很好地应对缓存污染问题;

2. 缓存过期

缓存过期后数据会被删除

2.1 删除策略

  • 惰性删除: 当一个数据的过期时间到了以后,并不会立即删除数据,而是等到再有请求来读写这个数据时,对数据进行检查,如果发现数据已经过期了,再删除这个数据
  • 定时删除: 每隔一段时间(默认100ms),就会随机选出一定数量的数据,检查它们是否过期,并把其中过期的数据删除

2.2 过期方案

1.从库不会删除数据,3.2版本以后访问到从库过期数据,从库返回空
2.使用EXPIREAT/PEXPIREAT命令设置过期时间,避免从库上的数据过期时间滞后
3.slave-read-only用来控制 slave 是否可写,4.0版本后yes时本身就写在从库的带有过期时间的数据会被从库清理,从主库同步过来的带有过期时间的数据从库不会清理

-------------over------------

你可能感兴趣的:(Redis(六)-缓存方案-淘汰/过期)