【问题标题】:Using Flask-SQLAlchemy in Multiple uWSGI Processes在多个 uWSGI 进程中使用 Flask-SQLAlchemy
【发布时间】:2016-03-19 02:22:37
【问题描述】:

我一直在与我的 Flask 应用程序中的一个持续错误作斗争:

OperationalError: (_mysql_exceptions.OperationalError) (2006, 'MySQL server has gone away')

我正在使用 mySQL 服务器实例和 Flask-SQLAlchemy 模块。我仔细检查了 mySQL 实例上连接的过期时间和 SQLAlchemy 配置中的重置时间。那里没有问题,连接池在 mySQL 连接过期之前被重置。我得出的结论是,一定有一些问题导致连接关闭,然后该连接的下一个用户阻塞。

我正在使用产生四个进程的 uWSGI 运行 Flask 应用程序。如果我切换到单个进程,我将无法重现该错误。我猜进程是通过共享连接池互相踩踏的。我添加了以下函数,以便在 uWSGI fork 进程时运行。

from uwsgidecorators import postfork

@postfork
def reset_db_connections():
    db.engine.dispose()

在启动时工作正常,并且似乎可以解决同时出现多个请求时的问题。但是,现在当一个进程被重置时,该进程的下一个请求会出现类似但不一样的 SQL Server 已消失错误。这是数据库的初始设置代码

def configure_db():
    from my_application.models import SomeModel
    db.create_all()

db = SQLAlchemy(app, session_options={'expire_on_commit': False})
configure_db()

数据库的典型用法如下所示:

def save(self):
    try:
        db.session.add(self)
        db.session.commit()
    except Exception, ex:
        app.logger.error("Error saving campaign: %s" % ex)
        db.session.rollback()

读取是以下两种风格之一:

user = db.session.query(User).filter(User.email == email).scalar()
user = User.query.filter(User.email == email).scalar()

我的理解是 Flask-SQLAlchemy 使用范围会话,因此它们应该在多进程环境中提供一些保护。我需要在 fork 上重置连接池吗?分叉时我是否也应该检查实时会话?

更新:

我已经把叉子改成这样:

@postfork
def reset_db_connections():
    db.session.close_all()
    db.engine.dispose()
    db.create_scoped_session()

我仍然收到 OperationalError,但它只发生在分叉期间,并且似乎不会干扰请求。堆栈跟踪不包括叉子,虽然它不允许我抓住它。

Traceback(最近一次调用最后一次):

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/pool.py”, 第 636 行,在 _finalize_fairy 中 仙女._reset(池)

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/pool.py”, 第 774 行,在 _reset self._reset_agent.rollback()

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py”, 第 1563 行,在回滚中 self._do_rollback()

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py”, 第 1601 行,在 _do_rollback self.connection._rollback_impl()

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py”, 第 670 行,在 _rollback_impl 中 self._handle_dbapi_exception(e, None, None, None, None)

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py”, 第 1341 行,在 _handle_dbapi_exception 中 exc_info

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/util/compat.py”, 第 199 行,在 raise_from_cause 中 reraise(类型(异常),异常,tb=exc_tb)

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py”, 第 668 行,在 _rollback_impl 中 self.engine.dialect.do_rollback(self.connection)

文件 “/home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/dialects/mysql/base.py”, 第 2519 行,在 do_rollback 中 dbapi_connection.rollback()

【问题讨论】:

    标签: python mysql flask sqlalchemy flask-sqlalchemy


    【解决方案1】:

    你必须对 uwsgi 使用 lazy-apps=true 选项。

    看到这个答案:uWSGI, Flask, sqlalchemy, and postgres: SSL error: decryption failed or bad record mac

    我不会使用 'lazy' 选项,因为它已被弃用

    当使用主进程处理多个进程时,uwsgi 在主进程中初始化应用程序,然后复制 应用到每个工作进程。问题是如果你打开一个 初始化应用程序时的数据库连接,然后你有 多个进程共享同一个连接,这会导致错误 以上。

    在某些情况下,例如使用 flask_admin 或在 app/__init__.py 中调用 Base.metadata.create_all(),您的应用确实在导入时已经与数据库建立了连接。在导入模块后使用lazy-apps=false uwsgi 分叉,从而将连接的文件描述符复制到子级。使用lazy-apps=true uwsgi 分叉自身,然后进行导入。这样每个子流程都有自己的连接。

    uWSGI 尝试(ab)使用 fork() 调用的 Copy On Write 语义 只要有可能。默认情况下,它会在加载你的 应用程序共享尽可能多的内存。如果这 由于某种原因,这种行为是不可取的,请使用lazy-apps 选项。 这将指示 uWSGI 在每个 worker 之后加载应用程序 叉子()。

    【讨论】:

      猜你喜欢
      • 2020-08-07
      • 1970-01-01
      • 2020-01-08
      • 2021-11-22
      • 2020-04-24
      • 2014-05-18
      • 2013-02-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多