celery

celery

1 celery准备

  • 安装celery
pip install celery
  • 安装redis(消息队列和结果存储使用redis)
pip install redis
  • 安装eventlet (win 平台,如果是mac,linux不需要)
pip install eventlet

2 celery 使用场景

  • 异步任务

一些耗时的操作可以交给celery异步执行,而不用等着程序处理完才知道结果。视频转码、邮件发送、消息推送等等

  • 定时任务

定时推送消息、定时爬取数据、定时统计数据等

  • 延迟任务

提交任务后,等待一段时间再执行某个任务

3 celery架构

4 快速使用

celery_demo.py--主文件

from celery import Celery
import time

broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery('celery_test', broker=broker, backend=backend)


@app.task
def add(n, m):
    time.sleep(2)
    return n + m


@app.task
def send_email(mail):
    return '邮件发送成功:%s' % mail

add_task.py--提交异步任务

from .celery_demo import *

# 同步调用
res = send_email('1@qq.com')
print(res)
# 邮件发送成功:1@qq.com


# 异步调用
res = add.delay(5, 9)
print(res)
# 84fa186b-f4f5-4260-ad94-7fae02f70ae6

image-20240516162332366

让worker执行任务

  • 通过命令启动worker
  • win启动
 celery -A main worker -l info -P eventlet
  • mac linux
celery -A main worker -l info 

image-20240516162417831

get_result.py---查看结果

from celery.result import AsyncResult
from celery import Celery
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery('celery_test', broker=broker, backend=backend)


id = '84fa186b-f4f5-4260-ad94-7fae02f70ae6'
if __name__ == '__main__':
    result = AsyncResult(id=id, app=app)
    if result.successful():
        result = result.get()
        print(result)
    elif result.failed():
        print('任务失败')
    elif result.status == 'PENDING':
        print('任务等待中被执行')
    elif result.status == 'RETRY':
        print('任务异常后正在重试')
    elif result.status == 'STARTED':
        print('任务已经开始被执行')

image-20240516162449398

5 celery包结构

目录结构

项目名
    ├── celery_task  	# celery包
    │   ├── __init__.py # 包文件
    │   ├── celery.py   # celery连接和配置相关文件,必须叫celery.py
    │   ├── user_tasks.py    # 所有任务函数
    	└── sms_task.py.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'
# 1 创建app对象
app = Celery('app', broker=broker, backend=backend,include=['celery_task.user_task','celery_task.sms_task'])

user_task.py ---任务函数

from .celery import app
import time

@app.task
def add(x, y):
    time.sleep(3)
    return x + y

sms_task.py ---任务函数

from .celery import app

@app.task
def send_sms(mobile):
    print(f'手机号:{mobile},下单成功')
    return True

add_task.py ---添加任务

from celery_task.sms_task import send_sms


res=send_sms.delay('1895355534')
print(res)
# ad31926f-b83a-4415-8b8a-3cf5995f2061

启动worker

  • 包所在目录下,启动worker
celery -A celery_task worker -l debug -P eventlet

get_result.py ---获取结果

from celery.result import AsyncResult
from celery_task.celery import app

id = 'ad31926f-b83a-4415-8b8a-3cf5995f2061'
if __name__ == '__main__':
    result = AsyncResult(id=id, app=app)
    if result.successful():
        result = result.get()
        print(result)
    elif result.failed():
        print('任务失败')
    elif result.status == 'PENDING':
        print('任务等待中被执行')
    elif result.status == 'RETRY':
        print('任务异常后正在重试')
    elif result.status == 'STARTED':
        print('任务已经开始被执行')

6 执行异步--延迟--定时

  • 延迟任务 add_task.py
from celery_task.user_task import add
from datetime import datetime, timedelta
# datetime.utcnow() 取utc时间---》默认使用utc时间--》修改配置文件做中国时区
eta=datetime.utcnow() + timedelta(seconds=30)
res=add.apply_async(args=[5,6],eta=eta)
  • 定时任务:配置文件 celery.py
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False

# 任务的定时配置
from datetime import timedelta
from celery1 import crontab
app.conf.beat_schedule = {
    'add-task': {
        'task': 'celery_task.user_task.add',
        'schedule': timedelta(seconds=3),  # 每3秒
        # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
        'args': (300, 150),
    },
    'send-sms-task': {
        'task': 'celery_task.order_task.send_sms',
        'schedule': crontab(hour=16,minute=40), # 每天16点40执行
        'args': ('189232222',888),
    },
}
  • 启动worker
celery -A celery_task worker -l debug -P eventlet
  • 启动beat
celery -A celery_task beat -l debug

7 django中使用celery

通用方案

  • 写一个celery任务-更新轮播图
  • 目录结构
项目名
    ├── celery_task  	# celery包
    │   ├── __init__.py # 包文件
    │   ├── celery.py   # 配置文件
    │   ├── home_task.py    # 所有任务函数
    ....

配置文件 celery.py

import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'

app = Celery('app', broker=broker, backend=backend, include=['celery_task.home_task'])
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False


