如何在celery任务中启动的asyncio协同例程中利用django模型?

时间:2019-02-16 18:23:34

标签: postgresql celery python-3.6 python-asyncio django-2.1

我已经重构了一些Django代码来进行某些网络抓取。我为要进行数据抓取的每个用户启动一个单独的Celery任务。在每个Celery任务中,我使用asyncio和aiohttp对给定的用户进行抓取。

我可以访问所有django模型类和方法,但是一旦我做一些事情来触发实际的数据库查询,就会收到类似以下的错误:

d3

在Celery Tasks中,我可以做使Django与数据库交互而没有任何问题的事情,只要它们不涉及异步。同样,只要不是从Celery Task内反过来启动异步任务,我就可以通过Django在asyncio任务中与数据库成功交互。

如果我设置了... [2019-02-16 18:04:38,126] WARNING log /home/chrisadmin/anaconda3/lib/python3.6/site-packages/celery/app/trace.py:561: RuntimeWarning: Exception raised outside body: OperationalError('SSL SYSCALL error: Bad file descriptor\n',): Traceback (most recent call last): File "/home/chrisadmin/anaconda3/lib/python3.6/site-packages/django/db/backends/utils.py", line 85, in _execute return self.cursor.execute(sql, params) psycopg2.OperationalError: SSL SYSCALL error: Socket operation on non-socket ... ,我不会得到例外,但是在这种情况下,当然Celery Tasks不会并发运行。

要抓取单个用户,asyncio / aiohttp绰绰有余。但是我想使用Celery能够在进程/机器之间横向扩展并并行抓取多个用户。以前,我曾尝试过专门使用Celery,但是我尝试使用asyncio / aiohttp进行重构,以减少不必要的开销。

我希望能够使用Celery并行为多个用户启动抓取,然后在每个Celery Task中,我希望能够抓取各自的用户,包括通过Django模型/方法保存他们的抓取数据。< / p>

1 个答案:

答案 0 :(得分:0)

经过进一步的调查,当我不完全理解异步与celery结合使用时,似乎数据库连接和线程安全可能存在问题。

到目前为止,似乎可行的解决方案是创建一个新函数:

from django.db import connections
from django.conf import settings


def reset_db_connections():
    if not settings.CELERY_TASK_ALWAYS_EAGER:
        connections.close_all()

我将此函数称为任何芹菜任务的第一行:

@shared_task(bind=True)
def my_celery_task(self, args):
    reset_db_connections()
    # do stuff
    # call stuff that uses asyncio

到目前为止,无论我为CELERY_TASK_ALWAYS_EAGER进行什么设置,这似乎都可以使我的代码正常工作。

我最初尝试的不是connections.close_all()

 for conn in db.connections.all():
     if conn.connection.closed != 0:
         conn.connection.close()

但是在我没有关闭需要关闭的连接和/或我正在关闭已经关闭的连接的情况下导致了错误。

作为上述解决方案的替代方案,我发现更改了设置:

DATABASES = {"default": dj_database_url.config(conn_max_age=600)}

DATABASES = {"default": dj_database_url.config(conn_max_age=0)}

也解决了该问题。但是,据我了解,设置conn_max_age=0会为每个数据库操作使用一个新的连接,这似乎不是一个好主意。使用上面的reset_db_connections()方法,我可以离开conn_max_age=600