lru_cache和cache原理

举报
子都爱学习 发表于 2021/10/23 18:07:02 2021/10/23
【摘要】 LRULRU (Least Recently Used) 是缓存置换策略中的一种常用的算法。当缓存队列已满时,新的元素加入队列时,需要从现有队列中移除一个元素,LRU 策略就是将最近最少被访问的元素移除,从而腾出空间给新的元素。python中的实现python3中的functools模块的lru_cache实现了这个功能lru_cache查看源码解释:Least-recently-used ...

LRU
LRU (Least Recently Used) 是缓存置换策略中的一种常用的算法。当缓存队列已满时,新的元素加入队列时,需要从现有队列中移除一个元素,LRU 策略就是将最近最少被访问的元素移除,从而腾出空间给新的元素。

python中的实现
python3中的functools模块的lru_cache实现了这个功能
lru_cache查看源码解释:Least-recently-used cache decorator.
lru_cache装饰器会记录以往函数运行的结果,实现了备忘(memoization)功能,避免参数重复时反复调用,达到提高性能的作用,在递归函数中作用特别明显。这是一项优化技术,它把耗时的函数的结果保存起来,避免传入相同的参数时重复计算。

cache使用场景:1.频繁使用 2.每一次获取代价高 3.一定时间内具有幂等性 4.压力大 5.预热(提前存入cache)


lru_cache(maxsize=128, typed=False)
maxsize为最多缓存的次数,如果为None,则无限制,设置为2的n次幂时,性能最佳;
typed=True,则不同参数类型的调用将分别缓存,默认为False

实现原理

def lru_cache(maxsize=128, typed=False):
    if isinstance(maxsize, int):
        if maxsize < 0:
            maxsize = 0
    elif maxsize is not None:
        raise TypeError('Expected maxsize to be an integer or None')

    def decorating_function(user_function):
        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
        return update_wrapper(wrapper, user_function)

    return decorating_function

基本用法:

from functools import lru_cache

# Least-recently-used cache decorator.
# 缓存 -》 命中

import time
@lru_cache()    # 3.8后内部处理 lru_cache(add) 等价于 lru_cache()(add)
def add(x, y):
    print("-----")
    time.sleep(2)
    return x + y

add(3, 4)
# -----
# 7
add(3, 4) # 缓存命中
# 7
add(x=3, y=4)
# -----
# 7
add(y=4, x=3)
# -----
# 7


实战使用:

1.计算斐波那契数列第n项

from functools import  lru_cache
@lru_cache()
def fib(n):
    return 1 if n<3 else fib(n-1)+fib(n-2)
fib(20)
# 6765

fib(200)
# 280571172992510140037611932413038677189525

# # lru_cache 默认是128个key,为什么fib(200)还这么快?
# # 最近最少使用的key会删除, 对于计算fib(200)只依赖199和198,换出的是前面很久没有使用的,fib(1)等

2.在我们编写接口时可能需要缓存一些变动不大的数据如配置信息,我们可能编写如下接口:

@api.route("/user/info", methods=["GET"])
@functools.lru_cache()
@login_require
def get_userinfo_list():
    userinfos = UserInfo.query.all()
    userinfo_list = [user.to_dict() for user in userinfos]
    return jsonify(userinfo_list)

​ 我们缓存了从数据库查询的用户信息,下次再调用这个接口时将直接返回用户信息列表而不需要重新执行一遍数据库查询逻辑,可以有效较少IO次数,加快接口反应速度。

2.1 进阶用法

​ 还是以上面的例子,如果发生用户的删除或者新增时,我们再请求用户接口时仍然返回的是缓存中的数据,这样返回的信息就和我们数据库中的数据就会存在差异,所以当发生用户新增或者删除时,我们需要清除原先的缓存,然后再请求用户接口时可以重新加载缓存。

@api.route("/user/info", methods=["POST"])
@functools.lru_cache()
@login_require
def add_user():
    user = UserInfo(name="李四")
    db.session.add(user)
    db.session.commit()
    
    # 清除get_userinfo_list中的缓存
    get_userinfo_list = current_app.view_functions["api.get_machine_list"]
    cache_info = get_userinfo_list.cache_info()
    # cache_info 具名元组,包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize 和 当前缓存大小 currsize
    # 如果缓存数量大于0则清除缓存
    if cache_info[3] > 0:
    	get_userinfo_list.cache_clear()
    return jsonify("新增用户成功")

在上面这个用法中我们,如果我们把lru_cache装饰器和login_require装饰器调换位置时,上述的写法将会报错,这是因为login_require装饰器中用了functiontools.wrap模块进行装饰导致的,具原因我们在下节解释, 如果想不报错得修改成如下写法。

@api.route("/user/info", methods=["POST"])
@login_require
@functools.lru_cache()
def add_user():
    user = UserInfo(name="李四")
    db.session.add(user)
    db.session.commit()
    
    # 清除get_userinfo_list中的缓存
    get_userinfo_list = current_app.view_functions["api.get_machine_list"]
    cache_info = get_userinfo_list.__wrapped__.cache_info()
    # cache_info 具名元组,包含命中次数 hits,未命中次数 misses ,最大缓存数量 maxsize 和 当前缓存大小 currsize
    # 如果缓存数量大于0则清除缓存
    if cache_info[3] > 0:
    	get_userinfo_list.__wrapped__.cache_clear()
    return jsonify("新增用户成功")

2.2 functiontools.wrap装饰器对lru_cache的影响

​ 在上节我们看到,因为@login_require和@functools.lru_cache()装饰器的顺序不同, 就导致了程序是否报错, 其中主要涉及到两点:

  • login_require装饰器中是否用了@functiontools.wrap()装饰器
  • @login_require和@functools.lru_cache()装饰器的执行顺序问题

当我们了解完这两点后就可以理解上述写法了。

2.3 多个装饰器装饰同一函数时的执行顺序

​ 这里从其他地方盗了一段代码来解释一下,如下:

def decorator_a(func):
    print('Get in decorator_a')
    def inner_a(*args,**kwargs):
        print('Get in inner_a')
        res = func(*args,**kwargs)
        return res
    return inner_a

def decorator_b(func):
    print('Get in decorator_b')
    def inner_b(*args,**kwargs):
        print('Get in inner_b')
        res = func(*args,**kwargs)
        return res
    return inner_b


@decorator_b
@decorator_a
def f(x):
    print('Get in f')
    return x * 2

f(1)

输出结果如下:

'Get in decorator_a'
'Get in decorator_b'
'Get in inner_b'
'Get in inner_a'
'Get in f'

lru_cache缓存和redis缓存的区别

比较类型 lru_cache redis
缓存类型 缓存在app进程内存中 缓存在redis管理的内存中
分布式 只缓存在单个app进程中 可做分布式缓存
数据类型 hash 参数作为key,返回结果为value 有5种类型的数据结构
适用场景 比较小型的系统、单体应用 常用的缓存解决方案
功能 缓存功能但是缺少过期时间控制,但是使用上更加便捷 具备缓存需要的各种要素

总结

​ 综上所述,python自带的缓存功能使用于稍微小型的单体应用。优点是可以很方便的根据传入不同的参数缓存对应的结果, 并且可以有效控制缓存的结果数量,在超过设置数量时根据LRU算法淘汰命中次数最少的缓存结果。缺点是没有办法对缓存过期时间进行设置。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。