Python 缓存滥用导致的系统问题

1次阅读

@lru_cache默认无过期且不共享,易致内存溢出、数据不一致;应显式设maxsize、避免直接用于web视图、禁用不可哈希参数、勿缓存orm实例、多进程须改用redis等外部缓存。

Python 缓存滥用导致的系统问题

缓存没设过期时间,@lru_cache 会吃光内存

python@lru_cache 默认不设最大容量(maxsize=None),且完全不处理时间过期。一旦函数被高频调用、参数组合多(比如带用户 ID、时间戳、分页 offset),缓存项会无限增长,最终拖慢 GC、触发 OOM。

实操建议:

  • 显式指定 maxsize:例如 @lru_cache(maxsize=128),避免默认的“无上限”陷阱
  • 别在长期运行的服务里(如 flask/fastapi 视图函数)直接套 @lru_cache,它绑定的是函数对象,跨请求不清理,也不感知上下文变化
  • 如果真需要时间过期,换用 functools.cache 不行——它连 maxsize 都不支持;得用 cacheoutdiskcache 这类带 TTL 的库

functools.cache 看似简单,但参数必须可哈希

functools.cache@lru_cache(maxsize=None) 的简化 alias,底层仍靠 hash() 做键。传入 dict、list、set、dataclass(未设 frozen=True)这类不可哈希对象,会直接抛 TypeError: unhashable type

常见错误现象:本地测试用固定字符串没问题,一上生产传了 request.args(Flask 的 ImmutableMultiDict)就崩。

立即学习Python免费学习笔记(深入)”;

实操建议:

  • 检查所有参数类型:用 hash(arg) 手动试一遍,尤其注意嵌套结构
  • 把复杂参数转成规范字符串键:例如 json.dumps(sorted(request_args.items()), sort_keys=True),再喂给缓存函数
  • 别依赖 ide 提示——functools.cache 不做运行时类型校验,错只在第一次调用时暴露

django/Flask 里混用 lru_cache数据库查询,结果不一致

缓存函数若封装Model.objects.get()session.execute(),它的返回值是首次执行时的 ORM 实例或 Row 对象。后续调用返回的是同一个 Python 对象引用,而数据库可能已被其他进程/线程更新——你读到的是“旧状态+新内存地址”,看似没报错,数据却静默过期。

使用场景典型如:用户权限判断函数加了缓存,但管理员后台刚改完权限,前端刷新页面仍显示旧菜单。

实操建议:

  • 缓存只存原始数据(如 user_id → {'is_admin': True, 'roles': [...]}),别缓存 ORM model 实例
  • 如果必须复用对象,加一层浅拷贝:return copy.deepcopy(cached_obj),但注意性能开销
  • 更稳妥的做法:用框架原生缓存(如 Django 的 cache.get() + cache.set()),它们天然支持序列化/反序列化,也方便配 Redis 后端统一失效

多进程下 @lru_cache 完全不共享,还浪费内存

Python 多进程(multiprocessinggunicorn worker、Celery task)中,每个子进程都有独立的 Python 解释器和内存空间。@lru_cache 缓存在各自进程内维护,彼此隔离。结果就是:10 个 worker 各自缓存同一份数据 10 次,内存翻 10 倍,且无法协同失效。

性能影响明显:本想靠缓存降 DB 压力,实际每进程都照查不误;更糟的是,你以为加了缓存就安全了,监控里 DB QPS 却纹丝不动。

实操建议:

  • 确认部署模型:Gunicorn 用 --preload 也不能共享 lru_cache,因为 fork 后各进程仍初始化独立缓存
  • 跨进程场景一律改用外部缓存:Redis、memcached,哪怕本地用 diskcache.Cache() 也比进程内缓存强
  • 如果只是想避免重复初始化(如加载大配置文件),用模块级变量 + if 'config' not in globals(): 更轻量,且明确可控

事情说清了就结束。缓存不是开关,是状态管理——它在哪存、谁可见、何时失效,每个点都得对齐你的部署拓扑和数据一致性要求。

text=ZqhQzanResources