celery
celery
介绍
是一个python的框架。
主要用来解决 定时任务
异步任务
延迟任务
的框架
官方文档
https://docs.celeryq.dev/en/stable/
运行原理
1 可以不依赖任何服务器,通过自身命令,启动服务
2 celery服务为为其他项目服务提供异步解决任务需求的
注: 会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,ceTery就会在需要时异步完成项目的需求
人(django)是一个独立运行的服务 医院(celery)也是一个独立运行的服务
正常情况下,人可以完成所有健康情况的动作,不需要医院的参与:但当人生病时,就会被医院接收,解决人生病问题
人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
celery架构
任务中间件 Broker
, 其他服务提交的异步任务,放在中间件里面排队。
任务中间件是需要借助于第三方redis
任务执行单元 worker
执行异步任务的进程
celery 提供的
结果存储 backend
在项目中就是函数的返回结果,存到backend中
也需要借助第三方 redis, mysql
安装
pip install celery
windos启动worker需要下载pip install eventlet 这个来支持
快速使用
第一步
新建main,py设置中间件与执行结果库,写任务
from celery import Celery
"""
参数
中间件 broker
执行结果 backend
"""
# 中间件 用本地的redis 1库,提交的任务放在里面
broker ='redis://127.0.0.1:6379/1'
# 执行结果 用本地的redis 2库,执行完的结果放在里面
backend ='redis://127.0.0.1:6379/2'
# 实例化得到对象,并起个名字可以__name__获得这个文件的名字
app = Celery('test',broker=broker,backend=backend)
# 写任务 需要使用装饰器的形式
@app.task
def add(a,b):
import time
time.sleep(3)
return a+b
提交任务 s1.py
# 导入任务提交使用
from main import add
# 同步调用
# res = add(5,6)
# print(res)
# 异步调用
res = add.delay(2,3)
print(res) # 4ebd35bc-4dc4-4c96-9af7-81649ce607f7 返回了该任务的id值可以根据这个拿到任务结果
"这个时候任务还没有执行,worker没启动,他才是真正干活的人"
启动worker
windos启动worker需要下载pip install eventlet 这个来支持
# 启动worker命令
"""需要切换到任务所在目录下
main 任务文件名
"""
4.x之前版本
celery worker -A main -l info -P eventlet
4.x之后
celery -A main worker -l info -P eventlet
mac
celery -A main worker -l info
"""敲完命令会一直运行,只要提交任务就会自动执行"""
获取执行结果s2.py
# 把main的里面实例对象app导进来
from main import app
# 导入异步结果模块
from celery.result import AsyncResult
# 放入提交任务得到的id
id='4ebd35bc-4dc4-4c96-9af7-81649ce607f7'
if __name__=='__main__':
# 实例化得到对象,传id与执行app
a=AsyncResult(id=id,app=app)
# 判断是否成功
if a.successful():
# 拿到结果
result = a.get()
print(result)
# 执行失败
elif a.failed():
print('任务失败')
# 执行等待
elif a.status == 'PENDING':
print('任务等待着被执行')
elif a.status == 'RETRY':
print('任务异常正在重试')
elif a.status == 'STARTED':
print('任务已经开始被执行')
celery包的使用
celery_task # celery包
__init__.py # 包文件
celery.py # celery连接和配置相关文件,且名字必须交celery.py
tasks.py # 所有任务函数
add_task.py # 添加任务
get_result.py # 获取结果
第一步
新建一个celery_task包
第二步
在包的下面新建celery.py存放
from celery import Celery
# 中间件 用本地的redis 1库,提交的任务放在里面
broker ='redis://127.0.0.1:6379/1'
# 执行结果 用本地的redis 2库,执行完的结果放在里面
backend ='redis://127.0.0.1:6379/2'
# 实例化得到对象,并起个名字可以__name__获得这个文件的名字 include 指定管理的任务
app = Celery('test',broker=broker,backend=backend,include=['celery_task.order_task','celery_task.user_task'])
第三步
在celery_task下新建user_task.py存放用户任务
# 编写任务
from .celery import app
import time
@app.task
def send_sms(phone,code):
print("给%s发送短信成功,验证码为:%s" % (phone, code))
time.sleep(2)
return True
在celery_task下新建order_task.py 存放订单任务
from .celery import app
import time
@app.task
def add(a, b):
print('-----', a + b)
time.sleep(2)
return a + b
第四步
提交任务和包没关系在外面创建
send_sms_task.py
from celery_task.user_task import send_sms
# 同步调用
# res = send_sms('18888888',8888)
# 异步调用
res = send_sms.delay('18888888',8888)
print(res) #f6e913b7-da1a-462c-b5f4-6a8211d1d565
第五步
启动worker
到包的同级目录下执行命令
celery -A celery_task worker -l info -P eventlet
第六步
获取结果
包的同级目录下新建get_result.py
# 把main的里面实例对象app导进来
from celery_task.celery import app
# 导入异步结果模块
from celery.result import AsyncResult
# 放入提交任务得到的id
id='f6e913b7-da1a-462c-b5f4-6a8211d1d565'
if __name__=='__main__':
# 实例化得到对象,传id与执行app
a=AsyncResult(id=id,app=app)
# 判断是否成功
if a.successful():
# 拿到结果
result = a.get()
print(result)
# 执行失败
elif a.failed():
print('任务失败')
# 执行等待
elif a.status == 'PENDING':
print('任务等待着被执行')
elif a.status == 'RETRY':
print('任务异常正在重试')
elif a.status == 'STARTED':
print('任务已经开始被执行')
celery延迟任务
user_task
# 编写任务
from .celery import app
import time
@app.task
def send_sms(phone,code):
print("给%s发送短信成功,验证码为:%s" % (phone, code))
time.sleep(2)
return True
celery
from celery import Celery
# 中间件 用本地的redis 1库,提交的任务放在里面
broker ='redis://127.0.0.1:6379/1'
# 执行结果 用本地的redis 2库,执行完的结果放在里面
backend ='redis://127.0.0.1:6379/2'
# 实例化得到对象,并起个名字可以__name__获得这个文件的名字 include 指定管理的任务
app = Celery('test',broker=broker,backend=backend,include=['celery_task.order_task','celery_task.user_task'])
提交任务py文件
from celery_task.user_task import send_sms
# 传入时间对象
from datetime import datetime,timedelta
"""
datetime.utcnow() 拿到当前utc时间
datetime.now() 拿到当前时间
timedelta(seconds=10) 时间对象只能和这个相加
参数: seconds 秒
minutes 分
days 天
weeks 周
"""
eta = datetime.utcnow() + timedelta(seconds=20)
# 需要传入时间对象 eta=eta 延迟20秒后执行
res=send_sms.apply_async(args=['18088888888',8888],eta=eta)
print(res)
celery定时任务
需要写在celery.py里
from celery import Celery
# 中间件 用本地的redis 1库,提交的任务放在里面
broker ='redis://127.0.0.1:6379/1'
# 执行结果 用本地的redis 2库,执行完的结果放在里面
backend ='redis://127.0.0.1:6379/2'
# 实例化得到对象,并起个名字可以__name__获得这个文件的名字 include 指定管理的任务
app = Celery('test',broker=broker,backend=backend,include=['celery_task.order_task','celery_task.user_task'])
from datetime import timedelta
from celery.schedules import crontab
# 定时任务
# celery的配置文件#####
# 第一步:在celery的py文件中写入
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False
# 任务的定时配置
app.conf.beat_schedule = {
'send_sms': {
# 设置需要执行的任务
'task': 'celery_task.user_task.send_sms',
'schedule': timedelta(seconds=3), # 时间对象做定时任务
# crontab 也能做定时任务
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
# 'schedule': crontab(hour=9, minute=43), # 每天9点43
'args': ('18888888', '6666'),
},
}
之前我们启动任务是手动写一个提交任务的py文件运行提交,但是定时任务没有。
启动定时任务需要启动worker与beat
beat就是一个定时提交的进程,定时任务就是配置在app.conf.beat_schedule的任务
执行beat命令
celery -A celery_task beat -l info
执行worker
再开一个终端
celery -A celery_task worker -l info -P eventlet
celery_task 是celery包的名字
celery异步任务
任务.delay(参数)
注意点:
1 启动命令的执行位置,如果是包结构,一定在包这一层 就是包的同级目录不要进入包里面了
2 include=['celery_task.order_task'],路径从包名下开始导入,因为我们在包这层执行的命令,如果你把celery_task加到环境变量里也可以直接写order_task
如果在公司中,只是做定时任务,还有一个更简单的框架
APSchedule
相关文档
官方文档:https://apscheduler.readthedocs.io/en/master/userguide.html
https://blog.csdn.net/qq_41341757/article/details/118759836
django中使用celery
把我们写的包复制到项目根路径下
-luffy_api
-celery_task #celery的包路径
celery.py
"""想要在celery包里使用django的ORM操作一定不要忘记在celery里添加代码
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
"""
-luffy_api #源代码路径
在需要使用提交异步任务的位置eg:视图函数中 导入任务
任务.delay() # 提交异步任务
启动worker 如果有定时任务就再启动beat
等待任务被worker执行
在视图函数里查询任务执行结果
用celery 在django中写一个秒杀功能
逻辑分析
1 前端秒杀按钮,用户点击,发送ajax请求到后端
2 后端视图函数提交秒杀任务借助于celery,提交到中间件中了
3 当次秒杀的请求返回去,携带者任务celery的id号在给前端
4 前端开启定时任务,每隔3s钟,带着任务id,向后端发送请求,查看是否秒杀成功
# 后端的情况
1 任务还在等待被执行,返回给前端,前端继续每隔3s发送一次请求
2 任务执行完了,秒杀成功了,返回给前端,恭喜您秒杀成功,关闭前端定时器
3 任务执行完了,秒杀失败了,返回给前端,秒杀失败,关闭前端定时器
写一个秒杀接口
from celery_task.order_task import sckill_task
from rest_framework.viewsets import ViewSet # 自动生成路由
class sckill(ViewSet):
@action(methods=['GET'],detail=False)
def sckill(self,request,*args,**kwargs):
# 拿到前端带过来的商品id
goods_id = request.query_params.get('id')
# 异步提交任务并把商品id传过去在任务内开始秒杀生成订单,扣除库存,返回是否秒杀成功
res=sckill_task.delay(goods_id)
return APIResponse(task_id=res.id)
写一个秒杀任务
from .celery import app
# 写一个秒杀任务
import time
import random
@app.task
def sckill_task(goods_id):
# 生成订单,减库存,放在一个事务中,这里写伪代码
print('开始秒杀,生成购物订单')
time.sleep(random.randint(0,6))
print('秒杀成功,扣除库存了')
return random.choice([True,False])
写一个秒杀按钮前端
<template>
<div>
<button @click="handlesckill">秒杀</button>
</div>
</template>
<script>
export default {
name: "sckill",
data(){
return{
task_id:'',
t:null,
}
},
methods:{
handlesckill(){
this.$axios.get(this.$settings.BASE_URL + '/user/sckill/sckill/?id=12').then(res=>{
// 接收后端返回的 任务id
this.task_id = res.data.task_id
// 开启定时任务每隔2秒向后端发送请求查看秒杀结果
this.t = setInterval(()=>{
this.$axios.get(this.$settings.BASE_URL + '/user/sckill/get_result/?task_id='+this.task_id).then(res=>{
// 判断任务是否没结束 301就是正在秒杀
if (res.data.code==301){
this.$message(res.data.msg)
}else{
// 如果结束了就中止定时任务
clearInterval(this.t)
this.t = null
this.$message(res.data.msg)
}
}
)
},2000)
}).catch(res=>{
})
}
}
}
</script>
<style scoped>
</style>
"记得去router里设置路由"
需要查看结果接口写一个
封装查看结果代码
get_result.py
# 把main的里面实例对象app导进来
from celery_task.celery import app
# 导入异步结果模块
from celery.result import AsyncResult
from utils.common_response import APIResponse
def get_result_task(appid,app=app):
# 实例化得到对象,传id与执行app
a=AsyncResult(id=appid,app=app)
# 判断是否成功
if a.successful():
# 拿到结果
result = a.get()
print(result)
if result[0]:
return APIResponse(msg='秒杀成功')
else:
return APIResponse(code =101,msg='秒杀失败')
# 执行等待
elif a.status == 'PENDING':
return APIResponse(code=301,msg='还在秒杀中')
查看结果接口
from rest_framework.viewsets import ViewSet
# 导入查询结果
from celery_task.get_result import get_result_task
class SckillView(ViewSet):
# 查看结果接口
@action(methods=['GET'],detail=False)
def get_result(self,request):
task_id = request.query_params.get('task_id')
return get_result_task(task_id)
注意事项:
在提交任务的时候,想要在celery的任务里查询表一定要在celery里添加
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')