如何正确设置 Flask + uWSGI + SQLAlchemy 以避免数据库连接问题

How to correctly setup Flask + uWSGI + SQLAlchemy to avoid database connection issues

我正在使用带有 SQLAlchemy 和 uWSGI 服务器的 Flask 应用程序。 uWSGI 在分叉期间通过所有进程重复连接是一个已知问题。关于如何解决这个问题的信息在网上有点散落,但两个主要选项似乎是:

这是我的应用示例:

# file: my_app/app.py

db = SQLAlchemy()


def create_app():
    app = Flask(__name__)
    app.config.from_pyfile(f'../config/settings.py')
    db.init_app(app)
    db.create_all(app=app)
    return app

run.py 文件的示例:

# file: run.py
from my_app/app import create_app


app = create_app()
if "__main__" == __name__:
    app.run(debug=app.config["DEBUG"], port=5000)

所以问题是 postfork 应该在哪里以及如何执行以正确设置 uWSGI 的服务器以在每个进程上使用隔离连接而不使用 lazy-apps = true

一般需要使用uwsgi提供的@postfork装饰器。 我们没有使用 SQLAlchemy,但我认为您也可以根据您的情况调整我们的解决方案。

这是我们的 uwsgi 配置的摘录:

[uwsgi]
strict = true

; process management
master = true
vacuum = true
pidfile = /tmp/uwsgi.pid

enable-threads = true
cpu-affinity = 1
processes = 4
threads = 2
mules = 2
...

下面是使用@postfork 创建进程安全数据库连接的应用程序初始化代码。

from flask import Flask
from uwsgidecorators import postfork

from app.lib.db import DB  # DB object will be replaced by SQLAlchemy on your side

app = Flask(__name__)

# due to uwsgi's copy-on-write semantics workers need to connect to database
# to preserve unique descriptors after fork() is called in master process
@postfork
def db_reconnect():
    # Open the DB connection and log the event
    app.db = DB(config.get('db', 'host'),
                config.get('db', 'user'),
                config.get('db', 'pass'),
                config.get('db', 'name'))

在我们的例子中,所有初始化代码都放在 __init__.pyapp 文件夹中,但我认为将它迁移到 app.py 文件中不会有任何困难.

如果您对此有任何其他问题,请告诉我。

SQLAlchemy 手册提供了两个如何处理此问题的示例:Using Connection Pools with Multiprocessing

涉及 Engine.dispose() 的第一种方法可以按照 Hett 的建议使用 uwsgidecorators.postfork 来实现 - 如果只涉及默认绑定,应该可以使用的简单示例:

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = "postgres:///test"
    db.init_app(app)

    def _dispose_db_pool():
        with app.app_context():
            db.engine.dispose()

    try:
        from uwsgidecorators import postfork
        postfork(_dispose_db_pool)
    except ImportError:
        # Implement fallback when running outside of uwsgi...
        raise

    return app

第二个涉及 sqlalchemy.event 并丢弃来自不同 PID 的连接 - 引用:

The next approach is to instrument the Pool itself with events so that connections are automatically invalidated in the subprocess. This is a little more magical but probably more foolproof.

如果你想要后一种神奇的解决方案,请参阅文档。