# 定时任务  
app.conf.beat_schedule = {
    'update-banner-task':{
            'task': 'celery_task.home_task.update_banner',
            'schedule': timedelta(minutes=1),
            'args': (),
        },
}

home_task.py

from .celery import app
from django.core.cache import cache
from home.models import Banner
from django.conf import settings
from home.serializer import Bannerserializer


@app.task
def update_banner():
    queryset = Banner.objects.all()
    ser = Bannerserializer(instance=queryset, many=True)
    for item in ser.data:
        item['image'] = settings.BACKEND_URL + item['image']
    cache.set('banner_list', ser.data)
    return '轮播图缓存更新成功'

启动beat和worker

#启动beat
celery -A celery_task beat -l debug
# 启动worker
celery -A celery_task worker -l debug -P eventlet

celery官方方案

  • 在项目目录下新建celery1.py
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE','luffy.settings.dev')

from celery import Celery
app = Celery('celery_demo')

app.config_from_object('django.conf:settings', namespace='CELERY')
# 自动去每个app下检索 所有名字叫task.py 的文件,作为celery的任务
app.autodiscover_tasks()
  • 在项目目录下新建__init__.py 注册
from .celery1 import app as celery_app
__all__ = ('celery_app',)
  • 在配置文件里添加celery 配置
# Broker配置
CELERY_BROKER_URL = 'redis://127.0.0.1:6379/1'
# BACKEND配置
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/2'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
# 结果序列化方案
CELERY_RESULT_SERIALIZER = 'json'
# 任务结果过期时间,秒
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24
# 时区配置
CELERY_TIMEZONE = 'Asia/Shanghai'
  • 在不同app中写tasks.py 使用@shared_task装饰器 必须

文件名必须叫tasks

from celery import shared_task
@shared_task
def send_email(user='***@qq.com'):
    return f'邮件发送成功:{user}'
  • appviews.py中提交任务
from rest_framework.views import APIView
from .task import send_email
class CeleryView(APIView):
    def get(self, request, *args, **kwargs):
        res = send_email.delay()
        return APIResponse(msg=f'邮件已经发送:{str(res)}')
  • 配好路由,启动worker
celery -A luffy.celery1 worker -l debug -P eventlet

补充

@app.task 是与特定 Celery 应用程序关联的任务装饰器,适用于在单个应用程序中定义和使用任务。

@shared_task 是一个通用的任务装饰器,可用于在多个应用程序之间共享任务定义,而无需与特定的应用程序实例关联。

8 定时任务配置

CELERY_BEAT_SCHEDULE = {
    'every_1_minutes': {
        'task': 'luffy_api.apps.home.task.add', # 
        'schedule': timedelta(seconds=2),
        'args': ()
    },
}

celery -A luffy_api.celery1 worker -l debug -P eventlet
celery -A luffy_api.celery1 beat -l debug

9 admin配置定时任务

安装插件

pip install django-celery-beat

app注册

   INSTALLED_APPS = [
    ....
    'django_celery_beat',
    ]

配置文件:屏蔽到原来的调度器

CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers.DatabaseScheduler'

数据库迁移

python manage.py migrate django_celery_beat

启动woker和beat

celery -A luffy.celery1 worker -l debug -P eventlet
celery -A luffy.celery1 beat -l debug

存在的问题

- 周期性 :不涉及到时区,正常用
- 固定时间的:跟时区紧密相关
	-可能会有时区问题

10 admin监视任务

安装插件

pip install django-celery-results

app注册

INSTALLED_APPS = [
...,
'django_celery_results',
]

修改backend配置,将Redis改为django-db

CELERY_RESULT_BACKEND = 'django-db'

数据库迁移

python manage.py migrate django_celery_results

11 Flower 监控celery任务

如果不想通django的管理界面监控任务的执行,还可以通过Flower插件来进行任务的监控。Flower的界面更加丰富,可以监控的信息更全

Flower 是一个用于监控和管理 Celery 集群的开源 Web 应用程序。它提供有关 Celery workers 和tasks状态的实时信息

  • 实时监控celery的Events

    • 查看任务进度和历史记录
    • 查看任务详细信息(参数、开始时间、运行时间等)
  • 远程操作

    • 查看workers 状态和统计数据
    • 关闭并重新启动workers 实例
    • 控制工作池大小和自动缩放设置
    • 查看和修改工作实例消耗的队列
    • 查看当前正在运行的任务
    • 查看计划任务(预计到达时间/倒计时)
    • 查看保留和撤销的任务
    • 应用时间和速率限制
    • 撤销或终止任务
  • Broker 监控

    • 查看所有 Celery 队列的统计信息

安装插件

pip install flower

启动

# 方式一:
celery -A celery_demo flower --port-5555
#方式二
celery --broker=redis://127.0.0.1:6379/1 flower

浏览器访问

http://127.0.0.1:5555/

12 任务异常自动告警

虽然可以通过界面来监控了,但是我们想要得更多,人不可能天天盯着界面看吧,如果能实现任务执行失败就自动发邮件告警就好了。这个Celery当然也是没有问题的。
通过钩子程序在异常的时候触发邮件通知

tasks.py

from celery import Task
from django.core.mail import send_mail
from django.conf import settings


