卡飞资源网

专业编程技术资源共享平台

学会这几个Python缓存技巧,不信领导不给你涨工资!

点赞、收藏、加关注,下次找我不迷路

一、啥是缓存?先把概念搞明白

好多新手一听到 "缓存" 就犯迷糊,其实这玩意儿没那么玄乎。咱打个比方,你去餐厅吃饭,厨师做菜之前,会先把常用的食材比如土豆、洋葱啥的提前切好放在备菜间,这样炒菜的时候直接拿就能用,不用现切,节省时间。这备菜间就是缓存,缓存就是把常用的数据提前存放在一个快速访问的地方,下次需要的时候直接拿出来用,不用再重新计算或者查询,能大大提高程序的运行效率。

比如说,你写了一个函数,用来计算一个很复杂的数值,每次调用都得花好几秒。如果这个函数需要频繁调用,那每次都重新计算就太浪费时间了。这时候就可以把计算结果缓存起来,第一次计算完存起来,后面再调用的时候直接返回缓存的结果,瞬间就快了。


二、缓存有啥用?好处多到你想不到

(一)提高程序运行效率

就像刚才说的计算复杂数值的函数,用了缓存之后,不用重复计算,时间直接节省下来了。尤其是在一些需要频繁调用的函数或者接口中,效果特别明显。比如一个电商网站的商品详情页,每次用户访问都要查询数据库获取商品信息,如果把这些信息缓存起来,用户再次访问的时候就能更快地显示出来,用户体验也更好。

(二)节省资源

重新计算或者查询数据需要消耗 CPU、内存、数据库连接等资源。用了缓存之后,减少了这些操作的次数,也就节省了资源。比如数据库,频繁的查询会给数据库带来很大的压力,甚至可能导致数据库性能下降,而缓存可以分担一部分压力,让数据库更稳定。

(三)提升用户体验

用户最讨厌的就是等待,页面加载慢、接口响应慢都会让用户很不爽。缓存可以让数据更快地返回,减少用户的等待时间,提升用户对系统的满意度。比如一个手机 APP,用户刷新页面时,如果数据是从缓存中获取的,瞬间就能显示出来,用户就会觉得这个 APP 很流畅。


三、Python 里的缓存技巧

(一)内置的 lru_cache 装饰器:简单好用,新手必备

Python 的 functools 模块里有一个 lru_cache 装饰器,简直是新手福音,用起来超简单。lru 是 Least Recently Used 的缩写,意思是最近最少使用,也就是说它会把最近最少使用的缓存数据淘汰掉,以保持缓存的大小在一定范围内。

1. 怎么用?

首先,你得导入 functools 模块里的 lru_cache 装饰器:

from functools import lru_cache

然后,在你想要缓存的函数上面加上 @lru_cache 装饰器就行了。比如说,我们有一个计算斐波那契数列的函数,斐波那契数列的计算是递归的,每次计算都需要重复计算很多中间值,效率很低,这时候就可以用 lru_cache 来缓存结果。

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    else:
        return fib(n-1) + fib(n-2)

这里的 maxsize 参数是用来设置缓存的最大大小的,默认是 128,如果设置为 None,就表示不限制缓存大小,但要注意,这可能会导致内存使用过多。还有一个参数 typed,默认是 False,如果设置为 True,会把不同类型的参数视为不同的键,比如 1 和 1.0 会被视为不同的参数,缓存不同的结果。

2. 效果咋样?用数据说话

我们来对比一下没加缓存和加了缓存的斐波那契函数的运行时间。我们计算 fib (30),没加缓存的时候,运行时间大概是 0.1 秒左右;加了缓存之后,第一次运行时间也是 0.1 秒左右,但是第二次运行的时候,直接从缓存中获取结果,运行时间几乎为 0。我们用表格来呈现一下:

情况

第一次运行时间(秒)

第二次运行时间(秒)

没加缓存

0.1

0.1

加了缓存

0.1

0.0001

可以看到,加了缓存之后,第二次运行时间大大缩短,效率提升明显。

(二)lru_cache 的局限性:别以为它万能,这些情况要注意

虽然 lru_cache 很好用,但它也有一些局限性。首先,它只能缓存那些参数是可哈希的函数,也就是说,参数必须是不可变类型,比如整数、字符串、元组等,如果你传递的是列表、字典等可变类型,就会报错。其次,它的缓存是存储在内存中的,如果数据量很大,会占用很多内存,可能会导致内存不足的问题。另外,它的缓存不会自动过期,一旦缓存了数据,就会一直存在,直到缓存大小超过 maxsize 被淘汰或者程序结束。

比如说,我们有一个函数,需要根据当前时间获取一些数据,每次调用的时间不同,结果也不同,但如果用 lru_cache 缓存的话,它会把不同时间的调用视为不同的参数,缓存不同的结果,这没问题。但是如果我们希望缓存的数据在一段时间后自动过期,比如 10 分钟后就不再使用缓存,lru_cache 就做不到了,这时候就需要用到其他的缓存方案。

(三)其他缓存方案:根据场景选合适的

1. functools.lru_cache 结合 ttl:给缓存加个过期时间

如果我们需要给缓存设置一个过期时间,比如 10 分钟后缓存失效,这时候可以结合使用 functools.lru_cache 和一个自定义的装饰器来实现。不过 Python 本身没有直接提供 ttl(生存时间)功能,需要我们自己实现。

我们可以写一个装饰器,在每次调用函数的时候,检查缓存是否过期,如果过期了就重新计算并更新缓存。这里给大家简单举个例子:

