2014-11-22 54 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。問題解決了!