2014-11-22 53 views
3

我有这个。缓存瓶 - 登录user_loader

@login_manager.user_loader 
def load_user(id=None): 
    return User.query.get(id) 

直到我介绍了Flask-Principal,它工作正常。

@identity_loaded.connect_via(app) 
def on_identity_loaded(sender, identity): 

    # Set the identity user object 
    identity.user = current_user 
    # return 
    if hasattr(current_user, 'id'): 
     identity.provides.add(UserNeed(current_user.id)) 

    # Assuming the User model has a list of roles, update the 
    # identity with the roles that the user provides 
    if hasattr(current_user, 'roles'): 
     for role in current_user.roles: 
      identity.provides.add(RoleNeed(role.name)) 

添加这会导致严重的性能问题。 SQLALCHEMY_ECHO显示每次加载静态文件时都会查询用户表。

#Warning: Dummy Cache 
users = {} 
@login_manager.user_loader 
def load_user(uid=None): 
    if uid not in users: 
     users[uid] = User.query.get(uid) 
    return users[uid] 

这个实验中,其解决了重复查询的问题后,我才意识到我需要引入缓存到我的瓶的应用程序。这里是quesitons。

  1. 如何缓存User.query.get(id)
  2. 何时需要清除此用户缓存?
+2

为什么不简单地将['skip_static'](https://pythonhosted.org/Flask-Principal/#flask_principal.Principal)设置为'True'?然后,例如,整个静态路由问题不受影响。 – 2014-11-22 14:30:11

+0

@MartijnPieters我是个白痴:|那是在我的鼻子下面...谢谢! – lustdante 2014-11-22 14:41:57

回答

1

老问题,但似乎没有其他答案或通过谷歌,并花了我一段时间来解决这个问题,所以也许这个答案将有助于某人。

TLDR SOLUTION:

首先,你需要一些缓存后端,我使用flask-cachingredis与蟒蛇redis库一封来自PyPI sudo pip install redis

接下来,做一个from flask_caching import Cache,然后cache = Cache(),我在另一个文件extensions.py。如果您使用的是应用程序工厂模式,这一点非常重要,因为您稍后需要导入cache,这有助于避免较大烧瓶应用程序出现循环引用问题。

在此之后,您需要注册的烧瓶应用烧瓶缓存扩展,这是我做的一个独立app.py文件是这样的:所以,现在的cache在瓶注册

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 

它可以从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) 

背景:

我需要考虑缓存烧瓶登录user_loader从事实,我已经UserMixinAnonymousUserMixin延长烧瓶登录中的类来实现访问控制列表管理起来一些类的方法,如get_rolesget_abilities。我也使用flask-sqlalchemy和postgresql后端,并且有一个角色表和一个与用户对象有关系的能力表。这些用户角色和能力主要在模板中进行检查,以基于用户角色和能力呈现各种视图。

在某些时候,我注意到当打开多个浏览器选项卡或浏览器在我的应用程序重新加载页面时,我开始出现错误TimeoutError: QueuePool limit of size 5 overflow 10 reached, connection timed out, timeout 30。 Flask-sqlalchemy的设置为SQLALCHEMY_POOL_SIZESQLALCHEMY_MAX_OVERFLOW,但增加这些值只是为我掩盖了问题,错误仍然发生,只需加载更多选项卡或执行更多页面重新加载。

进一步挖掘,以找出根本原因,我问我的PostgreSQL数据库与SELECT * FROM pg_stat_activity;和我积累了状态idle in transaction其中SQL查询被明确的联系用户,角色,能力访问检查多个连接每一个请求中找到。这些idle in transaction连接正在导致我的数据库连接池达到容量。

进一步测试发现,缓存烧瓶登录user_loader User对象消除idle in transaction连接,那么即使我离开SQLALCHEMY_POOL_SIZESQLALCHEMY_MAX_OVERFLOW为默认值,我没有再遭受TimeoutError: QueuePool limit。问题解决了!