前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >lru_cache和cache原理

lru_cache和cache原理

作者头像
玖柒的小窝
修改2021-10-25 10:12:41
8700
修改2021-10-25 10:12:41
举报
文章被收录于专栏:各类技术文章~各类技术文章~

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

实现原理:

代码语言:javascript
复制
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

基本用法:

代码语言:javascript
复制
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项

代码语言:javascript
复制
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.在我们编写接口时可能需要缓存一些变动不大的数据如配置信息,我们可能编写如下接口:

代码语言:javascript
复制
@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 进阶用法

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

代码语言:javascript
复制
@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模块进行装饰导致的,具原因我们在下节解释, 如果想不报错得修改成如下写法。

代码语言:javascript
复制
@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 多个装饰器装饰同一函数时的执行顺序

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

代码语言:javascript
复制
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)

输出结果如下:

代码语言:javascript
复制
'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算法淘汰命中次数最少的缓存结果。缺点是没有办法对缓存过期时间进行设置。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2.1 进阶用法
  • 2.2 functiontools.wrap装饰器对lru_cache的影响
    • 2.3 多个装饰器装饰同一函数时的执行顺序
    • lru_cache缓存和redis缓存的区别
    • 总结
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档