class SendEmailTask(Task):
    def on_success(self, retval, task_id, args, kwargs):
        info = f'任务成功-- 任务id是:{task_id} , 参数是:{args} , 执行成功 !'
        send_mail('celery任务监控成功告警', info, settings.EMAIL_HOST_USER, ["***@qq.com", ])
        print('------------成功')

    def on_failure(self, exc, task_id, args, kwargs, einfo):
        info = f'任务失败-- 任务id为:{task_id} , 参数为:{args} , 失败 ! 失败信息为: {exc}'
        send_mail('celery任务监控失败告警', info, settings.EMAIL_HOST_USER, ["***@qq.com", ])
        print('------------失败')

    def on_retry(self, exc, task_id, args, kwargs, einfo):
        print(f'任务id位::{task_id} , 参数为:{args} , 重试了 !  错误信息为: {exc}')

重启服务

celery -A luffy.celery1 worker -l debug -P eventlet
celery -A luffy.celery1 beat -l debug

13 异步秒杀方案---官方celery结构

后端

task.py

@shared_task
def seckill(course_id):
    print('根据课程id:%s,查询课程是否还有剩余,耗时2s' % course_id)
    time.sleep(2)
    res = random.choice([True, False])
    if res:  # 库存够
        print('扣减库存,耗时1s')
        time.sleep(1)
        print('下单,耗时2s')
        time.sleep(2)
        return True
    else:
        return False

views.py

from rest_framework.decorators import action
from rest_framework.viewsets import ViewSet
from .tasks import seckill
from celery.result import AsyncResult
from celery1 import app

class SeckillView(ViewSet):
    @action(methods=['POST'], detail=False)
    def seckill(self, request, *args, **kwargs):
        course_id = request.data.get('course_id')
        task_id = seckill.delay(course_id)
        return APIResponse(msg='您正在排队', task_id=str(task_id))

    @action(methods=['GET'], detail=False)
    def get_result(self, request, *args, **kwargs):
        task_id = request.query_params.get('task_id')
        a = AsyncResult(id=task_id, app=app)
        if a.successful():
            result = a.get()  # True 和 False
            if result:
                return APIResponse(success='1', msg='秒杀成功')
            else:
                return APIResponse(success='0', msg='秒杀失败')
        elif a.status == 'PENDING':
            print('任务等待中被执行')
            return APIResponse(success='2', msg='任务等待中被执行')
        else:
            return APIResponse(success='3', msg='秒杀任务正在执行')

前端

  • 路由
  {
    path: '/seckill',
    name: 'seckill',
    component: SeckillView
  },

SeckillView.vue

<template>
  <div>
    <Header></Header>
    <div style="padding: 50px;margin-left: 100px">
      <h1>Go语言课程</h1>
      <img src="http://photo.liuqingzheng.top/2023%2002%2022%2021%2057%2011%20/image-20230222215707795.png"
           height="300px"
           width="300px">
      <br>
      <el-button type="danger" @click="handleSeckill" v-loading.fullscreen.lock="fullscreenLoading">秒杀课程</el-button>
    </div>
    <br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
    <Footer></Footer>
  </div>
</template>

<script>
import Header from '@/components/Header'
import Footer from '@/components/Footer'

export default {
  name: "SckillView",
  data() {
    return {
      fullscreenLoading: false,
      task_id: '',
      t: null,
    }
  },
  methods: {
    handleSeckill() {
      this.fullscreenLoading = true;
      this.$axios({
        url: 'http://127.0.0.1:8000/api/v1/home/seckill/seckill/',
        method: 'POST',
        data: {
          course_id: '99'
        }
      }).then(res => {
        // 在排队,转圈的,还需要继续显示
        this.$message.success(res.data.msg)
        this.task_id = res.data.task_id
        // 继续发送请求---》查询是否秒杀成功:1 成功 2 没成功  3 秒杀任务还没执行
        // 启动定时任务,没隔1s,向后端发送一次请求
        this.t = setInterval(() => {
          this.$axios({
            url: 'http://127.0.0.1:8000/api/v1/home/seckill/get_result/',
            method: 'get',
            params: {
              task_id: this.task_id
            }
          }).then(res => {
            // 100 成功,success : 1 成功  0 失败  2 还没开始
            if (res.data.success == '1') {
              // 转圈框不显示
              this.fullscreenLoading = false;
              // 停止定时任务
              clearInterval(this.t)
              this.t = null
              this.$message.success(res.data.msg)

            } else if (res.data.success == '0') {
              // 转圈框不显示
              this.fullscreenLoading = false;
              // 停止定时任务
              clearInterval(this.t)
              this.t = null
              this.$message.error(res.data.msg)
            } else {
              // this.$message.error(res.msg)
              console.log(res.msg)
            }
          })


        }, 1000)


      }).catch(res => {
        this.fullscreenLoading = false;
        this.$message.error(res)
      })
    }
  },
  components: {
    Header, Footer
  }
}
</script>

<style scoped>

</style>
posted @ 2024-05-23 16:40  蓝幻ﹺ  阅读(10)  评论(0编辑  收藏  举报