一. Redis 提供的缓存淘汰策略
Redis 从 4.0 时代开始,总共提供了以下几种缓存过期替换策略:
- noeviction : 不进行缓存过期淘汰。设置这种策略时,当内存使用率达到设定上限后,如果继续有需要写内存的请求命令到达时,直接给客户端返回错误。当处理只读请求命令时,会依然正常返回结果。
除了 noeviction 之外, 其余策略均会进行缓存淘汰,它们又可粗略划分为以下两大类:
- 在设置了过期时间的缓存数据中进行淘汰
- 在所有数据集合中进行淘汰
当我们往缓存中放置数据时,可以使用 expire
命令给对应的键值对加上过期时间。以下几种淘汰策略的数据范围就限定在了加上了过期时间的键值对数据上,它们分别是:
- volatile-lru : 使用 LRU 算法对设置了过期时间的键值对进行删除。LRU 即 Least Recently Used, 最近最少使用,即最近使用次数最少的数据会被优先淘汰;
- volatile-ttl : 在设置了过期时间的键值对中进行筛选,根据过期时间的先后来进行删除,越早过期的被删除的优先级越高;
- volatile-random : 在设置了过期时间的键值对中,随机选择数据进行删除;
- volatile-lfu : 使用 LFU 算法对设置了过期时间的键值对进行淘汰。LFU 即 Least Frequently Used, 最近最频繁使用,该算法会统计最近数据项被访问的频率,然后选择那些很少被访问到的数据项进行淘汰。
上述淘汰策略是针对第一大类而言的,除此之外,还有针对全部数据范围内的淘汰策略,它们分别是:
- allkeys-lru : 使用 LRU 算法在所有缓存数据中进行数据淘汰;
- allkeys-random : 在所有数据范围内,随机选择数据进行删除;
- allkeys-lfu: 使用 LFU 算法在所有数据范围内进行数据淘汰。
以上八种淘汰策略,是 Redis 自 4.0 之后默认提供的。
Redis Server 在 EventLoop 中会检查内存的使用率,如果发现内存用量已经超过了设置值 maxmemory
的大小,则会根据配置好的淘汰策略来进行内存数据的淘汰。
二. Redis中LRU算法具体是如何工作的
LRU 算法按照最近最少使用的原则来筛选数据。具体是怎么做的呢?
LRU 算法使用一个链表将所有数据组织起来,链表的头表示 MRU 端,尾表示 LRU 端,分别代表最近最常使用的数据位置和最近最少使用的数据位置。在读数据时,当链表中的数据被访问到,则该数据所在节点会被移至MRU端,相应的其他节点都会往靠近LRU端的方向后移。
在写数据时,如果此时链表空间已满,则会将新写入的数据节点移动到 MRU 端,并在 LRU 端将最末尾的节点删除。
而在筛选数据进行删除时,LRU 算法总是从 LRU 端开始,因此越靠近 MRU 端的数据,它们留存在内存中的几率就越大。
由上述的 LRU 过程描述中可以得知,LRU 算法在实际使用时,不可避免要涉及到大量数据节点的移动,这无疑会增大操作耗时,从而造成性能上的损耗。因此,在 Redis 的 LRU 实现中,针对该问题进行了改进。具体而言就是,Redis 默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)。然后,Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。上述过程中提到的随机选出 N 个数据,这个 N 的数值可由配置项 maxmemory-samples
来控制。
三. 如何选择淘汰策略
一般情况下,推荐优先使用 allkeys-lru 策略。该策略可以充分利用 LRU 算法的优势,将最近最常被访问到的热点数据留在内存中,淘汰掉最不常用的数据。由于数据访问通常都有“二八定律”(即80%的时间访问的是20%的热点数据) 这样的规律,在这样的场景下, allkeys-lru 策略显得特别的合适。
如果应用中的数据访问没有明显的热点趋势,那可以选择 allkeys-random 策略。