Celery的介绍与使用

一、Celery介绍

1、Celery是什么?

Celery翻译过来是芹菜【不要问我跟芹菜有什么关系,问就是你直接去问作者】

框架:

    服务,python的框架,跟django无关

能用来做什么?

    <1>、异步任务

    <2>、定时任务

    <3>、延时任务

理借Celery的运行原理

    <1>、可以不依赖任何服务器,通过自身命令,启动服务

    <2>、celery服务为为其他项目服务提供异步解决任务需求的

        注:会有两个服务同时运行,一个是项目服务,一个是celery服务,项目将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求

人是一个独立运行的服务 | 医院也是一个独立运行的服务
	正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
	人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求

2、Celery架构(Broker,backend都用redis)

<1>、任务中间件Broker(中间件),其他服务提交的异步任务,放在里面排队

    需要借助于第三方redis、rabbitmq

<2>、任务执行单元worker,真正执行异步任务的进程

    celery提供的

<3>、结果存储,backend,结果存储,函数的返回结果,存到backend中

    需要借助于第三方:redis,mysql

3、使用场景

异步执行:解决耗时任务
延迟执行:解决延迟任务
定时执行:解决周期任务

二、Celery安装与快速使用

celery不支持win,通过eventlet支持在win上运行

安装:

其他操作系统:pip install celery
在windows上安装:pip install eventlet

快速使用:

  • 第一步:

    新建main.py

    from celery import Celery
    # 提交的异步任务,放在里面
    broker = 'redis://127.0.0.1:6379/1'
    # 执行完的结果,放在这里
    backend = 'redis://127.0.0.1:6379/2'
    app = Celery('test', broker=broker,  backend=backend)
    
    @app.task
    def add(a, b):
        import time
        time.sleep(3)
        print('-----', a + b)
        return a + b
    
  • 第二步:

    其他程序,提交任务

    res = add.delay(5, 6)  # 原来add的参数,直接放在delay中传入即可
    print(res)  # f150d8a5-c955-478d-9343-f3b60d0d5bdb
    
  • 第三步:

    启动worker命令,win需要安装eventlet

    -A后面跟的是celery对象所在的文件,-P是指定我们需要使用eventlet,-l后面的info是用于指定打印信息的级别(类似异常处理的提示)

    win:
        4.x之前版本
        	celery worker -A main -l info -P eventlet
        4.x之后
        	celery -A main worker -l info -P eventlet
    mac:
    
  • 第四步:

    worker会执行消息中间件中的任务,把结果存到我们指定的redis的第二个库中

    只要我们不主动关闭,celery worker就会一直运行

  • 第五步:

    查看执行结果,拿到执行的结果

    img

    上图是我们通过图形化界面看到的结果,正常来说我们需要用代码来获得运行的结果

    这里我们创建一个新的py文件来获取结果,并且需要用到之前获取到的id来查询结果

    get_result.py

    from main import app
    
    from celery.result import AsyncResult
    
    id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
    if __name__ == '__main__':
        ans = AsyncResult(id=id, app=app)
        if ans.successful():
            result = ans.get()
            print(result)
        elif ans.failed():
            print('任务失败')
        elif ans.status == 'PENDING':
            print('任务等待中被执行')
        elif ans.status == 'RETRY':
            print('任务异常后正在重试')
        elif ans.status == 'STARTED':
            print('任务已经开始被执行')
    

    ps:将来我们在任意位置需要查看结果的时候,导入上述代码使用即可

三、celery包结构

包结构目录

