Flask-Caching的@cache.cached默认不缓存完整HTTP响应,仅缓存视图返回值,需用make_response显式构造响应对象并手动设置状态码与Header;Redis配置须统一用CACHE_REDIS_URL并启动时验证连接;带查询参数请求必须设query_string=True;空结果需主动缓存防击穿。

Flask-Caching 的 @cache.cached 不等于全页缓存
加了 @cache.cached 后发现状态码变 200、Header 丢失、ETag 消失——这不是 bug,是默认行为。它只缓存视图函数的返回值(比如字典或字符串),然后由 Flask 用这个值重新构造响应,原始 Response 对象里的状态码、头信息、cookies 全部被丢弃。
要真正实现“页面级”缓存(即缓存完整 HTTP 响应),必须显式构造 Response 对象:
@app.route('/api/user')
@cache.cached(timeout=300)
def get_user():
data = {'id': 123, 'name': 'alice'}
resp = make_response(jsonify(data))
resp.status_code = 200
resp.headers['X-Source'] = 'backend'
return resp
- 必须在视图内调用
make_response(),不能依赖 Flask 自动包装 - 如果视图可能返回不同状态码(如 404 或 401),需确保每次返回前都设好
resp.status_code;否则缓存项统一为 200 - Header 如
Content-Type通常由jsonify()自动设置,但自定义 Header(如X-Cached)必须手动写入
Redis 连接必须用 CACHE_REDIS_URL,且启动时验证
只配 CACHE_TYPE = 'redis' 而不设 CACHE_REDIS_URL,Flask-Caching 会静默 fallback 到内存缓存(SimpleCache),进程一重启缓存全丢,线上完全不可用。
正确做法是统一用 URL 形式,并在应用初始化时触发连接检查:
立即学习“Python免费学习笔记(深入)”;
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
cache.init_app(app, config={
'CACHE_TYPE': 'redis',
'CACHE_REDIS_URL': os.getenv('REDIS_URL', 'redis://localhost:6379/1')
})
<p>try:
cache.cache._write_client.ping()
except Exception as e:
app.logger.critical(f'Redis unreachable: {e}')
raise SystemExit(1)
-
CACHE_REDIS_URL格式必须完整:带协议、端口、db 编号,例如redis://:p4ss@redis.example.com:6379/2 - URL 中的 db 编号(末尾
/2)优先级高于CACHE_REDIS_DB,混用会导致配置被忽略且无报错 - 别在视图里做 ping 检查——连接池初始化失败应阻断启动,而不是让请求失败后才暴露问题
带查询参数的 GET 请求必须设 query_string=True
默认情况下,@cache.cached 只按路径生成 key,/search?q=foo 和 /search?q=bar 会命中同一个缓存项,结果互相覆盖。
解决方法很简单,但极易遗漏:
@app.route('/search')
@cache.cached(timeout=60, query_string=True) # ← 关键开关
def search():
q = request.args.get('q', '')
return jsonify(results=search_db(q))
-
query_string=True会让缓存 key 包含完整查询字符串(包括顺序和编码),?q=foo&sort=desc和?sort=desc&q=foo视为不同 key - 对 POST 或带 body 的请求,
@cache.cached完全无效——它不读取request.json或request.form,这种场景必须手动缓存 - 如果接口同时支持 GET 参数和 header(如
Accept-Language),仅靠query_string=True不够,需手动构造 key 并调用cache.get()/cache.set()
空结果必须主动缓存,否则击穿无法避免
用户查一个不存在的 ID,数据库返回空,视图返回 {} 或 404,但如果不把空值也写进缓存,下次同请求仍会打到 DB——这就是缓存击穿。
不能依赖 “没命中就查库”,而要明确区分 “缓存未命中” 和 “缓存了空值”:
@app.route('/user/<int:uid>')
@cache.cached(timeout=300, query_string=True)
def get_user(uid):
user = db.query(User).filter_by(id=uid).first()
if not user:
# 主动缓存空结果,时效缩短防长期误伤
cache.set(f'user:{uid}', '__NOT_FOUND__', timeout=60)
return jsonify({'error': 'not found'}), 404
return jsonify(user.to_dict())
- 用特殊标记(如
'__NOT_FOUND__')代替None或空字典,避免与真实数据混淆 - 空值缓存时间建议设短(30–60 秒),既防击穿,又不至于掩盖刚创建的数据
-
cache.get()返回None才代表真未命中;返回'__NOT_FOUND__'就直接返回 404,不再查库
缓存不是开关,是状态层。key 怎么设计、值存什么、过期怎么设、空值怎么标——每处都得和业务逻辑对齐,漏掉任意一环,缓存就从加速器变成隐患源。

