from functools import lru_cache
import time

def ttl_cache(ttl_seconds):
    def decorator(func):
        @lru_cache(maxsize=None)
        def wrapper(*args, **kwargs, __ttl_time=None):
            if __ttl_time is None:
                __ttl_time = time.time()
            current_time = time.time()
            if current_time - __ttl_time > ttl_seconds:
                # 缓存过期,重新计算
                result = func(*args, **kwargs)
                wrapper(*args, **kwargs, __ttl_time=current_time)  # 更新缓存
                return result
            else:
                return func(*args, **kwargs, __ttl_time=__ttl_time)
        return wrapper
    return decorator

@ttl_cache(ttl_seconds=60)  # 设置缓存过期时间为60秒
def get_data():
    # 模拟获取数据的操作,比如查询数据库
    time.sleep(2)
    return "最新数据"

这样,每次调用 get_data 函数时,缓存会在 60 秒后过期,重新获取数据。

2. 第三方库 cachetools:功能更强大,满足更多需求

如果觉得自己实现 ttl 比较麻烦,也可以使用第三方库 cachetools,它提供了更强大的缓存功能,包括 TTL 缓存、LRU 缓存等。首先,你需要安装 cachetools:

pip install cachetools

(1)TTLCache:按时间过期

TTLCache 可以设置每个缓存项的生存时间,超过时间就会自动失效。使用起来很简单:

from cachetools import TTLCache

cache = TTLCache(maxsize=100, ttl=60)  # maxsize是缓存最大大小,ttl是生存时间(秒)

def get_data(key):
    if key in cache:
        return cache[key]
    # 模拟获取数据的操作
    data = "数据" + key
    cache[key] = data
    return data

这样,每个缓存项在 60 秒后就会失效。

(2)LRUCache:按最近最少使用淘汰

LRUCache 和 functools 的 lru_cache 类似,都是根据最近最少使用来淘汰缓存项,但 cachetools 的 LRUCache 可以更灵活地设置参数。

from cachetools import LRUCache

cache = LRUCache(maxsize=100)  # maxsize是缓存最大大小

def get_data(key):
    if key in cache:
        return cache[key]
    # 模拟获取数据的操作
    data = "数据" + key
    cache[key] = data
    return data

3. 数据库缓存:Redis,分布式场景必备

如果你的程序是分布式的,多个服务器节点需要共享缓存,这时候就需要用到数据库缓存,比如 Redis。Redis 是一个高性能的键值对存储数据库,支持丰富的数据结构,并且可以设置缓存过期时间,还支持持久化等功能。

使用 Redis 缓存也很简单,首先需要安装 redis-py 库:

pip install redis

然后连接 Redis 服务器,进行缓存操作:

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def get_data(key):
    data = r.get(key)
    if data:
        return data.decode()
    # 模拟获取数据的操作,比如查询数据库
    data = "数据" + key
    r.set(key, data)
    r.expire(key, 60)  # 设置缓存过期时间为60秒
    return data

Redis 适合在分布式系统中使用,多个节点可以共享缓存,提高系统的性能和可扩展性。


四、缓存也不是万无一失

(一)缓存穿透:查不到的数据,把缓存打穿了

啥是缓存穿透呢?就是用户频繁查询一个不存在的数据,每次查询都要穿透缓存到数据库或者其他数据源去查询,导致数据库压力很大。比如说,有人恶意攻击,频繁查询一个不存在的用户 ID,每次都要去数据库查,而数据库中根本没有这个数据,缓存也没有,就会导致每次请求都打到数据库上。

怎么避免呢?可以在缓存中存储空值或者默认值,比如当查询一个不存在的数据时,在缓存中存一个空值,设置一个较短的过期时间,这样下次再查询的时候,就可以直接从缓存中获取空值,不用去数据库查了。

(二)缓存雪崩:缓存大面积失效,系统扛不住

缓存雪崩是指在同一时间段内,大量的缓存数据同时失效,导致大量的请求直接打到数据库或者其他数据源上,造成系统压力过大,甚至崩溃。比如说,缓存设置的过期时间都是相同的,当过期时间到达时,所有缓存同时失效,这时候大量请求涌进来,就会出现雪崩。

怎么避免呢?可以给缓存的过期时间加上一个随机值,让缓存的失效时间分散开来,不要集中在同一时间段。比如设置过期时间为 60 秒到 100 秒之间的随机值,这样就不会有大量缓存同时失效了。

(三)缓存和数据库一致性问题:缓存和数据库数据不一样了

当我们更新数据库中的数据时,如果没有及时更新缓存或者删除缓存,就会导致缓存中的数据和数据库中的数据不一致。比如说,你修改了用户的昵称,数据库中已经更新了,但缓存中还是旧的昵称,这时候用户查询时就会得到错误的数据。

怎么解决呢?一般有两种方式,一种是更新数据库的同时,更新缓存;另一种是更新数据库的同时,删除缓存,下次查询时重新从数据库加载数据到缓存。这两种方式各有优缺点,需要根据具体场景选择。比如,对于读多写少的场景,删除缓存可能更合适,因为写操作较少,删除缓存后重新加载的成本较低;对于写频繁的场景,更新缓存可能会带来更多的开销,这时候需要权衡。


说了这么多,总结一下:缓存是提高程序效率的重要技巧,Python 里有内置的 lru_cache 装饰器,简单好用,适合新手;还有第三方库 cachetools,功能更强大,能满足不同的需求;分布式场景下可以用 Redis 作为数据库缓存。同时,也要注意缓存带来的问题,避免踩坑。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言