celery
- web应用:当用户在网站进行某个操作需要很长时间完成时,可以将这种操作交由celery执行,直接返回给用户,等到celery执行完成以后通知用户
- 任务场景:比如在运维场景下需要批量在几百台机器上执行某些命令或者任务,此时celery可以轻松处理
- 定时任务:向定时导数据报表、定时发送通知类似场景,虽然Linux的计划任务可以帮我实现,但是非常不利于管理,而Celery可以提供管理接口和丰富的API。
celery架构
- 消息中间件:broker
- 提交的任务【函数】都放在这里, celery本身不能提供消息中间件
- 需要借助于第三方: redis或rabbitmq
- 任务执行单元:worker
- 真正执行任务的的地方,一个个进程中执行函数
- 结果储存:backend
- 函数return的结果都存储在这里, celery本身不提供结果存储
- 需要借助于第三方: redis或rabbitmq
celery快速使用
celery不支持在Windows上使用,可以通过eventlet在windows上运行
pip install celery
pip install eventlet
简单使用
目录结构
project/
├── __init__.py
├── config.py
└── tasks.py
各目录文件说明
-
__init__.py
初始化celery以及加载配置文件#!/usr/bin/env python3 # -*- coding:utf-8 -*- # Author:wd from celery import Celery app = Celery('project') # 创建 Celery 实例 app.config_from_object('project.config') # 加载配置模块
-
config.py celery相关配置文件
更多配置参考:http://docs.celeryproject.org/en/latest/userguide/configuration.html
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # Author:wd BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件 CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案 CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间 CELERY_TIMEZONE='Asia/Shanghai' # 时区配置 CELERY_IMPORTS = ( # 指定导入的任务模块,可以指定多个 'project.tasks', )
-
task.py文件
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # Author:wd from project import app @app.task def show_name(name): return name
启动Worker
celery worker -A project -l debug
各个参数含义:
worker: 代表第启动的角色是work当然还有beat等其他角色;
-A :项目路径,这里我的目录是project
-l:启动的日志级别,更多参数使用celery --help查看
查看日志输出,会发现我们定义的任务,以及相关配置:
虽然启动了Worker,但是还需要通过delay或apply_async来将任务添加到Worker中,这里我们通过交互式方式添加任务,并返回AsyncResult对象,通过AsyncResult对象获取结果:
AsyncResult除了get方法用于常用获取结果方法外还提以下常用方法或属性:
- state: 返回任务状态;
- task_id: 返回任务id;
- result: 返回任务结果,同get()方法;
- ready(): 判断任务是否以及有结果,有结果为True,否则False;
- info(): 获取任务信息,默认为结果;
- wait(t): 等待t秒后获取结果,若任务执行完毕,则不等待直接获取结果,若任务在执行中,则wait期间一直阻塞,直到超时报错;
- successfu(): 判断任务是否成功,成功为True,否则为False;
celery 包结构
创建celery包结构
什么是包结构:通过将celery服务封装成包的形式,放在项目需要使用的时候导入即可
project
├── celery_task # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery连接和配置相关文件,且名字必须交celery.py
│ └── tasks.py # 所有任务函数
│ └── settings.py
├── add_task.py # 添加任务
└── get_result.py # 获取结果
- 1、第一步:在包下创建py文件(名字必须为celery.py)
# 导入celery模块
from celery import Celery
# 导入配置broker和backend
from .settings import BACKEND, BROKER
# 实例化celery对象
app = Celery('test',
broker=BROKER,
backend=BACKEND,
include=['celery_task.order_task',
'celery_task.user_task'])
- 2、第二步:创建settings.py,用于存放配置
BROKER = 'redis://127.0.0.1:6379/1'
BACKEND = 'redis://127.0.0.1:6379/2'
- 3、第三步,创建py文件(task.py),用于存放需要执行的异步任务
# 导入celery实例对象
from .celery import app
# 计算函数
@app.task()
def add(a, b):
print('计算结果为:', a + b)
return True
# 模拟发送短信
@app.task()
def send_sms(mobile, code):
print('已向手机号:%s 发送短信,验证码为:%s' % (mobile, code))
return True
- 4、第四步:开启worker
切换到celery所在的目录下,开启worker命令
celery -A celery_task worker -l info -P eventlet
- 5、第五步:提桥任务: # add_task.py 文件下
# 提交任务,这里模拟的是异步任务的提交
res = add.delay(a, b) # 提交后可以接收任务的ID
res1 = send_sms.delay(mobile, code)
- 6、第六步:查看任务执行结果: # get_result.py 文件下
# 导入celery实例
from celery_task.celery import app
from celery.result import AsyncResult
id = res
id1 = res1
# 通过传入任务的ID就可以查询到任务的执行结果
def res_func(id):
id = id
a = AsyncResult(id=id, app=app)
if a.successful(): # 执行完了
result = a.get()
if result: return '执行完成'
elif a.failed():
return '任务失败,失败的原因可能是未开启worker'
elif a.status == 'PENDING':
return '任务等待中被执行,当前任务较多或未开启worker'
elif a.status == 'RETRY':
return '任务异常后正在重试'
elif a.status == 'STARTED':
return '任务已经开始被执行,请稍后查询'
Celery执行异步任务、延迟任务、定时任务
定时任务&计划任务
celery的提供的定时任务主要靠schedules完成
执行定时任务需要启动beat和worker
- beat:定时提交任务的进程---》配置在app.conf.beat_schedule的任务
- worker:执行任务
period_task.py:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from project import app
from celery.schedules import crontab
@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
sender.add_periodic_task(10.0, add.s(1,3), name='1+3=') # 每10秒执行add
sender.add_periodic_task(
crontab(hour=16, minute=56, day_of_week=1), #每周一下午四点五十六执行sayhai
sayhi.s('wd'),name='say_hi'
)
@app.task
def add(x,y):
print(x+y)
return x+y
@app.task
def sayhi(name):
return 'hello %s' % name
config.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件
CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis
CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
CELERY_TIMEZONE='Asia/Shanghai' # 时区配置
CELERY_IMPORTS = ( # 指定导入的任务模块,可以指定多个
'project.tasks',
'project.period_task', #定时任务
)
启动Worker和beat:
celery worker -A project -l debug #启动work
celery beat -A project.period_task -l debug #启动beat,注意此时对应的文件路径
或者通过配置文件方式指定定时和计划任务,此时的配置文件如下:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from project import app
from celery.schedules import crontab
BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件
CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis
CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
CELERY_TIMEZONE='Asia/Shanghai' # 时区配置
CELERY_IMPORTS = ( # 指定导入的任务模块,可以指定多个
'project.tasks',
'project.period_task',
)
app.conf.beat_schedule = {
'period_add_task': { # 计划任务
'task': 'project.period_task.add', #任务路径
'schedule': crontab(hour=18, minute=16, day_of_week=1),
'args': (3, 4),
},
'add-every-30-seconds': { # 每10秒执行
'task': 'project.period_task.sayhi', #任务路径
'schedule': 10.0,
'args': ('wd',)
},
}
此时的period_task.py只需要注册到woker中就行
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from project import app
@app.task
def add(x,y):
print(x+y)
return x+y
@app.task
def sayhi(name):
return 'hello %s' % name
同样启动worker和beat结果和第一种方式一样。
更多详细的内容请参考:http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#crontab-schedules
-
执行异步任务
# 代码用法: 函数名.delay('函数执行需要的参数') res = func.delay(*args,**kwargs) # res 用于接收提交任务的ID
-
执行延迟任务
# 代码用法: # 1、执行延迟任务 from datetime import datetime, timedelta # 设置延迟后的时间,一分钟后执行 eat = datetime.utcnow() + timedelta(minutes=1) # 提交任务 res = send_sms.apply_async(args=['13855411111', '123'], eta=eta)
django中使用celery
Django的请求处理过程都是同步的无法实现异步任务,若要实现异步任务需要通过其他方式。
补充:
如果在公司中,只做定时任务可以使用更简单一点的框架
APSchedule:https://blog.csdn.net/qq_41341757/article/details/118759836
请求过程简单说明:浏览器发起请求 --> 请求处理 --> 请求经过中间件 ---> 路由映射 --> 视图处理业务逻辑 ---> 响应请求(template或Response)
配置使用
celery很容易集成到Django框架中,要实现定时任务还需要安装django-celery-beta
插件,需要注意的是Celery4.0只支持Django版本>=1.8的,如果是小于1.8版本需要使用Celery3.1。
配置
新建taskproj,目录结构(每个app下多了一个tasks文件,用于定义任务)
taskproj
├── app01
│ ├── __init__.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tasks.py
│ └── views.py
├── manage.py
├── taskproj
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── templates
在项目目录taskproj/taskproj/目录下新建celery.py:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'taskproj.settings') # 设置django环境
app = Celery('taskproj')
app.config_from_object('django.conf:settings', namespace='CELERY') # 使用CELERY_ 作为前缀,在settings中写配置
app.autodiscover_tasks() # 发现任务文件每个app下的task.py
taskproj/taskproj/init.py:
from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
__all__ = ['celery_app']
taskproj/taskproj/settings.py
CELERY_BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件
CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis
CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案
进入项目的taskproj目录启动worker:
celery worker -A taskproj -l debug
定义与触发任务
任务定义在每个tasks文件中,app01/tasks.py:
from __future__ import absolute_import, unicode_literals
from celery import shared_task
from taskproj import celery_app
@celery_app.task(bind=True)
def add(self, x, y):
return x + y
@shared_task
def mul(x, y):
return x * y
视图中触发任务
from django.http import JsonResponse
from app01 import tasks
# Create your views here.
def index(request,*args,**kwargs):
res=tasks.add.delay(1,3)
#任务逻辑
return JsonResponse({'status':'successful','task_id':res.task_id})
若想获取任务结果,可以通过task_id使用AsyncResult获取结果,还可以直接通过backend获取:
模拟写一个异步秒杀任务
# view.py
from celery.result import AsyncResult
from celery_task.celery import app
from celery_task.task import sckill_task
# 秒杀接口
class SeckillView(ViewSet):
# 开启秒杀
@action(methods=['GET'], detail=False)
def seckill(self, request):
# 获取商品链接
goods_id = request.query_params.get('goods_id')
# 将任务提交给worker
res = sckill_task.delay(goods_id)
# 将任务的ID反馈给前端
return APIResponse(task_id=str(res))
# 查询秒杀结果
@action(methods=['GET'], detail=False)
def get_result(self, request):
# 前端将任务ID产过来,用于接收结果
task_id = request.query_params.get('task_id')
# 调用接口,查询结果
a = AsyncResult(id=task_id, app=app)
if a.successful():
result = a.get()
if result:
return APIResponse(msg='秒杀成功')
else:
return APIResponse(code=101, msg='手速满了,秒杀失败')
elif a.status == 'PENDING':
return APIResponse(code=666, msg='加速秒杀中')
return APIResponse(msg='错误')
celery.py
import random
# 秒杀函数
@app.task()
def sckill_task(goods_id):
print('商品正在秒杀中')
time.sleep(random.choice([6, 7, 8, 9]))
print('商品秒杀结束')
return random.choice([True, False])
总结
第一步:将celery包复制到项目路径下
-luffy_api
-celery_task #celery的包路径
celery.py # 一定不要忘了一句话
import os
# 重点:celery中使用djagno,任务中可能会使用django的orm,缓存,表模型。。。。一定要加
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
-luffy_api #源代码路径
第二步:在需要使用异步的地方导入celery实例即可使用
-视图函数中使用,导入任务
-任务.delay() # 提交任务
第三步:启动worker,如果有定时任务,启动beat
第四步: 等待任务被worker执行
第五步:在视图函数中,查询任务结果
扩展
除了redis,RabbitMq能做结果存储外,还可以使用Django的orm作为结果存储,当然需要安装依赖插件,这样的好处在于我们可以直接通过Django的数据查看到任务状态,同时为可以定制更多的操作,下面介绍如何使用orm作为结果存储
-
安装
pip install django-celery-results
-
配置settings.py 注册app
INSTALLED_APPS = ( ..., 'django_celery_results', )
-
修改backend配置,将redis改为django-db
#CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis CELERY_RESULT_BACKEND = 'django-db' #使用django orm 作为结果存储
-
修改数据库
python3 manage.py migrate django_celery_results
此时会看到数据库会多创建
django_celery_results_taskresult
-
当然你有时候需要对task表进行操作,以下源码的表结构定义:
class TaskResult(models.Model): """Task result/status.""" task_id = models.CharField(_('task id'), max_length=255, unique=True) task_name = models.CharField(_('task name'), null=True, max_length=255) task_args = models.TextField(_('task arguments'), null=True) task_kwargs = models.TextField(_('task kwargs'), null=True) status = models.CharField(_('state'), max_length=50, default=states.PENDING, choices=TASK_STATE_CHOICES ) content_type = models.CharField(_('content type'), max_length=128) content_encoding = models.CharField(_('content encoding'), max_length=64) result = models.TextField(null=True, default=None, editable=False) date_done = models.DateTimeField(_('done at'), auto_now=True) traceback = models.TextField(_('traceback'), blank=True, null=True) hidden = models.BooleanField(editable=False, default=False, db_index=True) meta = models.TextField(null=True, default=None, editable=False) objects = managers.TaskResultManager() class Meta: """Table information.""" ordering = ['-date_done'] verbose_name = _('task result') verbose_name_plural = _('task results') def as_dict(self): return { 'task_id': self.task_id, 'task_name': self.task_name, 'task_args': self.task_args, 'task_kwargs': self.task_kwargs, 'status': self.status, 'result': self.result, 'date_done': self.date_done, 'traceback': self.traceback, 'meta': self.meta, } def __str__(self): return '<Task: {0.task_id} ({0.status})>'.format(self)
配置定时任务
settings.py中配置
CELERY_BEAT_SCHEDULE = {
# 'check-application': {
# 'task': 'application.tasks.get_api',
# 'schedule': timedelta(days=7),
# },
'check-server-alarm': {
'task': 'application.tasks.service_auto_start',
'schedule': timedelta(minutes=1)
},
'auto_calcu_app_stats': {
'task': 'application.tasks.auto_calcu_app_stats',
'schedule': crontab(hour='0', minute='30'),
},
'auto_calcu_api_stats': {
'task': 'application.tasks.auto_calcu_api_stats',
'schedule': crontab(hour='0', minute='30'),
},
'auto_calcu_server_stats': {
'task': 'application.tasks.auto_calcu_server_stats',
'schedule': crontab(hour='0', minute='30'),
}
}