Redis LRU
Redis LRU
应用背景
当Redis内存超出物理内存限制时, 内存的数据会开始和磁盘产生频繁的交换(swap)。交换会让Redis的性能急剧下降, 对于访问量比较频繁的Redis来说, 这样龟速的存取效率基本上等于不可用。
在生成环境中我们是不允许Redis出现交换行为的, 为了限制最大使用内存,Redis提供了配置参数maxmemory来限制内存超出期望大小。
当实际内存超出maxmemory时, Redis提供了几种可选策略(maxmemory-policy)来让用户自己决定该如何腾出新的空间以继续提供读写服务。
- noeviction 不会继续服务写请求(DEL请求可以继续服务),读请求可以继续进行。这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。
- volatile-lru 尝试淘汰设置了过期时间的key,最少使用的key优先被淘汰, 没有设置过期时间的key不会被淘汰, 这样可以保证需要持久化的数据不会突然丢失。
- volatile-ttl 和上面一样, 除了淘汰的策略不是LRU, 而是key的剩余ttl的值, ttl越小越优先被淘汰
- volatile-random 和上面一样,从以设置过期时间的数据中任意选择数据淘汰。
- allkeys-lru 区别于volatile-lru,这个策略要淘汰的key对象是全体的key集合, 而不是过期的key集合。这意味着没有设置过期时间的key也会被淘汰。
- allkeys-random 和上面一样, 不过淘汰的策略是随机的key
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 value
def __repr__(self):
return repr(self.items)
d = LRUDict(10)
for i in range(15):
d[i] = i
print(d)
近似LRU算法
Redis使用的是一种近似LRU算法, 它跟LRU算法不太一样, 之所以不使用LRU算法, 是因为需要消耗大量的额外内存,需要对现有的数据结果进行较大的改造。近似LRU算法则很简单, 在现有数据结构的基础上使用随机采样法来淘汰元素, 能达到和LRU算法非常近似的效果。 Redis为实现近似LRU算法, 它给每个key增加了一个额外的小字段, 这个字段的长度是24bit, 也就是最后一次被访问的时间戳。
处理key过期的方式分为集中式处理和懒惰处理,LRU淘汰不一样, 它的处理方式只有懒惰处理。当Redis执行写操作时, 发现内存超出maxmemory,就会执行一次LRU淘汰算法。这个算法也很简单, 就是随机采样出5(可以配置)个key, 然后淘汰掉最旧的key, 如果淘汰后内存还是存在超出maxmemory, 那就继续随机采样淘汰, 直到内存低于maxmemory为止。
如何采用就看maxmemory-policy的配置, 如果是allkeys就是是从所有的key字典中随机, 如果是volatile就是从过期的key字典中随机。每次采用多少key看的是maxmemory——samples的配置, 默认是5。