V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
piaochen0
V2EX  ›  Python

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

  •  
  •   piaochen0 · 167 天前 · 2115 次点击
    这是一个创建于 167 天前的主题,其中的信息可能已经有所发展或是发生改变。

    项目是 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
    感激不尽。

    第 1 条附言  ·  138 天前
    感谢各位老哥的赐教
    经过摸索,算是把问题全搞明白了
    卡死的问题是数据库连接池全部被占用,ORM 查询的时候,一直在等待连接
    使用 8 楼老哥的方法发现没有效果,每次执行 celery Task 后,发现 mysql 的连接数还在涨的很厉害
    今天偶然看到这段代码
    from django.db import connections
    # 每一个线程都有专属的 connections ,把本线程名下的所有连接关闭。
    connections.close_all()

    立马在线程里面加了上去,不停运行 Task 测试,发现连接数每次用完都被释放了。
    这个方法只有在线程方法内部使用有效,在线程外无效。

    基本猜测是 celery 的 Task 启动多线程后,线程中使用模型查询后,连接不会被自动释放。
    哪怕在调用这些线程的进程上释放,都没用。(也可能是我没找到方法)
    必须要在线程内部释放。

    希望能给需要的人帮助
    10 条回复    2022-01-20 11:01:53 +08:00
    LemonK
        1
    LemonK  
       167 天前
    mysql ?排查一下有没有其他线程共享同一个 DB 连接且事务未关闭。
    piaochen0
        2
    piaochen0  
    OP
       167 天前
    @LemonK 是的,mysql 。
    当时运行的场景其实是起了好多线程来执行同一个方法,所有的线程都会卡死在那一行。都没有使用事务。
    后来使用单线程来跑,本来以为应该没问题了,但是跑着跑着,也出现了同样的问题。
    当时也没有其他异步任务在执行。
    darkengine
        3
    darkengine  
       167 天前   ❤️ 1
    看下 mysql 日志是不是连接池满了
    zachlhb
        4
    zachlhb  
       166 天前 via iPhone
    有没有用线程?用线程要在每个线程执行完手动关闭数据库连接,否则连接数会爆掉
    kidblg
        5
    kidblg  
       166 天前
    hscxrzs
        6
    hscxrzs  
       166 天前
    如果有可以复现的最小代码集就好了
    ibuler
        7
    ibuler  
       166 天前
    @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
        8
    ibuler  
       166 天前
    也可以通过信号机制来处理,任务执行前,执行后做同样的动作,这样可能更优雅,其他开发人员,不需要再去处理连接的事情

    ```
    # 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
        9
    leven87  
       165 天前
    用 pgsql 想复现,但是没发现有问题
    piaochen0
        10
    piaochen0  
    OP
       163 天前
    @ibuler 我在 celery 中加了这段逻辑,log 日志看也执行了,但是每次运行异步任务后,查看 mysql 的 Max_used_connections ,还在不停的涨。不知道什么原因?
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1442 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 00:10 · PVG 08:10 · LAX 17:10 · JFK 20:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.