django+celery 模型简单查询卡死的诡异问题

2022-01-16 19:44:59 +08:00
 piaochen0

项目是 django 使用 celery 做一些异步任务执行。 目前发现部署后,django view 中调用 celery 异步执行任务时,遇到一个诡异的问题 方法中的一条模型查询语句,在该异步方法被执行一定次数后会直接卡死。
例如 Person.objects.get(id=1)
执行到这一句后,后续语句就不会执行了。几十分钟后仍然不会执行。
也没有任何报错。 数据库表里面肯定有这条数据,而且只有一条数据。 使用 Person.objects.filter(id=1).first()的方式,也是同样的问题。

但是我改成直接用 django.db.connections 执行 sql 语句去查询结果,就没这个问题。

目前发现异步任务方法大概被执行一定数量范围后,就会触发卡死的问题。 而且在这个次数范围内必现。

当次执行卡死后,再次执行该异步任务,大概率就不会卡死了。

不知道有没有其他小伙伴遇到这么诡异的问题的,或者有没有原因的可能思路。
django 版本用的是 2.1.5
celery 版本是 4.4.2
感激不尽。

3259 次点击
所在节点    Python
10 条回复
LemonK
2022-01-16 20:17:05 +08:00
mysql ?排查一下有没有其他线程共享同一个 DB 连接且事务未关闭。
piaochen0
2022-01-16 20:55:49 +08:00
@LemonK 是的,mysql 。
当时运行的场景其实是起了好多线程来执行同一个方法,所有的线程都会卡死在那一行。都没有使用事务。
后来使用单线程来跑,本来以为应该没问题了,但是跑着跑着,也出现了同样的问题。
当时也没有其他异步任务在执行。
darkengine
2022-01-16 21:07:47 +08:00
看下 mysql 日志是不是连接池满了
zachlhb
2022-01-17 08:13:04 +08:00
有没有用线程?用线程要在每个线程执行完手动关闭数据库连接,否则连接数会爆掉
kidblg
2022-01-17 09:41:14 +08:00
hscxrzs
2022-01-17 09:49:52 +08:00
如果有可以复现的最小代码集就好了
ibuler
2022-01-17 10:53:52 +08:00
@kidblg 的思路是对的,如果 celery 使用了线程模型,使用完数据库后应该手动关闭连接,这些动作如果在 view 中不用处理,django 自己操作的。

```
# from django.db import close_old_connections 点进去

# Register an event to reset transaction state and close connections past
# their lifetime.
def close_old_connections(**kwargs):
for conn in connections.all():
conn.close_if_unusable_or_obsolete()

signals.request_started.connect(close_old_connections)
signals.request_finished.connect(close_old_connections)

```

所以在 celery 中,用到的查询,都应该手动执行 close_old_connections ,如果是 celery 是 process 模型就不用处理了
ibuler
2022-01-17 11:04:11 +08:00
也可以通过信号机制来处理,任务执行前,执行后做同样的动作,这样可能更优雅,其他开发人员,不需要再去处理连接的事情

```
# ops/signal_handlers.py
from django.db import close_old_connections
from celery.signals import task_prerun, task_postrun

@task_prerun.connect()
def on_celery_task_pre_run(task_id='', **kwargs):
# 关闭之前的数据库连接
close_old_connections()


@task_postrun.connect()
def on_celery_task_post_run(**kwargs):
close_old_connections()


# ops/apps.py app 中导入信号处理器

class OpsConfig(AppConfig):
name = 'ops'

def ready(self):
from . import signal_handlers
super().ready()

```
leven87
2022-01-18 14:58:44 +08:00
用 pgsql 想复现,但是没发现有问题
piaochen0
2022-01-20 11:01:53 +08:00
@ibuler 我在 celery 中加了这段逻辑,log 日志看也执行了,但是每次运行异步任务后,查看 mysql 的 Max_used_connections ,还在不停的涨。不知道什么原因?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/828599

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX