我已经重构了一些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>
答案 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
。