flask-caching 的 @cache.cached 默认仅缓存视图函数返回值,不缓存完整响应(含状态码、header),需用 make_response 显式构造响应对象并手动处理状态码与头信息;redis 配置应统一使用 cache_redis_url 并验证连接;带查询参数的请求须设 query_string=true 才按参数缓存;空结果需手动 cache.set() 缓存短时效以防止穿透。

Flask-Caching 的 @cache.cached 默认不缓存整个响应体
很多人以为加了 @cache.cached 就能像 Nginx 那样缓存完整 HTTP 响应(含状态码、Header、Body),其实不是。它只缓存视图函数的返回值(比如字符串、字典),然后由 Flask 用这个值构造新响应——这意味着原始响应头(如 Content-Type、ETag)和状态码(如 404)会丢失,除非手动复制。
要全页缓存,必须显式把响应对象包装进缓存逻辑里:
@app.route('/api/data')
@cache.cached(timeout=300)
def get_data():
resp = make_response(jsonify({'result': 'ok'}))
resp.headers['X-Cached'] = 'true'
return resp
注意两点:
-
make_response()必须在视图内调用,不能靠 Flask 自动转换 - 如果视图可能返回不同状态码(如
200/401),缓存前需确保状态码也被序列化保存,否则所有缓存项都默认是200
Redis 后端配置时 CACHE_REDIS_URL 和 CACHE_REDIS_HOST 别混用
Flask-Caching 支持两种 Redis 配置方式,但官方文档没强调互斥性:用 CACHE_REDIS_URL(如 redis://localhost:6379/1)时,CACHE_REDIS_HOST、CACHE_REDIS_PORT 等参数会被忽略,且不会报错。
常见踩坑场景:
- 本地开发用 URL,生产环境改用 HOST+PORT,结果因配置残留仍连本地 Redis
- URL 中指定了 db=1,但代码里又调
cache.init_app(app, config={'CACHE_REDIS_DB': 2}),实际生效的是 URL 里的 db
建议统一用 CACHE_REDIS_URL,并在启动时验证连接:
cache.init_app(app, config={'CACHE_TYPE': 'RedisCache', 'CACHE_REDIS_URL': os.getenv('REDIS_URL')})
try:
cache.cache._write_client.ping() # 触发真实连接
except Exception as e:
app.logger.error(f"Redis connection failed: {e}")
带查询参数的请求必须显式启用 query_string=True
默认情况下,@cache.cached 只按请求路径(如 /api/search)生成 key,所有 ?q=foo 和 ?q=bar 都会命中同一个缓存项。
开启 query_string=True 后,key 会自动包含标准化后的查询参数(排序后拼接),但要注意:
- 参数顺序不影响 key(
?a=1&b=2和?b=2&a=1是同一个缓存) - 空参数(
?sort=)和缺失参数(?sort)会被视为不同 key,建议前端统一处理空值 - 敏感参数(如
token、signature)不该参与缓存 key 计算,需先从 request.args 中 pop 掉再调用cached()
缓存穿透风险:空结果也要缓存,但得设更短的 timeout
当接口根据 ID 查询数据库,ID 不存在时返回 404,若不缓存该响应,攻击者可高频刷不存在的 ID,直接打穿缓存层压到 DB。
解决方案是缓存空响应,但 timeout 要明显短于正常数据(比如正常 5 分钟,空结果 30 秒):
@app.route('/user/<user_id>')
@cache.cached(timeout=300, unless=lambda: not request.args.get('skip_cache'))
def get_user(user_id):
user = User.query.get(user_id)
if not user:
resp = make_response('', 404)
cache.set(cache.make_cache_key(), '', timeout=30) # 手动设空值 + 短期 TTL
return resp
return jsonify(user.to_dict())
</user_id>
这里的关键点是:cache.cached 不支持对不同返回值设不同 timeout,所以空结果必须用 cache.set() 手动控制。
另外,unless 参数适合临时绕过缓存(比如调试时加 ?skip_cache=1),但别在生产环境长期开着——它会让缓存失效逻辑变得隐晦难追踪。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