project
    ├── celery_task  	# celery包
    │   ├── __init__.py # 包文件
    │   ├── celery.py   # celery连接和配置相关文件,且名字必须交celery.py
    │   └── tasks.py    # 所有任务函数
    ├── add_task.py  	# 添加任务
    └── get_result.py   # 获取结果
  • 步骤一

    创建包celery_task,并且报下必须有一个叫celery.py的py文件

    celery.py

    from celery import Celery
    
    # 提交的异步任务,放在里面
    broker = 'redis://127.0.0.1:6379/1'
    # 执行完的结果,放在这里
    backend = 'redis://127.0.0.1:6379/2'
    # 不要忘了include
    '上面两个配置是设置存储数据的redis库位置'
    '下面的app中多了一格include属性,他相当于是注册需要执行的函数'
    app = Celery('test', broker=broker, backend=backend, include=['celery_task.order_task', 'celery_task.user_task'])
    
  • 步骤二

    接着我们我们在保内编写task文件,编写任务函数,我们可以根据用途分成不同的task文件

    order_task

    from .celery import app
    import time
    
    @app.task
    def add(a, b):
        print('-----', a + b)
        time.sleep(2)
        return a + b
    

    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
    
  • 步骤三

    提交任务到中间件,等待worker执行

    handup.py

    from celery_task.user_task import send_sms
    from celery_task.order_task import add
    
    '异步调用'
    res = send_sms.delay(199999999, '88888')
    print(res)
    
    # res = add.delay(19, 3)
    # print(res)
    
  • 步骤四

    包所在目录下,启动worker

    celery  -A celery_task  worker -l info -P eventlet
    

    img

  • 步骤五

    worker执行完,结果会被存到backend中

    img

  • 步骤六

    使用代码查看结果

    依旧是使用跟上面一样的代码,导入的东西需要改一下

    get_result.py

    from celery_task.celery import app
    
    from celery.result import AsyncResult
    
    id = '9a2070f7-34d0-421e-836e-f6c664967e3f'
    if __name__ == '__main__':
        ans = AsyncResult(id=id, app=app)
        if ans.successful():
            result = ans.get()
            print(result)
        elif ans.failed():
            print('任务失败')
        elif ans.status == 'PENDING':
            print('任务等待中被执行')
        elif ans.status == 'RETRY':
            print('任务异常后正在重试')
        elif ans.status == 'STARTED':
            print('任务已经开始被执行')
    

四、celery 执行异步任务,延迟任务,定时任务

异步任务

任务函数的后面点delay就是异步执行任务

任务.delay(参数)

延迟任务

任务.apply_async(args=[参数],eta=时间对象) 

这里的eta是一个时间对象,需要用datetime模块创建

### 延迟任务
# 需要传入时间对象
from datetime import datetime, timedelta

# 拿到utc时间   datetime.utcnow()
# print(type(datetime.now()))
# print(datetime.now()-timedelta(days=3))
# print(datetime.utcnow())
# print(type(timedelta(days=10)))

# 1 分钟之后的时间
eta = datetime.utcnow() + timedelta(seconds=20)
# 立即异步执行
res = send_sms.delay('1923333', '8888')  
print(res)
# 1分钟后执行这个任务
res = send_sms.apply_async(args=['18922345353', '8888'], eta=eta)  # 延迟一分钟执行,通过时间对象来控制
'这样设置之后,在我们执行任务的时候,当时间到达时间对象对应的时间后就会提交任务,没到达对应的时间之前就相当于处于阻塞态'
print(res)
'打印出的res仍然是任务的id'

### 定时任务   每隔多长时间,     每天执行某个任务

datetime.now()与datetime.utcnow()

# datetime.utcnow()
from datetime import datetime, timedelta

eta = datetime.utcnow() + timedelta(seconds=20)
print(eta)
'2023-03-09 07:10:23.437327'


# datetime.now()
from datetime import datetime, timedelta

eta = datetime.now() + timedelta(seconds=20)
print(eta)
'2023-03-09 15:24:51.385495'
  • datetime.utcnow()获取的是当前的格林威治时间

  • datetime.now()获取的是当前地区的时间,需要我们在配置文件中更改配置才能获取到本地时间

    配置文件中的配置项如下:
    LANGUAGE_CODE = 'zh-hans'
    
    TIME_ZONE = 'Asia/Shanghai'
    
    USE_I18N = True
    
    USE_L10N = True
    
    USE_TZ = False
    
  • timedelta(seconds=20)是给他增加一些时间,根据关键参数的值和类型,可以添加不同的时间量,具体名称可以去源码查看

    class timedelta(SupportsAbs[timedelta]):
        min: ClassVar[timedelta]
        max: ClassVar[timedelta]
        resolution: ClassVar[timedelta]
    
        if sys.version_info >= (3, 6):
            def __init__(
                self,
                days: float = ...,
                seconds: float = ...,
                microseconds: float = ...,
                milliseconds: float = ...,
                minutes: float = ...,
                hours: float = ...,
                weeks: float = ...,
                *,
                fold: int = ...,
            ) -> None: ...
        else:
            def __init__(
                self,
                days: float = ...,
                seconds: float = ...,
                microseconds: float = ...,
                milliseconds: float = ...,
                minutes: float = ...,
                hours: float = ...,
                weeks: float = ...,
            ) -> None: ...
    

