Celery - res.ready() hang住问题的发现与解决办法

背景

做celery的一个实验,实验内容是将一堆任务分组执行,等待一组任务执行完成后再执行下一组任务。但是,当启动1个worker并且在分组函数是被异步调用的时, 获取这一组任务的ready()时会hang住。

服务端源码:

# -*- utf-8 -*-
from __future__ import absolute_import, unicode_literals, print_function
from .config import app

from celery import Task, group

@app.task()
def save(s):
    print(s)

@app.task()
def distribution(indexs):
    for i in range(0, len(indexs), 5):
        start = i
        end = (i + 5) if (i + 5) <= len(indexs) else len(indexs)

        L = []
        for j in range(start, end):
            L.append(save.s(indexs[j]))

        res = group(L)()

        while not res.ready():
            time.sleep(1)

客户端源码:

from proj.tasks_2 import distribution
from celery import group

import time

if __name__ == '__main__':
   indexs = [1, 2, 3, 4, 5, 6, 7, 8]
   res = distribution.delay(indexs)

执行结果:

# 服务端启动命令
$ celery -A proj.tasks_2 worker --concurrency=1 --loglevel=info

# 客户端执行
$ python client.py

# 观察服务端情况
[2020-07-15 17:04:06,105: INFO/MainProcess] Received task: proj.tasks_2.save[9a7876dc-e4f5-4a26-8b73-ed1fa2f1a786]
[2020-07-15 17:04:06,110: INFO/MainProcess] Received task: proj.tasks_2.save[d4e56ee1-4180-477a-ae02-a526056e2042]
[2020-07-15 17:04:06,113: INFO/MainProcess] Received task: proj.tasks_2.save[f4cd1e81-7bb8-4462-8b5c-a194175456d8]
[2020-07-15 17:04:06,114: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:07,116: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:08,118: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:09,120: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:10,122: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:11,124: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:12,125: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:13,127: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:14,129: WARNING/ForkPoolWorker-1] sky hang...
[2020-07-15 17:04:15,131: WARNING/ForkPoolWorker-1] sky hang...

分析原因

调试时发现,当分发任务的函数distribution函数不再异步调用,比如客户端写成下面的样子:

from proj.tasks_2 import distribution
from celery import group

import time

if __name__ == '__main__':
   indexs = [1, 2, 3, 4, 5, 6, 7, 8]
   res = distribution(indexs)

就不在hang住。或者,服务端启动大于1个worker时,也可以保证这种情况不被hang住。

根据这种现象结合celery的整体框架,分析原因。是当前若仅有一个worker启动着,即仅有一个进程。那么如果仅有的worker没有处理完成当前的task,是没有其余的worker能够处理新产生的task的。而原实验中第一个task的结束是需要新产生的task的处理完成才能正常退出。由此,也就被迫陷入了死循环中。

综上分析,可以得出一个结论。无论worker启动数量如何(总有一种情况可以占满所有的worker),在celery框架中不能够在一个异步的task中嵌套另一个异步的task,因为这极有可能产生隐藏的死循环陷阱。

解决案例

如果,客户端一定要使用异步调用的方式,那就不再启用celery的方式而是通过自己写异步方法。

from proj.tasks_2 import distribution
from celery import group

import time
from threading import Thread

def async(f):
    def wrapper(*args, **kwargs):
        thr = Thread(target = f, args = args, kwargs = kwargs)
        thr.start()
    return wrapper

@async
def func(indexs):
   res = distribution(indexs)

if __name__ == '__main__':
   indexs = [1, 2, 3, 4, 5, 6, 7, 8]
   #res = distribution_middle.delay(indexs)
   res = func(indexs)
   print(res)

附录:

配置文件:

from __future__ import absolute_import, unicode_literals

from celery import Celery

app = Celery('proj',
             broker='redis://127.0.0.1:6379',
             backend='redis://127.0.0.1:6379/0',
             include=['proj.tasks'])

app.conf.update(
    result_expires = 3600,

    #task_routes = {'proj.tasks.add': {'queue': 'hipri'}}
)

if __name__ == '__main__':
    app.start()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
我想将frontend 也是用volumes,将其映射到/app/frontend目录,在/app/frontend下install以及build,如何实现 docker-compose.yml文件: version: '3' services: frontend: build: context: ./frontend dockerfile: Dockerfile ports: - 8010:80 restart: always backend: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: python manage.py runserver 0.0.0.0:8000 ports: - 8011:8000 restart: always celery-worker: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: celery -A server worker -l info --pool=solo --concurrency=1 depends_on: - redis - backend restart: always celery-beat: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: celery -A server beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler depends_on: - redis - backend restart: always redis: image: redis:latest ports: - 6379:6379 restart: always mysql: image: mysql:latest environment: - MYSQL_ROOT_PASSWORD=sacfxSql258147@ ports: - 8016:3306 volumes: - ./mysql:/var/lib/mysql restart: always frontend:dockerfile文件 FROM node:16.18.1 WORKDIR /app/frontend COPY package*.json ./ RUN npm install COPY . . RUN npm run build:prod FROM nginx:latest COPY --from=0 /app/frontend/dist/ /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
最新发布
07-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值