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
让worker执行任务
- 通过命令启动worker
- win启动
celery -A main worker -l info -P eventlet
- mac linux
celery -A main worker -l info
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('任务已经开始被执行')
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}'
- 在app的
views.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>