定时任务

定时任务需要两个进程才能实现,其中的beat进程是定时提交任务的,worder是执行任务的

-需要启动beat和启动worker
	-beat    定时提交任务的进程---》配置在app.conf.beat_schedule的任务
	-worker  执行任务的
  • 步骤一

    使用定时任务需要在celery的py文件中写入下列配置,这样beat就会根据配置去提交任务

    '定时任务'
    from celery.schedules import crontab
    from datetime import datetime, timedelta
    'celery的配置文件'
    '时区设置,这里配置成上海'
    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),
            # 存放时间对象的话,就表示每隔这样一段时间,执行一次任务
            # 'schedule': crontab(hour=8, day_of_week=1),  
            # 每周一早八点执行任务
            'schedule': crontab(hour=9, minute=43),
            # 每天的9点43分执行任务
            'args': ('19999999', '6666')
            # 这里的args是放置参数的,有参数就写,没参数就可以不写args
        },
    }
    
  • 步骤二

    启动beat

    celery -A celery_task beat -l info
    
  • 步骤三

    启动worker

    celery -A celery_task worker -l info -P eventlet
    
  • 注意事项

    • 1 启动命令的执行位置,如果是包结构,一定在包这一层
    • 2 include=['celery_task.order_task'],路径从包名下开始导入,因为我们在包这层执行的命令
  • 结果展示

    启动beat

    img

    启动worker

    img

    img

五、django中使用celery

1、定时任务推荐使用的框架(了解)

APSchedule(更简单):https://blog.csdn.net/qq_41341757/article/details/118759836

2、秒杀功能

