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')
posted @ 2023-03-08 20:59  李阿鸡  阅读(12)  评论(0编辑  收藏  举报
Title