2019-10-23 LRU算法及redis中内存回收

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中 内存回收

http://ifeve.com/redis-lru/

通过配置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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值