这里我们使用celery编写一个秒杀功能(还可以把发送短信,封装成用celery异步发送)

  • 秒杀功能逻辑分析

    这里我们只是简单模仿秒杀的功能,相关的数据库和数据,以及一些相关的代码,就简单编写了

    # 秒杀逻辑分析
    	1 前端秒杀按钮,用户点击---》发送ajax请求到后端
        2 视图函数---》提交秒杀任务---》借助于celery,提交到中间件中了
        3 当次秒杀的请求,就回去了,携带者任务id号在前端(在任务完成前,可以用模态框播放正在秒杀的动画,查到结果后秒杀动画关闭)
        4 前端开启定时任务,每隔3s钟,带着任务,向后端发送请求,查看是否秒杀成功
        5 后端的情况
        	1 任务还在等待被执行----》返回给前端,前端继续每隔3s发送一次请求
            2 任务执行完了,秒杀成功了---》返回给前端,恭喜您秒杀成功--》关闭前端定时器
            3 任务执行完了,秒杀失败了---》返回给前端,秒杀失败--》关闭前端定时器
    
  • 视图和路由

    urls.py

    from rest_framework.routers import SimpleRouter
    from . import views
    
    router = SimpleRouter()
    # 访问 http://127.0.0.1:8000/api/v1/user/userinfo/send_msg/   ---->get 请求就可以查询所有轮播图
    router.register('userinfo', views.UserView, 'userinfo')
    # http://127.0.0.1:8000/api/v1/user/register/   --->post 请求
    router.register('register', views.RegisterUserView, 'register')
    # http://127.0.0.1:8000/api/v1/user/sckill/   --->post 请求
    router.register('sckill', views.SckillView, 'sckill')
    
    urlpatterns = [
    
    ]
    urlpatterns += router.urls
    

    views.py

    # 秒杀的cbv
    from rest_framework.viewsets import ViewSet
    from celery_task.order_task import sckill_task
    from celery.result import AsyncResult
    from celery_task.celery import app
    
    class SckillView(ViewSet):
        '这里我们规定使用get接受,并且用户的秒杀任务的id放在路由中携带'
        @action(methods=['GET'], detail=False)
        def sckill(self, request):
            good_id = request.query_params.get('id')
            # 使用异步,提交秒杀任务
            res = sckill_task.delay(good_id)
            return APIResponse(task_id=res.id)
        '这是用于查询秒杀任务结果的接口'
        @action(methods=['GET'], detail=False)
        def get_result(self, request):
            task_id = request.query_params.get('task_id')
            ans = AsyncResult(id=task_id, app=app)
            if ans.successful():
                result = ans.get()
                print(result)
                if result:
                    return APIResponse(msg='秒杀成功')
                else:
                    return APIResponse(code=101, msg='秒杀失败')
    
            elif ans.status == 'PENDING':
                print('任务等待中被执行')
                return APIResponse(code=666, msg='秒杀进行中')
            else:
                '这里写个else,接收其他情况的结果,防止报错'
                return APIResponse()
    
  • 任务 order_task.py

    任务创建好后,需要去celery文件的app中注册

    app = Celery('test', broker=broker, backend=backend, include=['celery_task.order_task', 'celery_task.user_task', 'celery_task.sckill_task'])
    

    order_task.py

    import random
    from .celery import app
    import time
    
    '秒杀任务'
    @app.task
    def sckill_task(good_id):
        print('秒杀开始')
        time.sleep((random.choice([6, 8, 10])))
        '这里我们模仿秒杀的过程,经过一段时间后才能完成秒杀'
        print('秒杀结束')
        '这里我们用random来模仿秒杀成功的概率'
        return random.choice([True, False])
    
  • 前端Sckill.vue

    <template>
      <div class="sckill">
        <button @click="handleSckill">秒杀</button>
      </div>
    </template>
    
    
    <script>
    
    
    export default {
      name: 'Sckill',
      data() {
        return {
          task_id: '',
          t: null,
        }
      },
      methods: {
        handleSckill() {
          this.$axios(this.$settings.BASE_URL + '/user/sckill/sckill/?id=1').then(res => {
            this.task_id = res.data.task_id
            //  获取到 了秒杀任务的id,就创建定时器,不断查询秒杀任务的结果
            this.t = setInterval(() => {
              this.$axios.get(this.$settings.BASE_URL + '/user/sckill/get_result/?task_id=' + this.task_id).then(res => {
                    if (res.data.code == 666) {
                      // 这里是表示任务还在等待执行,定时任务需要继续执行
                      console.log(res.data.msg)
                    } else {
                      // 这里是秒杀结束了,但是结果不一定的,我们需要结束定时器
                      clearInterval(this.t)
                      this.t = null
                      this.$message(res.data.msg)
                    }
                  }
              )
            }, 2000)
          }).catch(res => {
    
          })
        }
      },
    }
    </script>
    

3、django 中使用celery总结

步骤一

把咱们写的包,复制到项目目录下

project
    ├── celery_task  	# celery包
    │   ├── __init__.py # 包文件
    │   ├── celery.py   # celery连接和配置相关文件,且名字必须交celery.py
    │   └── tasks.py    # 所有任务函数
    ├── add_task.py  	# 添加任务
    └── get_result.py   # 获取结果

步骤二

在celery.py文件中添加一行代码(manage.py中的导入环境变量的代码)

原因:celery中使用djagno,有时候,任务函数或类中会使用django的orm,缓存,表模型,因此一定要添加这行代码

这里的导入环境变量的代码,运行后就相当于加载了配置文件

这样我们在task文件中编写代码如果用到了一些项目中的表,就可以顺利的查找到(因为现在这些配置已经在项目中注册了)

如果我们不注册,就会提示找不到这个被使用的表,就算我们把导入的语句放到函数中去,在代码运行函数的时候仍然会报错

import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')

步骤三

在需要使用celery提交任务的配置,导入包使用即可

-视图函数中使用,导入任务
-任务.delay()  # 提交异步任务
定时任务
延时任务

步骤四

启动worker执行任务,如果有定时任务,启动beat

步骤五

等待任务被worker执行

步骤六

在视图函数中查询任务结果的返回给前端

posted @ 2023-04-06 17:06  dear丹  阅读(103)  评论(0编辑  收藏  举报