1、LRU算法
在内存不够的情况下,需要清除掉一些不太常用的缓存内容,保留缓存空间内数据的热度。LRU算法:Least Recent Used。淘汰掉最近最久未使用的缓存,来释放空间。
#!/usr/bin/python
# -*- coding: <utf-8> -*-
"""
LRU: Least Recent Used
"""
class Node(object):
"""
双向链表节点
"""
def __init__(self, key, value):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRU(object):
"""
LRU算法,维护一个双向链表,设定一个最大容量,将最新的数据插在链表头,如果超出最大容量,则将链表尾的数据移除
可以保证链表内的数据都是较新的热数据,越靠近头部的数据越新,反之越旧,容易被淘汰
"""
def __init__(self, capacity):
self.set_cap(capacity)
# 如果单纯用双链表来实现LRU,会出现get的时间复杂度为O(n)
# 所以将节点数据储存在一个hashmap中,用字典来代替,保证get和set的时间复杂度都为O(1)
self.node_map = {}
self.init_chain()
def init_chain(self):
self.head_node = Node(-1, -1)
self.tail_node = Node(-1, -1)
self.head_node.next = self.tail_node
self.tail_node.prev = self.head_node
def set_cap(self, capacity):
self.capacity = max(1, capacity)
def mv_to_head(self, node):
# 节点不在map中
if node.key not in self.node_map:
# 达到最大容量
if len(self.node_map) == self.capacity:
# 移除最后节点
self.remove_tail_node()
# 节点在map中
else:
# 如果只有一个节点或者第一个节点为node,则更新value
if len(self.node_map) == 1 or self.head_node.next.key == node.key:
self.node_map[node.key].value = node.value
return
# 将原有的节点从链表中取出
old_node = self.node_map[node.key]
old_node.next.prev = old_node.prev
old_node.prev.next = old_node.next
# 将新节点放在链表头
node.next = self.head_node.next
node.prev = self.head_node
self.head_node.next.prev = node
self.head_node.next = node
# 将新节点放入map,如果已经有该节点则更新value
self.node_map[node.key] = node
def remove_tail_node(self):
# map为空
if len(self.node_map) == 0:
return
# 找到最后一个节点
node = self.tail_node.prev
# 将倒数第二个节点置为最后一个
self.tail_node.prev = node.prev
node.prev.next = self.tail_node
# map中删除节点
if node.key not in self.node_map:
return
else:
del self.node_map[node.key]
def remove(self, key):
if key in self.node_map:
self.node_map[key].next.prev = self.node_map[key].prev
self.node_map[key].prev.next = self.node_map[key].next
del self.node_map[key]
return True
else:
return None #raise ValueError("can\'t find key[{}]".format(key))
def set_value(self, key, value):
node = Node(key, value)
self.mv_to_head(node)
return True
def get_value(self, key):
if key in self.node_map:
self.mv_to_head(self.node_map[key])
return self.node_map[key].value
else:
return None
def show(self):
if len(self.node_map) > 0:
print("head node")
node = self.head_node.next
while node != self.tail_node:
print("node: {}-->{}".format(node.key, node.value))
node = node.next
print("tail node")
return True
else:
return False
if __name__ == "__main__":
import sys
lru = LRU(5)
def show_help_info():
print("1、set node")
print("2、remove node")
print("3、get node")
print("4、show chain")
print("5、show help info")
print("0、exit")
show_help_info()
while True:
try:
print("#################################")
keyIn = int(input("pls enter a ctrl num:"))
except:
print("pls enter an integer.")
continue
if keyIn == 0:
print("user exit.")
sys.exit()
elif keyIn == 4:
if not lru.show():
print("chain is empty")
elif keyIn == 5:
show_help_info()
else:
key = input("pls enter key:")
if keyIn == 1:
value = input("pls enter value:")
if lru.set_value(key, value):
print("set key[{}] succ, value:{}".format(key, value))
elif keyIn == 2:
if lru.remove(key):
print("remove key[{}] succ.".format(key))
else:
print("can\'t find key[{}].".format(key))
elif keyIn == 3:
value = lru.get_value(key)
if not value:
print("can\'t find key[{}]".format(key))
else:
print("key[{}]-->value:{}".format(key, value))
else:
print("pls enter a num 0~4.")
2、LRU的问题
大部分情况下,LRU算法对热点的命中率很高,
但如果突然大量偶发性的数据访问,会让内存中存放大量冷数据,也即是缓存污染。
会引起LRU无法命中热点数据,导致缓存系统命中率急剧下降,也可以使用LRU-K、2Q、MQ等变种算法来提高命中率。
https://www.cnblogs.com/mushroom/p/4278275.html
https://blog.csdn.net/constant_zyh188/article/details/73350576
3、redis中 内存回收
通过配置redis.conf中的几个选项来设置内存回收
maxmemory:最大内存
maxmemory-policy:内存回收策略,选项有以下几种
- noenviction:不清除数据,只是返回错误,这样会导致浪费掉更多的内存,对大多数写命令(DEL 命令和其他的少数命令例外)
- allkeys-lru:从所有的数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,以供新数据使用
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰,以供新数据使用
- allkeys-random:从所有数据集(server.db[i].dict)中任意选择数据淘汰,以供新数据使用
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰,以供新数据使用
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,以供新数据使用
适用的情况
- 如果期望用户请求呈现幂律分布(power-law distribution),也就是,期望一部分子集元素被访问得远比其他元素多时,可以使用allkeys-lru策略。在你不确定时这是一个好的选择。
- 如果期望是循环周期的访问,所有的键被连续扫描,或者期望请求符合平均分布(每个元素以相同的概率被访问),可以使用allkeys-random策略。
- 如果你期望能让 Redis 通过使用你创建缓存对象的时候设置的TTL值,确定哪些对象应该是较好的清除候选项,可以使用volatile-ttl策略。
当你想使用单个Redis实例来实现缓存和持久化一些键,allkeys-lru和volatile-random策略会很有用。但是,通常最好是运行两个Redis实例来解决这个问题。
maxmemory-samples:redis3.0以后 设置一个回收候选池,用户控制回收是采样数量的大小。默认为5 ,越大则消耗cpu越大,性能越接近于LRU