【问题标题】:Flask SQLAlchemy sessions out of syncFlask SQLAlchemy 会话不同步
【发布时间】:2016-10-17 02:46:38
【问题描述】:

我有一个 Flask REST API,使用 gunicorn/nginx 堆栈运行。为运行 API 的每个线程设置一次全局 SQLAlchemy 会话。我设置了一个端点 /test/ 来运行 API 的单元测试。一项测试发出 POST 请求以向数据库添加内容,然后使用 finally: 子句进行清理:

def test_something():
    try:
        url = "http://myposturl"
        data = {"content" : "test post"}
        headers = {'content-type': 'application/json'}
        result = requests.post(url, json=data, headers=headers).json()
        validate(result, myschema)
    finally:
        db.sqlsession.query(MyTable).filter(MyTable.content == "test post").delete()
        db.sqlsession.commit()

问题在于,发出 POST 请求的线程现在在其会话中有一个“测试帖子”对象,但数据库没有这样的对象,因为运行测试的线程从数据库中删除了该对象。因此,当我向服务器发出 GET 请求时,大约 4 次中的 1 次(我有 4 个 gunicorn 工人),我得到了“测试帖子”对象,而 4 次中有 3 次没有。这是因为每个线程都有自己的会话对象,并且它们不同步,但我真的不知道该怎么办......

这是我的 SQLAlchemy 会话设置:

def connectSQLAlchemy():
    import sqlalchemy
    import sqlalchemy.orm
    engine = sqlalchemy.create_engine(connection_string(DBConfig.USER, DBConfig.PASSWORD, DBConfig.HOST, DBConfig.DB))
    session_factory = sqlalchemy.orm.sessionmaker(bind=engine)
    Session = sqlalchemy.orm.scoped_session(session_factory)
    return Session()

# Create a global session for everyone
sqlsession = connectSQLAlchemy()

【问题讨论】:

    标签: python multithreading session sqlalchemy gunicorn


    【解决方案1】:

    如果您使用的是 flask,请使用 flask-sqlalchemy,它会为您处理会话的生命周期。

    如果您坚持自己做,正确的模式是为每个请求创建一个会话,而不是创建一个全局会话。你应该这样做

    Session = scoped_session(session_factory, scopefunc=flask._app_ctx_stack.__ident_func__)
    return Session
    

    而不是

    Session = scoped_session(session_factory)
    return Session()
    

    然后做

    session = Session()
    

    每次您需要会话时。凭借scoped_sessionscopefunc,这将在每个请求中返回不同的会话,但在同一请求中返回相同的会话。

    【讨论】:

    • 我按照你说的做了,我把 self.session = db.Session() 放在 init 函数中,用于一个新的 APIResource(Resource) 类,但我是遇到同样的问题...我在数据库中添加一些内容并在单独的线程中删除它,但添加线程会记住该内容。
    • @Scott 你是说你有多个线程在同一个请求中
    • 没有。只是gunicorn线程。我认为这个解决方案的问题是我需要在某处请求结束时关闭会话。
    • @Scott 好吧,是的。你以前不这样做吗?同样,我强烈推荐使用 flask-sqlalchemy,因为它会为您处理好这些。
    • @univerio 我无法理解scoped_session(session_factory)scoped_session(session_factory, scopefunc=flask._app_ctx_stack.__ident_func__) 之间的有效区别是什么。在前者中,我们使用线程本地存储——每个调用Session() 的线程都将获得它自己的会话。对 Flask 的每个请求都会创建一个新线程,从而获得它自己的会话。为什么这实际上与后者不同?
    【解决方案2】:

    想通了。我所做的是在我的应用程序的 __init__.py 中为请求添加设置和拆卸:

    @app.before_request
    def startup_session():
        db.session = db.connectSQLAlchemy()
    
    @app.teardown_request
    def shutdown_session(exception=None): 
        db.session.close()
    

    仍在我的 db 模块中使用全局会话对象:

    db.py:
    
    ....
    session = None
    ....
    

    scoped_session 处理不同的线程,我认为......

    如果出于某种原因这是否是一种糟糕的方法,请告知。 =c)

    【讨论】:

    • scoped_session 不能像这样处理多个线程。一旦你有多个线程,你就会遇到麻烦。
    • 您需要以每个进程的方式进行连接,例如 4 个 procs = 4 个 DB 连接并以每个请求的方式打开 Sessions 1 个请求 = 1 个新 Session。
    • @alextsil "以每个进程的方式连接" 所以db.connectSQLAlchemy() 同时创建一个连接和一个会话?
    • @Him 不,您需要在@app.before.server_start 侦听器中运行create_engine,然后在create new session 侦听器中运行create new session。 (命名不准确,但您明白了。)这样每个工作人员将获得自己的连接池,每个请求将获得自己的会话。
    猜你喜欢
    • 2019-01-01
    • 2018-02-08
    • 1970-01-01
    • 2016-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    • 2018-06-08
    相关资源
    最近更新 更多