老问题,但在 SO 或通过谷歌似乎没有其他答案,我花了一段时间来解决这个问题,所以也许这个答案会对某人有所帮助。
首先,您需要一些缓存后端,我使用带有redis的烧瓶缓存和来自 pypi 的 python库。redissudo pip install redis
接下来,做一个from flask_caching import Cache然后cache = Cache()我在另一个名为extensions.py. 如果您使用的是应用程序工厂模式,这一点很重要,因为您稍后需要导入cache,这有助于避免大型烧瓶应用程序的循环引用问题。
在此之后,您需要在烧瓶应用程序上注册烧瓶缓存扩展,我会这样做一个单独的app.py文件,如下所示:
from flask import Flask
from extensions import cache
def create_app(config_obj=None):
"""An application factory"""
app = Flask(__name__)
app.config.from_object(config_obj)
cache.init_app(app, config={'CACHE_TYPE': 'redis',
'CACHE_REDIS_HOST': '127.0.0.1',
'CACHE_REDIS_PORT': '6379',
'CACHE_REDIS_URL': 'redis://127.0.0.1:6379'})
return app
因此,既然cache在 Flask 中注册了它,它就可以从应用程序中导入extensions.py并在整个应用程序中使用,而不会出现循环引用问题。继续使用您正在使用的任何文件user_loader:
import pickle
from flask import current_user
from extensions import cache
from models.user_models import User
@login_manager.user_loader
def load_user(user_id):
"""Load user by ID from cache, if not in cache, then cache it."""
# make a unique cache key for each user
user = 'user_{}'.format(user_id)
# check if the user_object is cached
user_obj = pickle.loads(cache.get(user)) if cache.get(user) else None
if user_obj is None:
query = User.query.get(int(user_id))
user_obj = pickle.dumps(query)
cache.set(user, user_obj, timeout=3600)
return query
return user_obj
最后,当您注销用户时,您可以将它们从缓存中删除:
@blueprint.route('/logout/')
@login_required
def logout():
"""Logout."""
# remove the user information from redis cache
user = 'user_{}'.format(current_user.id)
cache.delete(user)
# remove the user information from the session
logout_user()
# Remove session keys set by Flask-Principal
for key in ('identity.name', 'identity.auth_type'):
session.pop(key, None)
flash('You are logged out.', 'info')
return redirect(url_for('public.home')
这似乎工作得很好,它减少了对 SQLAlchemy 的查询命中,每个用户每页三个查询,并在我的应用程序的几个部分中将我的页面加载速度提高了 200 毫秒,同时消除了达到 SQLAlchemy 连接池限制的讨厌问题。
此解决方案的最后一个重点。如果出于任何原因更改用户对象,例如,如果为用户分配新角色或能力,则必须从缓存中清除用户对象。例如如下:
# set the user_id from current_user.id CACHE object
user_id = current_user.id
# remove the old USER object from cache since you will change it
# first set the cache key to user_{id}
cache_user = 'user_{}'.format(user_id)
# now delete the cache key
cache.delete(cache_user)
背景:
我需要考虑缓存 flask-login user_loader 是因为我通过扩展 flask-login 类UserMixin和AnonymousUserMixin一些类方法(如get_roles和)实现了访问控制列表管理get_abilities。我还在使用 flask-sqlalchemy 和 postgresql 后端,并且有一个角色表和一个与用户对象有关系的能力表。这些用户角色和能力主要在模板中进行检查,以根据用户角色和能力呈现各种视图。
在某些时候,我注意到在我的应用程序中打开多个浏览器选项卡或只是浏览器重新加载页面时,我开始收到错误消息TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30。Flask-sqlalchemy 有设置SQLALCHEMY_POOL_SIZE,SQLALCHEMY_MAX_OVERFLOW但是增加这些值只是掩盖了我的问题,通过加载更多选项卡或重新加载更多页面仍然会发生错误。
深入挖掘以找出根本原因,我查询了我的 postgresql 数据库,并发SELECT * FROM pg_stat_activity;现在每个请求中我都在累积多个连接,idle in transaction并且 SQL 查询明显与用户、角色、能力访问检查相关联。这些idle in transaction连接导致我的数据库连接池达到容量。
进一步的测试发现,缓存烧瓶登录 user_loaderUser对象消除了idle in transaction连接,然后即使我离开SQLALCHEMY_POOL_SIZE并SQLALCHEMY_MAX_OVERFLOW使用默认值,我也没有TimeoutError: QueuePool limit再次遭受。问题解决了!