如何正确设置 Flask + uWSGI + SQLAlchemy 以避免数据库连接问题
How to correctly setup Flask + uWSGI + SQLAlchemy to avoid database connection issues
我正在使用带有 SQLAlchemy 和 uWSGI 服务器的 Flask 应用程序。 uWSGI 在分叉期间通过所有进程重复连接是一个已知问题。关于如何解决这个问题的信息在网上有点散落,但两个主要选项似乎是:
- 在 uWSGI 的配置中使用
lazy-apps = true
(不推荐,因为它会消耗大量内存)
- 使用 uWSGI 的
@postfork
装饰器在分叉后关闭连接,为每个新进程启动新的新连接,但我不清楚何时以及如何在 Flask 应用程序中使用。
这是我的应用示例:
# 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__.py
或 app
文件夹中,但我认为将它迁移到 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.
如果你想要后一种神奇的解决方案,请参阅文档。
我正在使用带有 SQLAlchemy 和 uWSGI 服务器的 Flask 应用程序。 uWSGI 在分叉期间通过所有进程重复连接是一个已知问题。关于如何解决这个问题的信息在网上有点散落,但两个主要选项似乎是:
- 在 uWSGI 的配置中使用
lazy-apps = true
(不推荐,因为它会消耗大量内存) - 使用 uWSGI 的
@postfork
装饰器在分叉后关闭连接,为每个新进程启动新的新连接,但我不清楚何时以及如何在 Flask 应用程序中使用。
这是我的应用示例:
# 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__.py
或 app
文件夹中,但我认为将它迁移到 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.
如果你想要后一种神奇的解决方案,请参阅文档。