celery异步任务队列入门
参考:
Celery入门
任务调度delay&apply_async
celery 简要概述
Celery 中文手册
Celery动态添加定时任务
全网最细之Celery 4.x动态添加定时任务(这个还可以)
win10 Celery异步任务报错: Task handler raised error: ValueError('not enough values to unpack (expected 3, got 0)
celery最佳实践
Flower_0.9.1-Celery 监控工具
Celery使用指南
celery redis版本踩过的坑
celery配置 - Celery官方文档
使用Celery调度任务(下)
将CELERY_ALWAYS_EAGER设置为True会引发错误
celery同步执行任务(在项目中debug任务)
CELERY 常用配置介绍
Celery 信号
前言
在开发工作中,我们很多时候需要对一些任务做异步处理,或者要执行某些定时任务。在蓝鲸python开发框架中,采用了celery这个异步任务队列框架,去调度和执行我们开发的任务。今天来了解一下celery的几个概念、处理流程原理和使用配置。
概念
Celery是一个异步任务的调度工具,是Distributed Task Queue,分布式任务队列,分布式决定了可以有多个worker的存在,队列表示其是异步操作,即存在一个产生任务提出需求的工头,和一群等着被分配工作的码农。
celery可以执行和调度任务,但本身不提供队列服务,所以我们还需要一个消息队列中间件。
来看看celery的几个核心概念:
- task:就是任务,celery调度和执行的对象,有异步任务和周期任务
- broker:中间人,接收生产者发来的消息即task,将任务存入队列,由worker去消费。一般会配置使用redis和rabbitmq作为celery的消息中间件。
- worker:执行任务的单元,它实时监控消息队列,如果有任务就获取任务并执行它。也就是我们任务队列的消费者,worker可以有多个。一个worker只能同时执行一个任务,多个worker可以并行执行,如果worker都满了那剩下的任务就会进入队列排队。
- beat:任务调度器,beat进程会读取配置文件的内容,周期性的将配置中到期需要执行的任务发送给任务队列。django-celery-beat插件本质上是对数据库表变化检查,一旦有数据库表改变,调度器重新读取任务进行调度
- backend:任务执行结果存储,保存任务执行的结果和状态,可以是数据库,或者缓存。
使用
来看看在蓝鲸python3开发框架中怎么使用celery
先找到PROJECT_ROOT/config/default.py
# CELERY 开关,使用时请改为 True,修改项目目录下的 Procfile 文件,添加以下两行命令: # worker: python manage.py celery worker -l info # beat: python manage.py celery beat -l info # 不使用时,请修改为 False,并删除项目目录下的 Procfile 文件中 celery 配置 IS_USE_CELERY = True # CELERY 并发数,默认为 2,可以通过环境变量或者 Procfile 设置 CELERYD_CONCURRENCY = os.getenv("BK_CELERYD_CONCURRENCY", 8) # noqa # CELERY 配置,申明任务的文件路径,即包含有 @task 装饰器的函数文件 CELERY_IMPORTS = ("portrait.celery_tasks", "data_sync.celery_tasks", "common.celery_tasks") """ 以下为框架代码 请勿修改 """ # celery settings if IS_USE_CELERY: INSTALLED_APPS = locals().get("INSTALLED_APPS", []) import djcelery INSTALLED_APPS += ("djcelery",) djcelery.setup_loader() CELERY_ENABLE_UTC = False # django_celery_beat调度器 CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler"
把IS_USE_CELERY打开,设置并发的worker数量,以及从哪些模块中导入celery任务。
这里CELERY_IMPORTS如果不配置,默认导入每个app下面的tasks模块
然后到我们定义的celery模块去写我们的任务,比如common.celery_tasks
@task def a(): print("aaa") @periodic_task(run_every=crontab(minute="*/1")) def b(): print("bbb")
加上了@task、@periodic_task这样装饰器的函数,就是可以被celery调度执行的任务,我们可以使用它的delay()和apply_async()来调度执行任务。@periodic_task装饰的任务还会被beat监听并周期性加入到worker。
delay()与apply_async()
delay与apply_async都是用来做任务调度,但如果查看delay的源码会发现最终还是调用了apply_async,简单来说delay就是apply_async的快捷方式,而apply_async则有很多参数来控制任务调度。
apply_async支持常用参数:
- eta:指定任务执行时间,类型为datetime时间类型;
- countdown:倒计时,单位秒,浮点类型;
- expires:任务过期时间,如果任务在超过过期时间还未开始执行则回收任务,浮点类型(单位秒)获取datetime类型;
- retry:任务执行失败时候是否尝试,布尔类型;
- serializer:序列化方案,支持pickle、json、yaml、msgpack;
- priority:任务优先级,有0~9优先级可设置,int类型;
- retry_policy:任务重试机制,其中包含几个重试参数,类型是dict如下:
- max_retries:最大重试次数
- interval_start:重试等待时间
- interval_step:每次重试叠加时长,假设第一重试等待1s,第二次等待1+n秒
- interval_max:最大等待时间
自定义任务类
我们可以自定义celery的Task和PeriodicTask任务类,重写回调方法加入自己的处理逻辑。
from celery.task.base import PeriodicTask, Task from blueapps.utils.logger import logger_celery class CustomTask(Task): """自定义Celery任务类""" abstract = True pass class CustomPeriodicTask(PeriodicTask): """自定义Celery周期任务类""" abstract = True def on_success(self, retval, task_id, args, kwargs): """异步任务执行成功时,会执行这个回调方法""" return super().on_success(retval, task_id, args, kwargs) def on_failure(self, exc, task_id, args, kwargs, einfo): """异步任务执行失败时,会执行这个回调方法""" logger_celery.exception("task执行失败,name: {}[{}],msg: {}".format(self.name, task_id, exc)) return super().on_failure(exc, task_id, args, kwargs, einfo) def retry( self, args=None, kwargs=None, exc=None, throw=True, eta=None, countdown=None, max_retries=None, **options ): """异步任务尝试重试时,会执行这个回调方法""" return super().retry(args, kwargs, exc, throw, eta, countdown, max_retries, **options) def after_return(self, status, retval, task_id, args, kwargs, einfo): """异步任务执行成功,并且return了一些内容,会执行这个回调方法""" return super().after_return(status, retval, task_id, args, kwargs, einfo) def update_state(self, task_id=None, state=None, meta=None): """可以手动调用这个方法来更新任务状态""" return super(CustomPeriodicTask, self).update_state(task_id, state, meta) def send_error_email(self, context, exc, **kwargs): """异步任务执行失败时,并且配置了send_error_emails=True时,会执行这个回调方法""" return super().send_error_email(context, exc, **kwargs)
绑定任务
我们可以给任务绑定自身实例,拿到任务相关的信息
@task(bind=True) def b(self): time.sleep(4) print("bbb", self.request)
这里self就是任务实例b,self.request包含了task、id、args、kwargs、retries、eta、expires等很多属性。
我们可以拿到task_id去获取任务执行状态。
@task(bind=True) def b(self): time.sleep(4) task_id = self.request.id print("bbb", task_id) result = self.AsyncResult(task_id) status = result.status
当然了没有开启结果存储会报错AttributeError("'DisabledBackend' object has no attribute '_get_task_meta_for'",)
AsyncResult对象里面存的就是一个异步的结果,当任务完成时result.ready()为true,然后用result.get()也可以取结果。
>>> b.delay() <AsyncResult: ae3d31f5-8efe-4d2b-af1c-46a1be83d445> >>> b.AsyncResult("ae3d31f5-8efe-4d2b-af1c-46a1be83d445") <AsyncResult: ae3d31f5-8efe-4d2b-af1c-46a1be83d445> >>> result = b.AsyncResult("ae3d31f5-8efe-4d2b-af1c-46a1be83d445") >>> result <AsyncResult: ae3d31f5-8efe-4d2b-af1c-46a1be83d445> >>> result.ready() True >>> result.status 'SUCCESS' >>> result._get_task_meta() {'status': 'SUCCESS', 'result': None, 'traceback': None, 'children': []}
celery的工作流
Task.delay() --> apply_async --> send_task --> amqp.create_task_message --> amqp.send_task_message --> result=AsyncResult(task_id)返回result
delay之后调用实际上是apply_async之后 调用的send_task之后开始创建任务,发送任务,然后生成一个异步对象,把这个结果返回。
日志打印
在celery任务中,我们使用print()打印的信息级别是warning,建议使用logging明确日志级别打印。打印的日志正常是在worker中,beat只与周期任务的调度有关。
celery命令
在win10下需要指定协程类型eventlet,否则可能会报错
-P/--pool是POOL的配置,默认是prefork(并发),solo是串行,并发可以有prefork、gevent、eventlet,
-l日志级别,-f日志文件路径,在linux上可以用-B参数同步启动celery beat
python manage.py celery worker -l INFO -P eventlet python manage.py celery beat -l INFO
动态添加定时任务
PeriodicTask
此模型定义要运行的单个周期性任务。
- 必须为任务指定一种Schedule,即clocked, interval, crontab, solar四个字段必须填写一个,且只能填写一个
- name字段给任务命名,它是unique的
- task字段指定运行的Celery任务,如"proj.tasks.test_task"
- one_off:默认值为False,如果one_off=True,任务被运行一次后enabled字段将被置为False,即任务只会运行一次
- args:传递给任务的参数,是一个json字符串,如 ["arg1", "arg2"]
- expires:过期时间,过期的任务将不再会被驱动触发
使用ClockedSchedule:在特定的时间执行任务
def test_clock(): clock = ClockedSchedule.objects.create(clocked_time=datetime.now() + timedelta(seconds=10)) PeriodicTask.objects.create( name="%s" % str(datetime.now()), task="project_celery.celery_app.test_task", clocked=clock, # 如果使用ClockedSchedule,则one_off必须为True one_off=True )
使用IntervalSchedule:以特定间隔运行的Schedule
用IntervalSchedule能够实现与ClockedSchedule同样的功能:计算目标时间与当前时间的时间差,令此时间差作为IntervalSchedule的周期,并且将任务的one_off参数置为True
def time_diff(target_time): diff = target_time - datetime.now() return int(diff.total_seconds()) def test_interval(): seconds = time_diff(datetime.strptime("2020-3-19 15:39:00", "%Y-%m-%d %H:%M:%S")) schedule = IntervalSchedule.objects.create(every=seconds, period=IntervalSchedule.SECONDS) PeriodicTask.objects.create( name="%s" % str(datetime.now()), task="project_celery.celery_app.test_task", interval=schedule, one_off=True )
使用CrontabSchedule
使用CrontabSchedule一定要注意将时区设置为当前地区时区
model参数与crontab表达式的对应关系:
minite, hour, day_of_week, day_of_month, month_of_year
全部默认为"*"
def test_crontab(): # 表示 * * * * * ,即每隔一分钟触发一次 schedule = CrontabSchedule.objects.create(timezone='Asia/Shanghai') PeriodicTask.objects.create( name="%s" % str(datetime.now()), task="project_celery.celery_app.test_task", crontab=schedule, one_off=True )
flower可视化工具
pip install flower==0.9.1 python manage.py flower
celery配置
# 时区 CELERY_TIMEZONE = "Asia/Shanghai" # 消息中间件 BROKER_URL = "redis://:password@localhost:6379/0" # 使用redis存储任务执行结果,默认不使用 # 遇到redis.exceptions.ResponseError时降低版本pip install --upgrade redis==2.10.6 CELERY_RESULT_BACKEND = "redis://:123456@localhost:6379/1" # 任务序列化和反序列化为json CELERY_TASK_SERIALIZER = "json" # 存储结果序列化为json CELERY_RESULT_SERIALIZER = "json" # CELERY 并发数,worker数量 CELERYD_CONCURRENCY = 2 # 禁用时区设置 CELERY_ENABLE_UTC = True # 限制任务的执行频率 # 下面这个就是限制tasks模块下的add函数,每秒钟只能执行10次 CELERY_ANNOTATIONS = {'tasks.add':{'rate_limit':'10/s'}} # 或者限制所有的任务的刷新频率 CELERY_ANNOTATIONS = {'*':{'rate_limit':'10/s'}} # 也可以设置如果任务执行失败后调用的函数 def my_on_failure(self,exc,task_id,args,kwargs,einfo): print('task failed') CELERY_ANNOTATIONS = {'*':{'on_failure':my_on_failure}} # 并发的worker数量,也是命令行-c指定的数目 # 事实上并不是worker数量越多越好,保证任务不堆积,加上一些新增任务的预留就可以了 CELERYD_CONCURRENCY = 4 # celery worker每次去mq取任务的数量,默认值就是4 CELERYD_PREFETCH_MULTIPLIER = 4 # 每个worker执行了多少次任务后就会死掉,建议数量大一些 CELERYD_MAX_TASKS_PER_CHILD = 200 # celery任务执行结果的超时时间 CELERY_TASK_RESULT_EXPIRES = 1200 # 单个任务的运行时间限制,超过会被杀死 CELERYD_TASK_TIME_LIMIT = 60 CELERY_DISABLE_RATE_LIMITS = True # 任务本地阻塞执行 CELERY_ALWAYS_EAGER = False
CELERY_ALWAYS_EAGER
开启后所有任务都会本地阻塞执行,apply_async()和delay()都只会在执行完返回结果,与普通方法一样执行而没有异步效果。
不会送到队列和worker,一切由beat调度执行,是串行的。
因为不经过worker和中间件,所以只在beat打印日志,并且对于异常中断不会打印任何信息,worker和中间件看不到任何记录。
好处是你可以在不启动worker或消息中间件的情况下执行你的任务或者debug你的任务。
CELERY_ALWAYS_EAGER = False
以下代码在CELERY_ALWAYS_EAGER开关下分别放到celery中执行,就可以看到明显的串行和并行区别。
@periodic_task(run_every=crontab(minute="*/1"), base=CustomPeriodicTask) def a(): time.sleep(30) print("aaa") raise Exception("hahah") @periodic_task(run_every=crontab(minute="*/1"), base=CustomPeriodicTask) def b(self): time.sleep(4) print("bbb")
本文来自博客园,作者:云白Li,转载请注明原文链接:https://www.cnblogs.com/libaiyun/p/16462476.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示