在多个uWSGI进程中使用Flask-SQLAlchemy

时间:2015-12-13 15:45:03

标签: python mysql flask sqlalchemy flask-sqlalchemy

我在Flask应用程序中遇到持续错误:

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

我正在使用mySQL服务器实例和Flask-SQLAlchemy模块。我仔细检查了mySQL实例上的连接的到期时间以及SQLAlchemy配置中的重置时间。没有问题,在mySQL连接到期之前重置连接池。我得出结论,必须有一些问题导致连接关闭,然后该连接的下一个用户窒息。

我使用生成四个进程的uWSGI运行Flask应用程序。如果我切换到单个进程,我无法重现错误。我猜测进程是通过共享连接池相互踩踏的。每当uWSGI分叉进程时,我都会添加以下函数。

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,但它只发生在fork中,并且似乎不会干扰请求。堆栈跟踪不包括fork,但不允许我捕获它。

  

追踪(最近一次呼叫最后一次):

     

文件   " /home/vagrant/env/local/lib/python2.7/site-packages/sqlalchemy/pool.py" ;,   第636行,在_finalize_fairy中       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(type(exception),exception,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()

1 个答案:

答案 0 :(得分:8)

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

请参阅此答案:uWSGI, Flask, sqlalchemy, and postgres: SSL error: decryption failed or bad record mac

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

  

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

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

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