网友通过本文主要向大家介绍了redis lru,lru策略,lru算法实现,lru页调度算法实现,编程实现lru算法等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com
redis lru实现策略
在使用redis作为缓存的场景下,内存淘汰策略决定的redis的内存使用效率。在大部分场景下,我们会采用LRU(Least Recently Used)来作为redis的淘汰策略。本文将由浅入深的介绍redislru策略的具体实现。首先我们来科普下,什么是LRU ?(以下来自维基百科)
Discards the least recently used items first. This algorithm requires keeping track of what was used when, which is expensive if one wants to make sure the algorithm always discards the least recently used item. General implementations of this technique require keeping "age bits" for cache-lines and track the "Least Recently Used" cache-linebased on age-bits. In such an implementation, every time a cache-line is used, the age of all other cache-lines changes.
简而言之,就是每次淘汰最近最少使用的元素。一般的实现,都是采用对存储在内存的元素采用'agebits’来标记该元素从上次访问到现在为止的时长,从而在每次用LRU淘汰时,淘汰这些最长时间未被访问的元素。
这里我们先实现一个简单的LRUCache,以便于后续内容的理解 。(来自leetcod,不过这里我重新用Python语言实现了) 实现该缓存满足如下两点:
1.get(key) - 如果该元素(总是正数)存在,将该元素移动到lru头部,并返回该元素的值,否则返回-1。
2.set(key,value) - 设置一个key的值为value(如果该元素存在),并将该元素移动到LRU头部。否则插入一个key,且值为value。如果在设置前检查到,该key插入后,会超过cache的容量,则根据LRU策略,删除最近最少使用的key。
分析
这里我们采用双向链表来实现元素(k-v键值对)的存储,同时采用hash表来存储相关的key与item的对应关系。这样,我们既能在O(1)的时间对key进行操作,同时又能利用DoubleLinkedList的添加和删除节点的便利性。(get/set都能在O(1)内完成)。

具体实现(Python语言)
通过上面简单的介绍与实现,现在我们基本已经了解了什么是LRU,下面我们来看看LRU算法在redis 内部的实现细节,以及其会在什么情况下带来问题。在redis内部,是通过全局结构体struct redisServer 保存redis启动之后相关的信息,比如:
- class Node:
- key=None
- value=None
- pre=None
- next=None
- def __init__(self,key,value):
- self.key=key
- self.value=value
- class LRUCache:
- capacity=0
- map={} # key is string ,and value is Node object
- head=None
- end=None
- def __init__(self,capacity):
- self.capacity=capacity
- def get(self,key):
- if key in self.map:
- node=self.map[key]
- self.remove(node)
- self.setHead(node)
- return node.value
- else:
- return -1
- def getAllKeys(self):
- tmpNode=None
- if self.head:
- tmpNode=self.head
- while tmpNode:
- print (tmpNode.key,tmpNode.value)
- tmpNode=tmpNode.next
- def remove(self,n):
- if n.pre:
- n.pre.next=n.next
- else:
- self.head=n.next
- if n.next:
- n.next.pre=n.pre
- else:
- self.end=n.pre
- def setHead(self,n):
- n.next=self.head
- n.pre=None
- if self.head:
- self.head.pre=n
- self.head=n
- if not self.end:
- self.end=self.head
- def set(self,key,value):
- if key in self.map:
- oldNode=self.map[key]
- oldNode.value=value
- self.remove(oldNode)
- self.setHead(oldNode)
- else:
- node=Node(key,value)
- if len(self.map) >= self.capacity:
- self.map.pop(self.end.key)
- self.remove(self.end)
- self.setHead(node)
- else:
- self.setHead(node)
- self.map[key]=node
- def main():
- cache=LRUCache(100)
- #d->c->b->a
- cache.set('a','1')
- cache.set('b','2')
- cache.set('c',3)
- cache.set('d',4)
- #遍历lru链表
- cache.getAllKeys()
- #修改('a','1') ==> ('a',5),使该节点从LRU尾端移动到开头.
- cache.set('a',5)
- #LRU链表变为 a->d->c->b
- cache.getAllKeys()
- #访问key='c'的节点,是该节点从移动到LRU头部
- cache.get('c')
- #LRU链表变为 c->a->d->b
- cache.getAllKeys()
- if __name__ == '__main__':
- main()
redisServer 中包含了redis服务器启动之后的基本信息(PID,配置文件路径,serverCron运行频率hz等),外部可调用模块信息,网络信息,RDB/AOF信息,日志信息,复制信息等等。 我们看到上述结构体中lruclock:LRU_BITS,其中存储了服务器自启动之后的lru时钟,该时钟是全局的lru时钟。该时钟100ms(可以通过hz来调整,默认情况hz=10,因此每1000ms/10=100ms执行一次定时任务)更新一次。
- struct redisServer {
- pid_t pid; /* Main process pid. */
- char *configfile; /* Absolute config file path, or NULL */
- …..
- unsigned lruclock:LRU_BITS; /* Clock for LRU eviction */
- ...
- };
接下来我们看看LRU时钟的具体实现:
因此lrulock最大能到(2**24-1)/3600/24= 194天,如果超过了这个时间,lrulock重新开始。对于redisserver来说,server.lrulock表示的是一个全局的lrulock,那么对于每个redisObject都有一个自己的lrulock。这样每redisObject就可以根据自己的lrulock和全局的server.lrulock比较,来确定是否能够被淘汰掉。
- server.lruclock = getLRUClock();
- getLRUClock函数如下:
- #define LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
- #define LRU_BITS 24
- #define LRU_CLOCK_MAX ((1<
lru */ - /* Return the LRU clock, based on the clock resolution. This is a time
- * in a reduced-bits format that can be used to set and check the
- * object->lru field of redisObject structures. */
- unsigned int getLRUClock(void) {
- return (mstime()/LRU_CLOCK_RESOLUTION) & LRU_CLOCK_MAX;
- }
redis key对应的value的存放对象:
- typedef struct redisObject {
- unsigned type:4;
- unsigned encoding:4;
- unsigned lru:LRU_BITS; /* LRU time (relative to server.lruclock) or
- * LFU data (least significant 8 bits frequency
- * and most significant 16 bits decreas time). */
- int refcount;
- void *ptr;
- } robj
那么什么时候,lru会被更新呢 ?访问该key,lru都会被更新,这样该key就能及时的被移动到lru头部,从而避免从lru中淘汰。下面是这一部分的实现:
- /* Low level key lookup API, not actually called directly from commands
- * implementations that should instead rely on lookupKeyRead(),
- * lookupKeyWrite() and lookupKeyReadWithFlags(). */
- robj *lookupKey(redisDb *db, robj *key, int flags) {
- dictEntry *de = dictFind(db->dict,key->ptr);
- if (de) {