那些年我们一起追过的缓存写法(一)

发布时间:2026/7/26 21:16:05

那些年我们一起追过的缓存写法(一) 那些年我们一起追过的缓存写法(一)在编程的世界里缓存就像是一个神奇的魔法盒子——它能让你曾经计算过的结果迅速重现避免重复劳动。从初学时的简单字典到后来复杂的多级缓存我们走过的每一步都充满了探索的乐趣。今天就让我们一起回顾那些年我们一起追过的缓存写法从最基础的概念讲起逐步走向更高级的用法。## 什么是缓存为什么需要它想象一下你每次去图书馆找一本特定的小说都要从头翻遍所有书架。这显然很浪费时间。如果你把这本书放在书桌上下次想看时直接拿起来是不是快多了这就是缓存的核心思想把频繁访问的数据存放在高速存取的地方。在程序中缓存可以- 减少重复计算提升性能- 降低数据库或API的负载- 改善用户体验## 初阶用字典实现最简单的缓存最朴素的缓存方式就是使用一个字典或哈希表来存储键值对。我们用Python写一个简单的缓存函数python# 一个简单的计算器带缓存功能def fib_with_cache(n, cache{}): 计算斐波那契数列的第n项并使用字典缓存中间结果。 if n in cache: # 如果结果已在缓存中 return cache[n] # 直接返回 if n 2: result n else: result fib_with_cache(n-1, cache) fib_with_cache(n-2, cache) cache[n] result # 将计算结果存入缓存 return result# 测试print(fib_with_cache(10)) # 输出55print(fib_with_cache(100)) # 即便计算大数也不卡顿这段代码利用了Python中默认参数只初始化一次的特性使得cache字典在多次调用间共享。它的优点是非常简单缺点也很明显缓存会无限增长而且不支持并发安全。## 中阶使用functools.lru_cache自动管理Python标准库为我们提供了更优雅的解决方案functools.lru_cache装饰器。它自动实现了**最近最少使用LRU**淘汰策略当缓存满了会移除最久未使用的数据。pythonfrom functools import lru_cachelru_cache(maxsize128) # 最多缓存128个结果def fib_lru(n): 使用lru_cache装饰的斐波那契函数。 当缓存满时自动淘汰最久未使用的条目。 if n 2: return n return fib_lru(n-1) fib_lru(n-2)# 测试print(fib_lru(10)) # 55print(fib_lru(100)) # 354224848179261915075瞬间返回print(fib_lru.cache_info()) # 查看缓存状态CacheInfo(hits?, misses?, maxsize128, currsize?)lru_cache的优点- 自动管理缓存大小- 支持统计信息命中率等- 线程安全在Python 3.8中- 可设置typedTrue区分不同类型参数但注意lru_cache仅适用于**纯函数**无副作用输出仅由输入决定并且参数必须是可哈希的。## 进阶自定义TTL生存时间缓存有时候我们需要缓存数据在特定时间后自动失效比如从数据库读取的用户信息5分钟后应该刷新。lru_cache不支持TTL我们可以自己构建一个带过期时间的缓存pythonimport timefrom functools import wrapsdef ttl_cache(ttl_seconds300, maxsize128): “” 自定义TTL缓存装饰器。 - ttl_seconds: 缓存生存时间秒 - maxsize: 最大缓存条目数 “” def decorator(func): cache {} # 使用有序字典维护访问顺序便于LRU淘汰 from collections import OrderedDict wraps(func) def wrapper(*args, **kwargs): # 生成缓存键注意这里简化了kwargs的处理 key (args, tuple(sorted(kwargs.items()))) now time.time() # 检查缓存是否有效 if key in cache: value, timestamp cache[key] if now - timestamp ttl_seconds: # 缓存命中且未过期更新访问顺序 cache.move_to_end(key) return value else: # 缓存过期删除旧条目 del cache[key] # 计算新值 result func(*args, **kwargs) # 如果超出最大容量淘汰最久未使用的条目 if len(cache) maxsize: cache.popitem(lastFalse) # 移除最早访问的条目 cache[key] (result, now) return result return wrapper return decorator# 使用示例ttl_cache(ttl_seconds10, maxsize5)def get_user_info(user_id): “” 模拟从数据库获取用户信息耗时操作 “” print(f正在从数据库获取用户 {user_id} 的信息…“) time.sleep(2) # 模拟耗时 return {“id”: user_id, “name”: fUser_{user_id}”, “time”: time.time()}# 测试print(get_user_info(1)) # 第一次等待2秒print(get_user_info(1)) # 第二次立即返回缓存命中time.sleep(11) # 等待超过TTLprint(get_user_info(1)) # 第三次缓存过期重新计算这个自定义缓存虽然功能强大但仍有不足**不支持并发安全**、**未处理异常情况**、**键生成不够健壮**。在实际项目中建议使用成熟的第三方库如cachetools。## 高级用法多级缓存策略在大型系统中单层缓存往往不够用。常见的是**多级缓存**例如- **一级缓存**本地进程内缓存如lru_cache速度最快但容量有限- **二级缓存**分布式缓存如Redis速度较快容量大- **三级缓存**数据库或外部API速度最慢一个简化的多级缓存实现pythonimport redisfrom functools import lru_cacheclass MultiLevelCache: “” 多级缓存示例本地LRU Redis “” definit(self, local_size100, redis_clientNone): self.local_cache lru_cache(maxsizelocal_size)(self._get_from_redis) self.redis redis_client or redis.Redis(host‘localhost’, port6379, db0) def _get_from_redis(self, key): “”“从Redis获取数据如果不存在则返回None”“” return self.redis.get(key) def get(self, key): “”“获取数据先查本地缓存再查Redis”“” result self.local_cache(key) # 先查本地 if result is None: # 本地没有从数据库加载实际中需实现 result self._load_from_db(key) # 回填到Redis self.redis.set(key, result, ex300) # 5分钟过期 # 清除本地缓存强制下次重新加载 self.local_cache.cache_clear() return result defload_from_db(self, key): “”“模拟从数据库加载”“” print(f从数据库加载 {key}) return fdata{key}这种设计结合了本地缓存的快速和Redis的容量优势是生产环境中的常见模式。## 总结从最简单的字典缓存到标准库的lru_cache再到自定义TTL缓存最后到多级缓存架构我们见证了缓存写法的进化之路。每一步都体现了对性能、内存管理、并发安全的追求。关键要点1. 缓存不是银弹——需要权衡内存占用与性能提升2. 选择合适的淘汰策略LRU、LFU、TTL等3. 注意缓存的失效与更新缓存穿透、雪崩、击穿4. 生产环境中优先使用成熟库cachetools、redis-py等下期预告我们将深入探讨缓存的常见陷阱——缓存穿透、雪崩和击穿以及如何用布隆过滤器、互斥锁等技巧来应对。敬请期待—缓存的世界深似海但每一次优化都让我们离完美更近一步。希望这篇文章能帮助你回顾那些年我们一起追过的缓存写法也为未来的探索埋下种子。

相关新闻