Redis 的缓存淘汰机制(Eviction)

为了适配用作缓存的场景,redis 支持缓存淘汰(eviction)并提供相应的了配置项:

maxmemory

 设置内存使用上限,该值不能设置为小于 1M 的容量。
 选项的默认值为 0,此时系统会自行计算一个内存上限。

maxmemory-policy

 熟悉 redis 的朋友都知道,每个数据库维护了两个字典:

db.dict:数据库中所有键值对,也被称作数据库的 keyspace

db.expires:带有生命周期的 key 及其对应的 TTL(存留时间),因此也被称作 expire set

 当达到内存使用上限maxmemory时,可指定的清理缓存所使用的策略有:

noeviction 当达到最大内存时直接返回错误,不覆盖或逐出任何数据

allkeys-lfu 淘汰整个 keyspace 中最不常用的 (LFU) 键 (4.0 或更高版本)

allkeys-lru 淘汰整个 keyspace 最近最少使用的 (LRU) 键

allkeys-random 淘汰整个 keyspace 中的随机键

volatile-ttl 淘汰 expire set 中 TTL 最短的键

volatile-lfu 淘汰 expire set 中最不常用的键 (4.0 或更高版本)

volatile-lru 淘汰 expire set 中最近最少使用的 (LRU) 键

volatile-random 淘汰 expire set 中的随机键

 当 expire set 为空时,volatile-* 与 noeviction 行为一致。

maxmemory-samples

 为了保证性能,redis 中使用的 LRU 与 LFU 算法是一类近似实现。
 简单来说就是:算法选择被淘汰记录时,不会遍历所有记录,而是以 随机采样 的方式选取部分记录进行淘汰。
 maxmemory-samples 选项控制该过程的采样数量,增大该值会增加 CPU 开销,但算法效果能更逼近实际的 LRU 与 LFU 。

lazyfree-lazy-eviction

 清理缓存就是为了释放内存,但这一过程会阻塞主线程,影响其他命令的执行。
 当删除某个巨型记录(比如:包含数百条记录的 list)时,会引起性能问题,甚至导致系统假死。
 延迟释放 机制会将巨型记录的内存释放,交由其他线程异步处理,从而提高系统的性能。
 开启该选项后,可能出现使用内存超过 maxmemory 上限的情况。

缓存淘汰机制

一个完整的缓存淘汰机制需要解决两个问题:

确定淘汰哪些记录 —— 淘汰策略

删除被淘汰的记录 —— 删除策略

淘汰策略

缓存能使用的内存是有限的,当空间不足时,应该优先淘汰那些将来不再被访问的数据,保留那些将来还会频繁访问的数据。因此淘汰算法会围绕 时间局部性 原理进行设计,即:如果一个数据正在被访问,那么在近期很可能会被再次访问

为了适应缓存读多写少的特点,实际应用中会使用哈希表来实现缓存。当需要实现某种特定的缓存淘汰策略时,需要引入额外的簿记 book keeping 结构。

下面回顾 3 种最常见的缓存淘汰策略。

FIFO (先进先出)

 越早进入缓存的数据,其不再被访问的可能性越大。
 因此在淘汰缓存时,应选择在内存中停留时间最长的缓存记录。

 使用队列即可实现该策略:
 


Redis 的缓存淘汰机制(Eviction)

 优点:实现简单,适合线性访问的场景
 缺点:无法适应特定的访问热点,缓存的命中率差
 簿记开销:时间 O(1),空间 O(N)

LRU (最近最少使用)

 一个缓存被访问后,近期再被访问的可能性很大。
 可以记录每个缓存记录的最近访问时间,最近未被访问时间最长的数据会被首先淘汰。

 使用链表即可实现该策略:
 


Redis 的缓存淘汰机制(Eviction)

 当更新 LRU 信息时,只需调整指针:
 


Redis 的缓存淘汰机制(Eviction)

 优点:实现简单,能适应访问热点
 缺点:对偶发的访问敏感,影响命中率
 簿记开销:时间 O(1),空间 O(N)

LRU 改进

 原始的 LRU 算法缓存的是最近访问了 1 次的数据,因此不能很好地区分频繁和不频繁缓存引用。
 这意味着,部分冷门的低频数据也可能进入到缓存,并将原本的热点记录挤出缓存。
 为了减少偶发访问对缓存的影响,后续提出的 LRU-K 算法作出了如下改进:

在 LRU 簿记的基础上增加一个历史队列 History Queue

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgwddd.html