uWSGI、Flask、sqlalchemy和postgres:SSL错误:解密失败或坏记录mac

51

我正在尝试使用uWSGI + Nginx搭建一个Web应用服务器,其中运行一个使用SQLAlchemy与Postgres数据库通信的Flask应用程序。

当我向Web服务器发出请求时,每隔一个响应就会出现500错误。

错误信息为:

Traceback (most recent call last):
  File "/var/env/argos/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 867, in _execute_context
    context)
  File "/var/env/argos/lib/python3.3/site-packages/sqlalchemy/engine/default.py", line 388, in do_execute
    cursor.execute(statement, parameters)
psycopg2.OperationalError: SSL error: decryption failed or bad record mac


The above exception was the direct cause of the following exception:

sqlalchemy.exc.OperationalError: (OperationalError) SSL error: decryption failed or bad record mac

这个错误是由一个简单的Flask-SQLAlchemy方法触发的:

result = models.Event.query.get(id)

uwsgisupervisor 管理,其配置如下:


[program:my_app]
command=/usr/bin/uwsgi --ini /etc/uwsgi/apps-enabled/myapp.ini --catch-exceptions
directory=/path/to/my/app
stopsignal=QUIT
autostart=true
autorestart=true

而的配置看起来像这样:

[uwsgi]
socket = /tmp/my_app.sock
logto = /var/log/my_app.log
plugins = python3
virtualenv =  /path/to/my/venv
pythonpath = /path/to/my/app
wsgi-file = /path/to/my/app/application.py
callable = app
max-requests = 1000
chmod-socket = 666
chown-socket = www-data:www-data
master = true
processes = 2
no-orphans = true
log-date = true
uid = www-data
gid = www-data

我所能理解的是它与uwsgi的forking有关,但除此之外我不清楚还需要做些什么。

4个回答

95

问题最终是由uwsgi的分叉引起的。

当使用主进程和多个子进程一起工作时,uwsgi会在主进程中初始化应用程序,然后将应用程序复制到每个工作进程中。问题在于,如果您在初始化应用程序时打开数据库连接,则有多个进程共享同一个连接,这会导致上述错误。

解决方案是设置uwsgi的lazy配置选项,强制在每个进程中完全加载应用程序:

lazy

设置懒惰模式(在工作进程中加载应用程序而不是在主进程中)。

此选项可能会对内存使用造成影响,因为无法使用写时复制语义。启用lazy模式时,只有工作进程将被uWSGI的重新加载信号重新加载;主进程将保持活动状态。因此,在重新加载时,不会通过主进程获取uWSGI配置更改。

还有一个lazy-apps选项:

lazy-apps

在每个工作进程中加载应用程序,而不是在主进程中加载。

此选项可能会对内存使用产生影响,因为无法使用写时复制语义。与 lazy 不同的是,这只影响应用程序的加载方式,而不影响主进程重新加载的行为。

这个 uwsgi 配置最终对我起作用了:

[uwsgi]
socket = /tmp/my_app.sock
logto = /var/log/my_app.log
plugins = python3
virtualenv =  /path/to/my/venv
pythonpath = /path/to/my/app
wsgi-file = /path/to/my/app/application.py
callable = app
max-requests = 1000
chmod-socket = 666
chown-socket = www-data:www-data
master = true
processes = 2
no-orphans = true
log-date = true
uid = www-data
gid = www-data

# the fix
lazy = true
lazy-apps = true

9
谢谢你对自己的回答,它对我今天有所帮助。 - Finch_Powers
13
请注意,存在一个名为“lazy”的旧选项,它具有更强的侵入性并且被强烈不建议使用(它仅保留了向后兼容性)。 - zengr
11
你是如何想出这个方法的?调试的过程是怎样的? - claudio

7
作为替代方案,您可以丢弃引擎。这就是我解决问题的方法。
如果在创建应用程序时出现查询,则可能会出现此类问题,即在创建应用程序本身的模块中。如果是那样,引擎将分配一组连接池,然后uwsgi进行fork。
通过调用'engine.dispose()',连接池本身将关闭,并且只要有人再次开始查询,新的连接就会出现。因此,如果您在创建应用程序的模块末尾执行此操作,UWSGI fork后将创建新的连接。

2
这对我来说是一个更好的解决方案,因为我正在使用gunicorn --preload在分叉之前加载应用程序代码以降低内存使用率,所以我特别不想使用延迟加载。实际上,这是SQLAlchemy文档中engine.dispose()的推荐用例:https://docs.sqlalchemy.org/en/13/core/connections.html#engine-disposal - Jonah Kagan

1
我正在Heroku上使用gunicorn运行flask应用。当我在Procfile中添加了--preload选项后,我的应用程序开始出现问题。当我删除该选项时,我的应用程序恢复正常运行。

0

不确定是否将此作为答案添加到此问题中,还是提出一个单独的问题,并在那里将其作为答案。我遇到了完全相同的错误,原因与已发布和回答的人略有不同。在我的设置中,我使用gunicorn作为Flask应用程序的wsgi。在此应用程序中,我将一些强烈的数据库操作卸载到celery工作程序中。错误将来自celery工作程序。

从阅读这里的许多答案并查看psycopg2以及sqlalchemy会话文档,对我来说显然共享SQLAlchemy会话在单独的进程之间是一个坏主意(在我的情况下是gunicorn工作程序和sqlalchemy工作程序)。

最终解决方案是在celery工作程序函数中创建一个新会话,因此每次调用时都使用新会话,并在每个Web请求后销毁会话,以便flask每个请求使用一个会话。整体解决方案如下:

Flask_app.py

@app.teardown_appcontext
def shutdown_session(exception=None):
    session.close()

celery_func.py

@celery_app.task(bind=True, throws=(IntegrityError))
def access_db(self,entity_dict, tablename):
    with Session() as session:
        try:
            session.add(ORM_obj)
            session.commit()
        except IntegrityError as e:
            session.rollback()
            print('primary key violated')
            raise e

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接