Redis知识梳理(23) [ 缓存算法法 LRU ]

当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来 说,这样龟速的存取效率基本上等于不可用。

在生产环境中我们是不允许 Redis 出现交换行为的,为了限制最大使用内存, Redis 提供了配置参数 maxmemory 来限制内存超出期望大小。

当实际内存超出 maxmemory 时,Redis 提供了几种可选策略 (maxmemory- policy) 来让用户自己决定该如何腾出新的空间以继续提供读写服务。

  1. noeviction 不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进 行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认 的淘汰策略。
  2. volatile-lru 尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没 有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢 失。
  3. volatile-ttl 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。
  4. volatile-random 跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key。
  5. allkeys-lru 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集 合,而不只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘 汰。
  6. allkeys-random 跟上面一样,不过淘汰的策略是随机的 key。

volatile-xxx 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对 所有的 key 进行淘汰。

如果你只是拿 Redis 做缓存,那应该使用 allkeys-xxx, 客户端写缓存时不必携带过期时间。

如果你还想同时使用 Redis 的持久化功能, 那就使用 volatile-xxx 策略,

这样可以保留没有设置过期时间的 key,它们是永 久的 key 不会被 LRU 算法淘汰。

LRU 算法

实现 LRU 算法除了需要 key/value 字典外,还需要附加一个链表,链表中的元 素按照一定的顺序进行排列。当空间满的时候,会踢掉链表尾部的元素。当字典 的某个元素被访问时,它在链表中的位置会被移动到表头。所以链表的元素排列 顺序就是元素最近被访问的时间顺序。

位于链表尾部的元素就是不被重用的元素,所以会被踢掉。位于表头的元素就是最近刚刚被人用过的元素,所以暂时不会被踢。

下面我们使用 Python 的 OrderedDict(双向链表 + 字典) 来实现一个简单的 LRU 算法。

​ from collections import OrderedDict

​ class LRUDict(OrderedDict):

​ def init(self, capacity):
self.capacity = capacity
self.items = OrderedDict()

​ def setitem(self, key, value):
old_value = self.items.get(key)
if old_value is not None:
self.items.pop(key)
self.items[key] = value
elif len(self.items) < self.capacity:
self.items[key] = value
else:
self.items.popitem(last=True)
self.items[key] = value

​ def getitem(self, key):
value = self.items.get(key)
if value is not None:
self.items.pop(key)
self.items[key] = value
return

​ def repr(self):
return repr(self.items)

​ d = LRUDict(10)

​ for i in range(15):
d[i] = i
print d

近似 LRU 算法

LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

Redis 使用的是一种近似 LRU 算法,它跟 LRU 算法还不太一样。之所以不使用 LRU 算法,是因为需要消耗大量的额外的内存,需要对现有的数据结构进行较大 的改造。近似 LRU 算法则很简单,在现有数据结构的基础上使用 随机采样法来 淘汰元素,能达到和 LRU 算法非常近似的效果。

Redis 为实现近似 LRU 算法, 它给每个 key 增加了一个额外的小字段,这个字段的长度是 24 个 bit,也就是 最后一次被访问的时间戳

上一节提到处理 key 过期方式分为集中处理和懒惰处理,LRU 淘汰不一样,它 的处理方式只有懒惰处理。当 Redis 执行写操作时,发现内存超出 maxmemory,就会执行一次 LRU 淘汰算法。这个算法也很简单,就是随机采 样出 5(可以配置) 个 key,然后淘汰掉最旧的 key,如果淘汰后内存还是超出 maxmemory,那就继续随机采样淘汰,直到内存低于 maxmemory 为止。

如何采样就是看 maxmemory-policy 的配置,如果是 allkeys 就是从所有的 key 字典中随机,如果是 volatile 就从带过期时间的 key 字典中随机。

每次采样 多少个 key 看的是 maxmemory_samples 的配置,默认为 5。

下面是随机 LRU 算法和严格 LRU 算法的效果对比图:

img

图中绿色部分是新加入的 key,深灰色部分是老旧的 key,浅灰色部分是通过 LRU 算法淘汰掉的 key。从图中可以看出采样数量越大,近似 LRU 算法的效果 越接近严格 LRU 算法。同时 Redis3.0 在算法中增加了淘汰池,进一步提升了近 似 LRU 算法的效果。

淘汰池是一个数组,它的大小是 maxmemory_samples,在每一次淘汰循环中,新随机出来的 key 列表会和淘汰池中的 key 列表进行融合,淘汰掉最旧的一个 key 之后,保留剩余较旧的 key 列表放入淘汰池中留待下一个循环。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值