celery 基本使用

Celery

Tutorials

  • broker
    celery通过消息进行通信,通常使用一个叫Broker(中间人)来协client(任务的发出者)和worker(任务的处理者). clients发出消息到队列中,broker将队列中的信息派发给worker来处理。一个celery系统可以包含很多的worker和broker,可增强横向扩展性和高可用性能。

installation

pip install celery or pip install celery --upgrade

gevent mode : pip install gevent

注意:
celery==5.2.3, python==3.8;

python 版本为3.7时,多进程会出现re-initialized错误。

run

  • 不使用多进程、多线程模式,主要用来测试单独运行模型是否正常。

celery -A celery_main:celery worker -l DEBUG --concurrency 1 -P solo

celery: celery -A tasks worker --loglevel=info --pidfile=celery.pid --logfile=celery.log

using purge:
$celery_bin -A celery_main:celery worker -l debug -c $worker_num --purge
purge: Purge messages from all configured task queues.

kill

kill -INT [WorkerMainProcessID]  #  worker主进程的进程ID需要通过ps -ef | grep celery获取;或者通过 celery -A app inspect stats的输出结果获取
kill -TERM [WorkerMainProcessID] 
flower

installation: pip install flower

run: celery -A celery_main:celery flower --port=8080

Runing Mode

pool eventlet: eventlet==0.25.1;

pool gevnet: gevent==1.4;

celery: celery-5.1.2 (Read the official documents in case of revised api )

Model Initialize

在prefork模式下,单独使用一个加载的模型不能实现多进程; 想要实现是的是对于每一个并发量,加载一个模型,即每一个worker根据并发量来控制模型的数量,从而使处理速度增加,负载均衡;

  1. 在每个worker前进行模型的加载;
@worker_process_init.connect()
def init_worker_process(**kwargs):
    """
    load model before running tasks
    :param kwargs:
    :return:
    """
    global pytorch_model
    pytorch_model = load_model()

load_model()必须是定义在同一个文件下,如果是不同文件,那么在init_worker_process中import相应文件;

from model_threads import loadModel
model_dict = loadModel()

ref:
Unable to use Pytorch with CUDA in Celery task

  1. 重写base task类方法;
    注意在重写之后,对任务进行装饰器修饰需要添加bind=True
    bind意味着该任务第一个参数是task instance(self),这样就能获得task instance中的各种属性和方法。
@task(bind=True, name="my_add")
def add(self, x, y):
    return x + y

ref:
Serving ML Models in Production with FastAPI and Celery

Task

  • task callback
