文章目录
一、Celery 介绍
1. 介绍:
-
Celery 是一个简单、灵活且可靠的,处理大量消息的分布式系统,并且提供维护这样一个系统的必需工具。
-
它是一个专注于实时处理的任务队列,同时也支持任务调度。
-
Celery 有广泛、多样的用户与贡献者社区,你可以通过 IRC 或是 邮件列表 加入我们。
-
Celery 是开源的,使用 BSD 许可证 授权。
Celery 官网:http://www.celeryproject.org/
Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html
Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/
2. 注意:
Celery is a project with minimal funding, so we don’t support Microsoft Windows. Please don’t open any issues related to that platform.
3. celery 可以做的事:
- 异步执行:解决耗时任务,将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等
- 延迟执行:解决延迟任务
- 定时执行:解决周期(周期)任务,比如每天数据统计
4. celery的概念: celery是独立的服务,跟其它框架无关,需要单独启动
- 可以不依赖任何服务器,通过自身命令,启动服务
- celery服务为为其他项目服务提供异步解决任务需求的
- 注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
5. celery的架构:
Celery的架构由三部分组成,消息中间件(message broker)、任务执行单元(worker)和 任务执行结果存储(task result store)组成。
-
消息中间件
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等 -
任务执行单元
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。 -
任务结果存储
Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等
二、Celery 的安装配置
安装语句:
pip install celery
pip3 install eventlet
2.1 基础使用
1、第一步:实例化得到app对象,注册任务
创建 main.py
from celery import Celery
backend = 'redis://127.0.0.1:6379/1' # 结果存储
broker = 'redis://127.0.0.1:6379/2' # 消息中间件
app = Celery('main', broker=broker, backend=backend)
# 任务变成了celery的任务了
@app.task
def add(a, b):
import time
time.sleep(2)
return a + b
Celery 的第一个参数是当前模块的名称,这个参数是必须的,这样的话名称可以自动生成。
2. 第三方来提交任务
from main import add
res=add.delay(4,5) # 把任务提交到消息中间件中
3. 使用 celery 来执行任务:启动 worker
linux、mac
系统输入: celery -A main worker -l info
window
上装 eventlet
后输入: celery -A main worker -l info -P eventlet
注意点:main 是模块名,需要自己替换
4. 查看任务执行结果,手动去 redis 中看
2.2 Celery 包结构
使用 Celery 包结构用的较多,写好包,以后copy到任意项目中,都可以顺利使用
包结构:
project
├── celery_task # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery连接和配置相关文件,且名字必须叫celery.py
│ └── tasks.py # 所有任务函数
├── add_task.py # 包外其他项目,用于添加任务
└── get_result.py # 获取结果
- celery.py 文件
from celery import Celery
# 消息中间件
broker = 'redis://127.0.0.1:6379/1'
# 结果储存
backend = 'redis://127.0.0.1:6379/2'
# 创建 app
app = Celery(
# main 名字无所谓
main='celery',
broker=broker,
backend=backend,
# 任务函数所在的文件需要在 include 注册,注意文件路径必须要正确
include=['celery_task.user_task', ]
)
- user_task.py 文件,存放 user 相关的任务函数
# 导入 app,需要使用相对导入,不带入项目的内容
from .celery import app
import time
# 创建任务函数
@app.task
def task_a(a, b):
# 休眠10秒模拟函数运行时间
time.sleep(10)
return a + b
- test.py 文件,我在 Django 的测试文件中运行。右键运行会提交任务,提交后会在 redis 中生成任务,如下图所示
# 导入任务函数
from celery_task.user_task import task_a
# 运行提交任务函数
res = task_a.delay(1, 2)
# 返回的值是 id 号
print(res)
- 有任务之后,运行 worker 可以取出任务并执行(没有先后顺序,可以先启动 worker 等待任务提交)
celery -A celery_task worker -l info -P eventlet
注意:celery_task 是包名,导入的是包的名字
启动后 worker 会取出任务并执行,在 redis 中任务键会被销毁,运行截图、销毁后截图和运行结果截图如下所示
- get_result.py 文件,该文件在 celery_task 包外,可以获取当前执行状态,需要手动执行,但是实际使用肯定不能是手动执行
from celery_task.celery import app
from celery.result import AsyncResult
# 任务 id 号
id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
if __name__ == '__main__':
asy = AsyncResult(id=id, app=app)
if asy.successful():
result = asy.get()
print(result)
elif asy.failed():
print('任务失败')
elif asy.status == 'PENDING':
print('任务等待中被执行')
elif asy.status == 'RETRY':
print('任务异常后正在重试')
elif asy.status == 'STARTED':
print('任务已经开始被执行')
2.3 秒杀逻辑
思路:
秒杀肯定是异步提交的,如果是同步的用户提交后程序原地等待任务执行,那么就失去了秒杀的意义。用户提交后直接程序不等待直接返回,并且在前端让用户等待任务的结果,期间前端设置一个定时器,不停的去后台校验秒杀是否成功,如果成功,前端关闭定时器,显示秒杀成功
- 前端(Vue)
<template>
<div>
<button @click="handleClick" style="margin-left: 500px;">点我秒杀</button>
</div>
</template>
<script>
export default {
name: "Seckill",
data() {
return {
# 用于存放任务函数id
task_id: ''
}
},
methods: {
handleClick() {
// 向后台发送秒杀请求,并携带数据
this.$axios.post('http://127.0.0.1:8000/home/test/', {
info: '真帅'
}).then(res => {
// 将返回的任务函数id保存在data中
this.task_id = res.data.task_id
// 前端提示信息
this.$message({
message: res.data.msg,
type: 'warning'
});
let taskId = res.data.task_id
// 创建定时器,每隔3秒向后端发送请求
let t = setInterval(() => {
// 查看任务执行状态
this.$axios.get('http://127.0.0.1:8000/home/test/?task_id=' + taskId).then(res => {
// 前端提示信息
this.$message({
message: res.data.msg,
type: 'warning'
});
// 判断返回的状态码,用于停止计时器
if (res.data.code === 100){
clearInterval(t)
}
}
)
}, 3000)
})
}
}
}
</script>
<style scoped>
</style>
- 后端(Django)
from rest_framework.views import APIView
from utlis.Response import APIResponse
from celery_task.user_task import seckill
class TestView(APIView):
def post(self, request):
info = request.data.get('info')
res = seckill.delay('xwx', info) # 提交异步任务
return APIResponse(msg='您正在排队', task_id=str(res))
def get(self, resquest):
# 获取前端返回的任务函数id
task_id = resquest.GET.get('task_id')
# 查看当前执行状态
from celery_task.celery import app
from celery.result import AsyncResult
asy = AsyncResult(id=task_id, app=app)
if asy.successful():
result = asy.get()
return APIResponse(msg='秒杀成功', code=100)
elif asy.failed():
return APIResponse(msg='秒杀失败', code=100)
else:
return APIResponse(msg='请继续等待', code=101)
- 除了上面的代码,还需要配置路由,这里就不放了。还有需要启动 worker
celery -A celery_task worker -l info -P eventlet
结果如下示意,实际上前端可以使用转圈等动画
三、Celery 异步任务,延迟任务,定时任务
3.1 Celery 异步任务
异步任务在前面案例已有演示,即 任务.dealy(参数)
3.2 Celery 延迟任务
延迟任务顾名思义就是延迟执行任务,延迟的方式如下有俩种
- 方式一,传入 eta 时间对象
使用该方法需要注意的是,不要用了 UTC 时间
...
# eta 传时间对象,到这个时间,再执行
from datetime import datetime, timedelta
# 获取时间对象,为当前时间间隔10秒
eta = datetime.utcnow() + timedelta(seconds=10)
# args 为函数的参数,传入 eta 发起延时任务
res=send_sms.apply_async(args=('111', '222'), eta=eta)
...
- 方式二,配置 countdown 参数
该参数设置延迟的时间,单位为秒
res = send_sms.apply_async(args=('111', '222'), countdown=5) # 延迟任务
- apply_async 其余参数
expires : 设置超时时间.
test_task.apply_async((2,3), expires=60)
retry : 定时如果任务失败后, 是否重试.
test_task.apply_async((2,3), retry=False)
retry_policy : 重试策略.
max_retries : 最大重试次数, 默认为 3 次.
interval_start : 重试等待的时间间隔秒数, 默认为 0 , 表示直接重试不等待.
interval_step : 每次重试让重试间隔增加的秒数, 可以是数字或浮点数, 默认为 0.2
interval_max : 重试间隔最大的秒数, 即 通过 interval_step 增大到多少秒之后, 就不在增加了, 可以是数字或者浮点数, 默认为 0.2 .
3.3 Celery 定时任务
前面的案例都是需要手动点击提交任务,还可以使用 beat 设置自动提交任务。
定时任务需要编写配置,如下所示:
- 定时配置
from celery import Celery
# ------- 定时任务相关 ----------
from datetime import timedelta
from celery.schedules import crontab
# -----------------------------
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery(
main='celery',
broker=broker,
backend=backend,
include=['celery_task.user_task', ]
)
# ---------- 定时配置 -------------------
app.conf.beat_schedule = {
# seckill 是任务名,beat 执行的时候显示
'seckill': {
# task 指向任务函数,路径千万别错
'task': 'celery_task.user_task.seckill',
# schedule 用于设置间隔时间,这里是每隔 3 秒提交一个任务
'schedule': timedelta(seconds=3),
# 还有可以使用 crontab 来指定某一个时间点提交任务
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
# args 放置任务的参数,没有可以为空
'args': ('xwx', '666'),
}
}
# --------------------------------------
-
命令
配置完后需要启动 worker 和 beat,beat 用于自动提交任务
启动 worker :celery -A celery_task worker -l info -P eventlet
启动 beat:celery -A celery_task beat -l info
-
展示
启动后的截图如下所示: