面试题答案
一键面试利用Redis链表特性设计LRU缓存淘汰策略的实现思路
- 数据结构:
- 使用Redis的双向链表(Redis的链表实现为双向链表)来维护缓存数据的访问顺序。链表中的每个节点对应一个缓存数据项。
- 同时使用一个哈希表(Redis的哈希结构)来存储缓存数据,哈希表的键为缓存数据的标识,值为指向链表中对应节点的指针。这样可以在O(1)时间复杂度内根据键找到对应的链表节点。
- 访问操作:
- 当缓存命中时,将对应链表节点移动到链表头部,表示该数据最近被访问。如果是新数据,则在链表头部插入新节点,并在哈希表中添加相应记录。
- 淘汰操作:
- 当缓存满时,删除链表尾部的节点(最近最少使用的节点),并同时从哈希表中删除相应的记录。
关键代码片段(以Python为例,借助redis - py库模拟实现)
import redis
# 初始化Redis连接
r = redis.Redis(host='localhost', port=6379, db = 0)
# 链表节点类
class Node:
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity):
self.capacity = capacity
self.cache = {}
self.head = Node(None, None)
self.tail = Node(None, None)
self.head.next = self.tail
self.tail.prev = self.head
# 将节点移动到链表头部
def move_to_head(self, node):
self.remove_node(node)
self.add_to_head(node)
# 在链表头部添加节点
def add_to_head(self, node):
node.next = self.head.next
node.prev = self.head
self.head.next.prev = node
self.head.next = node
# 删除节点
def remove_node(self, node):
node.prev.next = node.next
node.next.prev = node.prev
# 删除尾部节点
def pop_tail(self):
node = self.tail.prev
self.remove_node(node)
return node
# 获取缓存值
def get(self, key):
if key in self.cache:
node = self.cache[key]
self.move_to_head(node)
return node.value
return -1
# 设置缓存值
def put(self, key, value):
if key in self.cache:
node = self.cache[key]
node.value = value
self.move_to_head(node)
else:
new_node = Node(key, value)
self.cache[key] = new_node
self.add_to_head(new_node)
if len(self.cache) > self.capacity:
removed = self.pop_tail()
del self.cache[removed.key]
实际Redis中LRU相关实现说明
在实际Redis中,虽然没有完全按照上述纯链表 + 哈希表的方式实现LRU。Redis采用了一种近似LRU算法,每个对象头中记录了一个24位的lru字段,记录对象最后一次被访问的时间戳。当需要淘汰时,随机采样一定数量的键,然后淘汰lru字段值最小(最久未被访问)的键。通过调整采样数量等参数,可以在性能和精确性之间取得平衡。