class CustomTask(celery.Task):
    def after_return(self, status, retval, task_id, args, kwargs, einfo):
        try:
            th = threading.Thread(target=push_results, args=(retval,))
            th.start()
        except Exception as e:
            raise ActiveException(code=ErrorCode.ABNORMAL, msg='the after return function run error 
  • task cost time
@task_prerun.connect
def task_prerun_handler(signal, sender, task_id, task, args, kwargs):
    tasks[task_id] = time()


@task_postrun.connect
def task_postrun_handler(signal, sender, task_id, task, args, kwargs, retval, state):
    try:
        cost = time() - tasks.pop(task_id)

ref:
per-task-name

Concurrency

celery中-c参数和-P参数

celery里面的-c参数指定的是并发度,而-P参数指定并发的实现方式,有 prefork(default)、eventlet、gevent等,prefork就是多进程的方式去实现并发。

problems

  • supervisord 中使用celery
    在使用supervisord过程中,应该先退出supervisord,然后再进行celery的kill;

    • kill celery process
      ps auxww | grep celery|grep -v grep|awk '{print $2}'同下:
    • kill gunicorn process
      ps -ef|grep gunicorn|grep -v grep|awk '{print $2}'|xargs -i kill -9 {}
  • 关于tasks结束后,redis依然贮留数据的问题;
    对于异步的任务,可直接在任务装饰器中使用ignore_results即可,如@celery.task(base=CustomTask, ignore_result=True)
    但是,对于同步任务,即使用get()获得结果的任务,此方法无效,解决办法在是celery配置文件中设置redis_results过期时间,即result_expires = 300;

    Don’t store task state. Note that this means you can’t use AsyncResult to check if the task is ready, or get its return value.

  • Received unregistered task of type 'tasks.tasks.detect_img_asy'.
    文件层级结构问题;在celery_main文件中的task路径,需要与主程序导入异步任务函数的路径相同;

    如:from tasks.tasks import detect_invasioninclude=['tasks.tasks'];

    1. 装饰器任务为:@celery_app.task
    2. 在任务中的name属性可以声明确定具体路径。
      @celery.task(name="modelPipline", ignore_result=True)
      ref: 1
  • ModuleNotFoundError: No module named 'celery.app.task'

    • celery安装后重启服务;
  • celery TypeError: 'type' object is not subscriptable

    • 切换至不同模式;
  • celery cannot combined with flower to start;

    • 需要另启cmd来运行flower;
  • celery worker cannot start in srcipts by threaing or process;

  • RuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you must use the 'spawn' start method

    ref: 1

    • fork 与 spawn 模式的区别;

      这里有问题,就是 forked 是啥,spawn 又是啥?这里就需要了解创建子进程的方式了。
      通过torch.multiprocessing.Process(target=training, args=(train_queue)) 创建一个子进程fork和spawn是构建子进程的不同方式,区别在于
      \1. fork: 除了必要的启动资源,其余的变量,包,数据等都集成自父进程,也就是共享了父进程的一些内存页,因此启动较快,但是由于大部分都是用的自父进程数据,所有是不安全的子进程。
      \2. spawn:从头构建一个子进程,父进程的数据拷贝到子进程的空间中,拥有自己的Python解释器,所有需要重新加载一遍父进程的包,因此启动叫慢,但是由于数据都是自己的,安全性比较高。

      回到刚刚那个报错上面去。为啥提示要不能重复加载。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eh9iQy1o-1683875960593)(Notes/fork.png)]

    • CUDA initialization error错误原因:

      1. 这是因为Python3中使用 spawn启动方法才支持在进程之间共享CUDA张量。而用的multiprocessing 是使用 fork 创建子进程,不被 CUDA 运行时所支持。
      2. 在使用PyTorch框架搭建完网络,训练时出现CUDAerror(3):initializationerror(multiprocessing)错误,此类错误(若确定网络搭建没问题的话)多半是因为有其它并行程序正在运行,占用,导致在运行PyTorch程序时无法启动多线程,导致报错。

      P.S 在celery使用过程中,利用init_worker_process来根据并发量来创建模型个数时,大概率会出现此问题,主要原因是在model.cuda或者model.to(device)之前,有其他CUDA的操作。

      例如,在Fairmot进行worker重复加载时,出现此错误主要是因为import DCN时会进行CUDA上的initialization操作;暂且未找到解决DCNv2包的解决办法,折衷处理是将原来的DCN替换为pytorch中自带的DCN模块,即from .dcn import DeformableConv2d as DCN, 具体代码参考附录。

      4/1添加:
      当使用fork模式时,主进程的变量被子进程变量共享,而CUDA的上下文环境是不支持这种共享的,因为CUDA每个子进程的变量都是独立的;
      因此才会报re-initialized错误;
      解决办法是:在init_worker_process中导入模型,包括:

      @worker_process_init.connect
      def init_worker_process():
        model_id = "stabilityai/stable-diffusion-2"
        from diffusers import StableDiffusionPipeline, DDIMScheduler, EulerDiscreteScheduler
      
        # Use the Euler scheduler here instead
        scheduler = EulerDiscreteScheduler.from_pretrained(model_id, subfolder="scheduler")
        pipe = StableDiffusionPipeline.from_pretrained(model_id, scheduler=scheduler, torch_dtype=torch.float16)
      

      是要统一写在init_worker下的,如果不是这样stablediffusionpipline是被其他子进程共享。

      ref:

      PyTorch-Deformable-Convolution-v2

  • TypeError: __init__() got an unexpected keyword argument 'username'

    • celery中的redis使用localhost有问题,需要指明ip地址;

    • celery中的redis安装一定需要按照官方文档安装!

      $ pip install -U "celery[redis]"
      

      ref:official documents

  • 设置多进程启动方法。

    import torch.multiprocessing as mp
    mp.set_start_method('spawn')
    

    此方法是使用multiprocessing时需要使用pytorch模型时,需要设置的方法;但是,如果使用fork似乎也运行正常。

  • CUDA kernel errors might be asynchronously reported at some other API call,so the stacktrace below might be incorrect.

    直接运行时export CUDA_LAUNCH_BLOCKING=1;

  • 运行过程中提升没有torch

    • 主要是因为安装celery的时候,是以-U的形式安装在.local, 而在此环境中没有安装pytorch;因此,需要使用在特定的anaconda环境下安装celery,然后使用celery命令时指明位置,如(zzzj_test) zzzj@alpha-AI:~/Projects/task_intrusion_celery/server$ /home/zzzj/.conda/envs/zzzj_nni/bin/celery -A celery_main:celery worker -l INFO -c 1 -P solo
  • celery使用redis作为broker,报错ConnectionError: Too many connections
    主要是在进行高并发测试时,解决方法为将celery.conf.broker_pool_limit = 20数目调大,默认为10.

  • TypeError: Object of type ndarray is not JSON serializable
    主要原因是redis中储存的数据不能是自定义的数据类型或者numpy.array类型,对于图像数据转换为base64,对于自定义数据类型,使用object.__dict__以字符串形式传递。

  • ImportError:cannot import name ‘Celery‘ from ‘celery
    python 版本和celery版本问题。

  • RuntimeError: Never call result.get() within a task!
    使用celery canvas中的group任务类型实现分组任务

  • Celery批量异步调用任务一直等待结果
    使用group异步调用时,如果一直等待结果,部分原因是任务没有保存结果,即ignore_result=True; 应该设置为默认值False;

  • 使用两个celery时,会出现 Received unregistered task of type 'celery.atomTask'.

  • Celery: stuck in infinitly repeating timeouts (Timed out waiting for UP message)
    主要是worker在init时耗时过长,提升PROC_ALIVE_TIMEOUT时长;

  • RuntimeWarning:You're running the worker with superuser privileges:this is absolutely not recommended

    from celery import platforms
    platforms.C_FORCE_ROOT=True
    

    或者使用uid,ugroup

    exec celery --app=app worker \
              --loglevel=INFO --logfile=/var/log/celery/worker-example.log \
              --statedb=/var/run/celery/worker-example@%h.state \
              --hostname=worker-example@%h \
              --queues=celery.example -O fair \
              --uid=nobody --gid=nogroup
    

    当两者无效时,使用multi运行时,就可以了。

  • celery在docker中使用时,会出现stuck现象,
    主要原因是docker需要使用本机的redis;(保留)

ref:

  • Celery: number of workers vs concurrency

  • Celery Task中一些有用的回调函数

  • Python分布式任务框架Celery的异步任务实现

  • Unable to use Pytorch with CUDA in Celery task

你可能感兴趣的:(python)