Python 如何优雅实现带过期时间的 LRU 缓存(不依赖第三方)
技术百科
冷炫風刃
发布时间:2026-01-24
浏览: 次 functools.lru_cache不支持TTL机制,因其设计为纯LRU淘汰且无过期时间参数;需手写TTLCache类,用OrderedDict存(value, expire_time)并检查时间戳,注意LRU顺序更新、线程安全及精度权衡。
为什么 functools.lru_cache 不能直接加过期时间
functools.lru_cache 是 Python 内置的 LRU 缓存,但它不支持 TTL(Time-To-Live)机制。缓存项一旦写入,就永远有效,直到被 LRU 淘汰或手动清除。你无法通过参数设置“5 秒后自动失效”。强行在函数内检查时间戳,会破坏装饰器的纯封装性,也容易漏掉并发访问下的竞态问题。
手写带 TTL 的 LRU 缓存:用 OrderedDict + 时间戳
核心思路是:用 collections.OrderedDict 维护访问顺序,每个缓存值存储为 (value, expire_time) 元组;每次 get 前检查 expire_time 是否已过期,过期则删除并返回未命中。
关键实操点:
- 用
time.time()(非time.monotonic())便于调试,但注意系统时间回拨会导致误删;生产环境可换用time.monotonic()+ 初始偏移 -
OrderedDict.move_to_end(key)必须在每次get成功后调用,否则 LRU 顺序错乱 - 写入时若已存在 key,需先
pop再重插,避免残留旧过期时间 - 缓存大小限制(
maxsize)和 TTL 要正交处理:淘汰只看数量,过期只看时间
示例片段(简化版):
from collections import OrderedDict import timeclass TTLCache: def init(self, maxsize=128, ttl=60): self.cache = OrderedDict() self.maxsize = maxsize self.ttl = ttl
def get(self, key): if key not in self.cache: return None value, expire_at = self.cache[key] if time.time() > expire_at: self.cache.pop(key) return None self.cache.move_to_end(key) # 更新 LRU 顺序 return value def put(self, key, value): if self.maxsize == 0: return if key in self.cache: self.cache.pop(key) elif len(self.cache) >= self.maxsize > 0: self.cache.popitem(last=False) # 弹出最久未用 self.cache[key] = (value, time.time() + self.ttl)用装饰器包装成类似
lru_cache的用法要复刻
@lru_cache(ttl=30)的体验,需支持带参装饰器、绑定到函数对象的独立缓存实例,并兼容cache_clear()等方法。注意点:
- 装饰器工厂函数必须返回真正的装饰器,不能直接返回缓存实例
- 每个被装饰函数应持有自己的
TTLCache实例,避免跨函数污染 - 把
cache_clear、cache_info等方法挂到 wrapper 上,否则用户调用func.cache_clear()会报错 - 不支持
typed=True(即不同类型的相同值视为不同 key),除非手动序列化类型信息
线程安全与实际部署的坑

上面的 TTLCache 类默认不是线程安全的:get 和 put 中的多步操作(查、删、改、move)可能被并发打断。简单加 threading.Lock 会严重拖慢性能,尤其读多写少场景。
更实用的做法:
- 读操作(
get)不加锁,允许短暂返回过期值(业务能容忍几毫秒偏差) - 写操作(
put)加锁,保证pop和set原子性 - 如果必须强一致性,用
threading.RLock并把整个get流程锁住——但请确认你的 QPS 是否真的需要 - 进程间不共享缓存;多进程部署时,每个进程有独立缓存副本,TTL 各自计算
真正难的不是实现,而是判断“过期”是否必须精确到毫秒级,以及能否接受缓存雪崩时的瞬时穿透。这些权衡点,比代码本身更影响最终效果。
# 自己的
# python
# 但它
# 绑定
# 弹出
# 因其
# app
# 不支持
# 并把
# 并发
# 对象
# 报错
# 为什么
# 线程
# red
# 封装
# 封装性
# 加锁
# elif
# 只看
# 并发访问
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- c++怎么编写动态链接库dll_c++ __dec
- 如何提升Golang程序I/O性能_Golang
- 零基础学会Python自动化办公_高效处理Exce
- Windows如何拦截2345弹窗广告_Windo
- Python深度学习实战教程_神经网络模型构建与训
- Win11怎么修复系统文件_使用sfc命令修复Wi
- Win10系统怎么查看端口状态_Windows10
- c++中如何进行二进制文件读写_c++ read与
- 如何高效识别并拦截拼接式恶意域名 spam
- Win11怎么关闭自动调节亮度_Windows11
- 如何在 Django 中安全修改用户密码而不使会话
- php命令行怎么运行_通过CLI模式执行PHP脚本
- Win11怎么设置虚拟内存最佳大小_Windows
- Python 模块的 __name__ 属性如何由
- 短链接怎么用php还原_从基础原理到代码实现教学【
- Mac自带的词典App怎么用_Mac添加和使用多语
- Win11怎么关闭搜索历史 Win11清除搜索框最
- XAMPP 启动失败(Apache 突然停止)的终
- 如何优化Golang程序CPU性能_Golang
- Win11时间格式怎么改成12小时制 Win11时
- c++怎么处理多线程死锁_c++ lock_gua
- Windows10电脑怎么设置文件权限_Win10
- 如何在 Go 开发中正确处理本地包导入与远程模块路
- 如何用::实现单例模式_php静态方法与作用域操作
- c++中explicit(bool)的用法 c++
- 如何使用Golang log记录不同级别日志_Go
- 使用类变量定义字符串常量时的类型安全最佳实践
- Linux如何使用grep搜索文件内容_Linux
- 如何使用Golang实现文件追加操作_向已有文件追
- Win10如何卸载预装Edge扩展_Win10卸载
- Win11视频默认播放器怎么改_Win11关联第三
- Win11怎么更改任务栏颜色_Windows11个
- 如何在 Go 中创建包含 map 的 slice(
- Dapper的Execute方法的返回值是什么意思
- Windows10如何更改任务栏高度_Win10解
- Win11怎么查看局域网电脑_Windows 11
- Win11如何设置环境变量 Win11添加和修改系
- LINUX的SELinux是什么_详解LINUX强
- Win11怎样安装微信开发者工具_Win11安装开
- 如何使用Golang指针与结构体结合_修改结构体内
- Python生成器表达式内存优化_惰性计算说明【指
- Windows10如何更改盘符名称_Win10重命
- 如何在Golang中编写异步函数测试_Golang
- Windows 11怎么更改锁屏超时时间_Wind
- Mac如何与安卓手机传文件_Mac和Android
- Mac如何将HEIC图片格式转为JPG_Mac批量
- php控制舵机角度怎么调_php发送pwm信号控制
- Win11文件扩展名怎么显示_Win11查看文件后
- Win11怎么设置任务栏透明_Windows11使
- c# 服务器GC和工作站GC的区别和设置

QQ客服