Celery基本使用及其在django项目中的应用
Celery简介
官方文档
Celery 官网:http://www.celeryproject.org/
Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html
Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/
celery是异步任务框架,执行异步任务,执行延迟任务,执行定时任务
# 注意:celery在windows上有bug,若bug无法解决时推荐在linux上使用celery
使用场景
异步执行:解决耗时任务,将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
延迟执行:解决延迟任务
定时执行:解决周期(周期)任务,比如每天数据统计
Celery异步任务框架
- 可以不依赖任何服务器,通过自身命令,启动服务(内部支持socket)
- celery服务为为其他项目服务提供异步解决任务需求的
- 会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
Celery架构
Celery的架构由三部分组成:消息中间件(Broker)、任务执行单元(Worker)和 任务执行结果存储(Backend)组成。
#### 消息中间件BrokeR
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等
#### 任务执行单元Worker
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。
#### 任务结果存储Backend
Backend用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
Celery使用方式
下载安装celery
模块
pip install celery
celery有两种使用方式:第一种方式是在一个py文件中使用;第二种是在一个包结构(包管理,【推荐】)
第一种使用方式:py文件
第一步:任务和worker放在一个py文件中,然后通过命令行的方式启动worker
# celery_task.py
from celery import Celery
# 配置broker和 backend,全部使用redis数据库
broker = 'redis://127.0.0.1:6379/1' # broker任务队列位置
backend = 'redis://127.0.0.1:6379/2' # 存储位置,执行完的结果存在这
# 实例化一个应用对象app
app = Celery(__name__, broker=broker, backend=backend)
@app.task # 使用app.task这个装饰器添加my_add这个任务
def my_add(x, y): # 具体需要被执行的任务
print(x, y)
return x + y
"""
用命令来执行, 启动worker
# linux/mac
celery worker -A celery_task -l info
# windows:
windows上如果通过上述命令启动失败或者后期执行任务失败则借助eventlet模块启动
pip3 install eventlet
celery worker -A celery_task -l info -P eventlet
"""
第二步:提交任务(执行该py文件)
# run_task.py
# 导入被执行的任务
from celery_task import my_add
# 异步调用,将该任务提交到broker中,并返回一个任务编号(uuid格式)
ret = my_add.delay(5, 4)
# my_add(3,4) # 这样的操作是直接执行my_add函数,不是异步调用,不会被添加到broker中
第三步:查看任务执行的结果
# get_results.py
from celery.result import AsyncResult
from celery_task import app
# 通过任务编号查找任务结果
id = 'a43aa718-e617-4ec4-8afe-220a8df0d4e7'
if __name__ == '__main__':
async = AsyncResult(id=id, app=app)
if async.successful():
result = async.get() # 拿到数据结果
print(result)
elif async.failed():
print('任务失败')
elif async.status == 'PENDING':
print('任务等待中被执行')
elif async.status == 'RETRY':
print('任务异常后正在重试')
elif async.status == 'STARTED':
print('任务已经开始被执行')
第二种使用方式:包管理结构
提倡使用用包管理的方式,好处是结构更清晰,将worker和具体的任务分开放在不同的py文件内。
包目录结构
project
├── celery_task # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery连接和配置相关文件,且名字必须叫celery.py
│ └── tasks.py # 所有任务函数
├── run_celery_task.py # 提交任务
└── get_result.py # 获取结果
第一步:新建一个包,包名任意,如:celery_task,在包内新建一个名字为celery.py
的文件,在该文件内书写worker
# celery_task/celery.py
from celery import Celery
# 配置broker和 backend,全部使用redis
broker = 'redis://127.0.0.1:6379/1' # broker任务队列位置
backend = 'redis://127.0.0.1:6379/2' # 存储位置,执行完的结果存在这
# 实例化app对象,通过include这个列表反射出这个包下面需要执行的任务文件(即具体的任务书写在task1这个文件中)
app = Celery(
__name__,
broker=broker,
backend=backend,
include=['celery_task.task1', 'celery_task.task2']
)
# 注意:该文件的名字必须是 celery.py
第二步:在包内的其他py文件内书写具体的任务
# task1.py
from .celery import app # 从包内的celery.py文件中导入app
@app.task
def my_add(x, y):
print(x, y)
return x + y
# task2.py
from .celery import app # 从包内的celery.py文件中导入app
@app.task
def my_multi(x, y):
print(x, y)
return x * y
第三步:启动worker,等待任务被提交后执行任务
# 通过命令行的方式启动:切换到celery_task包所在的路径下,执行如下命令
# linux/mac
celery worker -A celery_task -l info # celery_task是包名
# windows:
windows上如果通过上述命令启动失败或者后期执行任务失败则借助eventlet模块启动
pip3 install eventlet
celery worker -A celery_task -l info -P eventlet
第四步:提交任务:异步任务,延迟任务(手动提交,右键执行py文件)
在需要提交任务的位置,引入celery_task包内具体的任务文件,导入具体的任务函数,然后异步执行该函数。
# run_celery_task.py
# 执行任务
# 从包内导入具体的任务函数
from celery_task.task1 import my_add
from celery_task.task2 import my_multi
# 提交异步任务,delay()
ret1 = my_add.delay(6, 7)
ret2 = my_multi.delay(1, 9)
print(ret1)
print(ret2)
# 提交延迟任务, apply_async
from datetime import datetime, timedelta
# 需要utc时间
eta = datetime.utcnow() + timedelta(seconds=10)
# 任务参数通过args传递,eta是执行任务的utc时间
ret = my_add.apply_async(args=(240, 50), eta=eta)
print(ret)
提交定时任务的设置:定时任务在worker启动时就应该被设置好,即将定时任务的设置写在celety.py
文件内【自动提交任务】
# celery_task/celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery(
__name__,
broker=broker,
backend=backend,
include=['celery_task.task1', 'celery_task.task2']
)
# 执行定时任务
# 选择本地时区,并不使用UTC
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False
# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'add-task': {
'task': 'celery_task.task1.my_add',
'schedule': timedelta(seconds=3), # 每3秒执行一次
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (300, 150), # 该定时任务需要的参数
}
}
执行定时任务的命令:
# 启动worker
celery worker -A celery_task -l info -P eventlet
# 启动beat 【开启定时器】
celery beat -A celery_task -l info # 此时,windows上不用需要借助eventlet模块
第五步:获取任务结果,获取任务结果同第一种使用方式一致,通过任务id查看任务状态及任务结果
django中使用celery
#### 重点:由于采用了django的反射机制,使用celery.py所在的celery_task包必须放置在django项目的根目录下
"""
celery框架django项目工作流程
1)加载django配置环境
2)创建Celery框架对象app,配置broker和backend,得到的app就是worker
3)给worker对应的app添加可处理的任务函数,用include配置给worker的app
4)完成提供的任务的定时配置app.conf.beat_schedule
5)启动celery服务,运行worker,执行任务
6)启动beat服务,运行beat,添加任务
"""
首页轮播图:定时更新缓存
设置celery工作包内的celery.py
文件以及任务文件tasks.py
# ############################################################### celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/0'
backend = 'redis://127.0.0.1:6379/1'
app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
# 定时任务设置
# 本地时区, 不使用UTC
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False
# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'update-banner-list': {
'task': 'celery_task.tasks.update_banner_list',
'schedule': timedelta(seconds=5), # 测试:5s更新一次缓存,即5s调用一次定时的任务
# 'args': (), # update-banner-list()函数不需要参数
}
}
# ############################################################### tasks.py
from django.core.cache import cache
# 加载 配置django环境,这样才可以在本文件内调用django项目中的文件以及模型等
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Luffyapi.settings.dev")
import django
django.setup()
@app.task
def update_banner_list(): # 任务:更新缓存内的轮播图
# 导入需要的模块文件
from home import models, ser
from .celery import app
queryset = models.Banner.objects.filter(is_delete=False, is_show=True).order_by('-orders')[:3]
banner_list = ser.BannerSerializer(queryset, many=True).data
# 拿不到request对象,所以头像的连接base_url要自己组装
for banner in banner_list:
banner['image'] = 'http://127.0.0.1:8000%s' % banner['image']
cache.set('banner_list', banner_list, 30)
return True
轮播图接口视图函数从缓存中取数据
# views.py
def list(self, request, *args, **kwargs):
# 1 先去缓存拿数据
banner_list=cache.get('banner_list')
if not banner_list:
# 缓存中没有,再去数据库拿;有缓存直接返回
response = super().list(request, *args, **kwargs)
# 同步到缓存
cache.set('banner_list', response.data, 60*60*24) # 加一天的过期时间
return response
return Response(data=banner_list)