@cache.memoize不够用是因为它不支持参数白名单、动态key前缀和按返回值状态条件缓存;需手写装饰器通过Redis灵活控制key构造与缓存策略。

缓存函数结果时,为什么 @cache.memoize 不够用?
Flask-Caching 的 @cache.memoize 默认对所有参数做完整哈希,但实际中你常需要:只按 user_id 缓存,忽略 debug=True 这类调试参数;或对 page=1 和 page=2 分开缓存,但跳过 sort_order 的临时变动。它不支持参数白名单、动态 key 前缀、或按返回值状态决定是否缓存。
手写装饰器:用 redis.Redis 控制 key 构造与条件缓存
直接操作 Redis 客户端比依赖 Flask-Caching 更灵活。关键在三点:参数筛选、key 格式化、缓存策略分支。
- 用
inspect.signature提取函数签名,再通过**kwargs或显式参数名(如user_id、post_id)提取需参与缓存的字段 - key 拼接建议用
f"func:{func.__name__}:{user_id}:{post_id}",避免空格和特殊字符;可用hashlib.md5截取短哈希防超长 - 检查函数返回值是否为
None或空列表,就跳过setex;错误时也不写入缓存
def redis_cache_by_user_id(timeout=300):
def decorator(func):
def wrapper(*args, **kwargs):
# 只取 user_id 和 post_id,忽略其他
user_id = kwargs.get("user_id")
post_id = kwargs.get("post_id")
if not user_id or not post_id:
return func(*args, **kwargs)
key = f"api:get_post:{user_id}:{post_id}"
cached = redis_client.get(key)
if cached is not None:
return json.loads(cached)
result = func(*args, **kwargs)
if result: # 非空才缓存
redis_client.setex(key, timeout, json.dumps(result))
return result
return wrapper
return decorator
如何让同一个装饰器适配不同函数的参数名?
硬编码 kwargs.get("user_id") 会限制复用性。更通用的做法是把「缓存键字段」作为装饰器参数传入,用 functools.partial 或闭包绑定。
- 定义装饰器时接受
key_fields=("user_id", "category"),运行时从**kwargs或*args中按签名位置/名称提取 - 注意
args里可能混入 self(类方法),需先用inspect.ismethod(func)判断 - 若字段缺失,不报错 fallback 到直接调用原函数,避免缓存逻辑破坏主流程
缓存穿透与并发写入怎么处理?
当大量请求同时发现 key 不存在,会一拥而上打到后端。Redis 本身不提供原子性 set-if-not-exists + compute 流程。
立即学习“Python免费学习笔记(深入)”;
- 用
redis_client.set(key, placeholder, ex=60, nx=True)先占位(nx=True 表示仅当 key 不存在时设置),返回 True 才去查库并更新值 - 占位符可设为
"__MISSING__"字符串,后续请求读到它就 sleep(0.1) 后重试,而非重复查询 - 避免用
get+setex两步,中间存在竞态窗口
细粒度缓存真正难的不是写法,而是想清楚哪些参数该进 key、哪些该被忽略、以及空结果要不要占位——这些没法靠工具自动推断,得结合接口语义手动设计。


















