第三十一章:celery
一:celery
1.celery介绍
1.1 官网
# 1 celery官网:http://www.celeryproject.org/
# 2 介绍:Celery is a project with minimal funding, so we don’t support Microsoft Windows. Please don’t open any issues related to that platform
# 3 celery 是独立的服务
1.2 架构
# Celery 是什么:分布式的异步任务 框架
-翻译过来是 芹菜 的意思,跟芹菜没有关系
-框架:服务,python 的框架,跟 django 无关
-能用来做什么
-1 异步任务:可以提高项目的并发量,之前开启线程做,现在使用 celery 做
-2 定时任务
-3 延迟任务
# 理解 celery 的运行原理
1)可以不依赖任何服务器,通过自身命令,启动服务
2)celery 服务为为其他项目服务提供异步解决任务需求的
注:会有两个服务同时运行,一个是项目服务,一个是 celery 服务,项目服务将需要异步处理的任务交给 celery 服务,celery 就会在需要时异步完成项目的需求
# 架构
-任务中间件: Broker(中间件)
-其他服务提交的异步任务,放在里面排队
- celery 本身不提供消息中间件
-需要借助于第三方:redis,rabbitmq
-任务执行单元:worker,真正执行任务的地方,一个个进程,执行函数
-结果存储:backend
-函数 return 的结果存储在这里
- celery 本身不提供结果存储,借助于第三方库:redis,mysql
# 使用场景
异步执行:解决耗时任务
延迟执行:解决延迟任务
定时执行:解决周期(周期)任务
举例描述:
人是一个独立运行的服务(django) | 医院也是一个独立运行的服务(celery)
正常情况下,人可以完成所有健康情况的动作,不需要医院的参与;但当人生病时,就会被医院接收,解决人生病问题
人生病的处理方案交给医院来解决,所有人不生病时,医院独立运行,人生病时,医院就来解决人生病的需求
消息中间件(message broker)(邮箱, 邮局): 本身不提供消息服务,可以和第三方消息中间件集成,常用的有 redis mongodb rabbitMQ
任务执行单元(worker)(寄件人): 是Celery提供的任务执行单元, worker并发的运行在分布式的系统节点中
任务执行结果存储(task result store)(收件人):用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括Redis,MongoDB,Django ORM,AMQP等
2.快速使用
2.1 使用步骤
# 1.安装
pip install celery
win:pip install eventlet
# 2.写一个 main.py: 实例化得到 app 对象,写函数(任务),注册成 celery 的任务
# 3.再别的程序中:提交任务 ---> 提交到 broker 中去了
add.delay(3,4)
# 4.启动 worker,从 broker 中取任务执行,执行完放到 backend 中
win:
celery worker -A main -l info -P eventlet # 4.x
celery -A main worker -l info -P eventlet # 5.x 及之后用这个
lin、mac:
celery worker -A main -l info
celery -A main worker -l info
# 5.查看结果
-再 backend 中查看任务执行的结果
-直接看:命令行、可视化工具
-通过代码查看
from main import app
from celery.result import AsyncResult
id = '7bef14a0-f83e-4901-b4d8-e47aaac09b39'
if __name__ == '__main__':
res = AsyncResult(id=id, app=app)
if res.successful():
result = res.get() #7
print(result)
elif res.failed():
print('任务失败')
elif res.status == 'PENDING':
print('任务等待中被执行')
elif res.status == 'RETRY':
print('任务异常后正在重试')
elif res.status == 'STARTED':
print('任务已经开始被执行')
2.2 代码示例
# main.py 注册 test 任务
import time
from celery import Celery
backend = 'redis://127.0.0.1:6379/1'
broker = 'redis://127.0.0.1:6379/0'
app = Celery('test', backend=backend, broker=broker)
@app.task
def test(a, b):
time.sleep(2)
add = a + b
print(a + b)
return add
# work.py 提交任务
from main import test
print('hello world')
# delay 异步
res = test.delay(50, 66)
print('res', type(res)) # <class 'celery.result.AsyncResult'>
print("res", res) # res 8ffd680b-cd37-4f4b-ab01-9428ade4606b
# look.py 查看任务
from main import app
from celery.result import AsyncResult
# 拿到任务 id 查看执行结果
id = '8ffd680b-cd37-4f4b-ab01-9428ade4606b'
if __name__ == '__main__':
res = AsyncResult(id=id, app=app)
if res.successful():
result = res.get()
print(result)
elif res.failed():
print('任务失败')
elif res.status == 'PENDING':
print('任务等待被执行')
elif res.status == 'RETRY':
print('任务异常后正在重试')
elif res.status == 'STARTED':
print('任务已经开始被执行')
二:使用指南
# 注意:
1 启动命令的执行位置,如果是包结构,一定在包这一层
2 include=['celery_task.order_task'],路径从包名下开始导入,因为我们在包这层执行的命令
1.celery包结构
project
├── celery_task # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery 连接和配置相关文件,且名字必须交 celery.py
│ └── tasks.py # 所有任务函数
├── add_task.py # 添加任务
└── get_result.py # 获取结果
############# 第一步:新建包 celery_task #############
# 在包下新建[必须叫celery]的py文件,celery.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, include=['celery_task.order_task', 'celery_task.user_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 ,包所在目录下
celery -A celery_task worker -l info -P eventlet
###第四步:其他程序 提交任务,被提交到中间件中,等待worker执行,因为worker启动了,就会被worker执行
from celery_task import send_sms
res=send_sms.delay('1999999', 8888)
print(res) # 7d39033c-4cc7-4af2-8d78-e62c277db183
### 第五步:worker执行完,结果存到backend中
### 第六步:我们查看结构
from celery_task import app
from celery.result import AsyncResult
id = '7d39033c-4cc7-4af2-8d78-e62c277db183'
if __name__ == '__main__':
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('任务已经开始被执行')
2.celery任务类型
# 异步任务
任务.delay(参数)
# 延迟任务
任务.apply_async(args=[参数], eta=时间对象) # 如果没有修改时区,需要使用 utc 事件
# 定时任务
-需要启动 beat 和启动 worker
-beat 定时提交任务的进程 ---》配置在 app.conf.beat_schedule 的任务
-worker 执行任务的
### 使用步骤
# 第一步:在 celery 的 py 文件中写入
app.conf.timezone = 'Asia/Shanghai'
# 是否使用 UTC
app.conf.enable_utc = False
# celery的配置文件#####
# 任务的定时配置
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': ('18888888', '6666'),
},
}
## 第二步:启动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'], 路径从包名下开始导入,因为我们在包这层执行的命令
2.1 延时任务
# 延迟任务
任务.apply_async(args=[参数], eta=时间对象) # 如果没有修改时区,需要使用 utc 事件
# 包结构
project
├── celery_task # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery 连接和配置相关文件,且名字必须交 celery.py
│ └── test_task.py # 所有任务函数
├── run.py # 添加任务
# celery.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, include=['celery_task.test_task'])
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False
# test_task.py
from celery_task.celery import app
import time
@app.task
def send_sms(phone, code):
time.sleep(3)
print("给%s发送短信成功,验证码为:%s" % (phone, code))
return True
# run.py
from celery_task.test_task import send_sms
from datetime import datetime, timedelta
time_w = datetime.utcnow() + timedelta(seconds=20)
print(time_w)
# res = send_sms.delay('15612341234', '658923')
res2 = send_sms.apply_async(args=['1551111111', '123123', ], eta=time_w)
# print(res)
print(res2)
# 启动
# celery -A celery_task worker -l info -P eventlet
2.2 定时任务
# 包结构
project
├── celery_task # celery包
│ ├── __init__.py # 包文件
│ ├── celery.py # celery 连接和配置相关文件,且名字必须交 celery.py
│ └── test_task.py # 所有任务函数
├── run.py # 添加任务
# celery.py
from celery import Celery
from datetime import datetime, timedelta
# from celery.schedules import crontab
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
app = Celery('test', broker=broker, backend=backend, include=['celery_task.test_task'])
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用 UTC
app.conf.enable_utc = False
app.conf.beat_schedule = {
'send_sms': {
'task': 'celery_task.test_task.send_sms',
'schedule': timedelta(seconds=5),
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
# 'schedule': crontab(hour=9, minute=43), # 每周一早八点
'args': ('15066662222', '8965')
},
# 配置多个定时任务
}
# test_task.py
from celery_task.celery import app
import time
@app.task
def send_sms(phone, code):
time.sleep(3)
print("给%s发送短信成功,验证码为:%s" % (phone, code))
return True
# 启动
# celery -A celery_task beat -l info
# celery -A celery_task worker -l info -P eventlet
3.秒杀任务
# urls.py
router.register('seckill', SeckillView, 'seckill')
# 完整接口路由
# http://127.0.0.1:8000/api/v1/course/seckill/seckill/
# http://127.0.0.1:8000/api/v1/course/seckill/get_result/?id=
# views.py
from rest_framework.viewsets import ViewSet
from celery_task.test_task import shoping
from utils.response import APIResponse
from celery.result import AsyncResult
from celery_task.test_task import app
class SeckillView(ViewSet):
@action(methods=['GET'], detail=False)
def seckill(self, request):
shop_id = request.query_params.get('id')
res = shoping.delay(shop_id)
return APIResponse(msg='提交秒杀', id=res.id)
@action(methods=['GET'], detail=False)
def get_result(self, request):
id = request.query_params.get('id')
res = AsyncResult(id=id, app=app)
if res.successful():
result = res.get()
if result:
return APIResponse(msg=f'秒杀成功 {result}')
else:
return APIResponse(msg='秒杀失败')
elif res.status == 'PENDING':
return APIResponse(code= 101, msg='还在秒杀中')
# test_task.py 使用 celery_task 包
from celery_task.celery import app
import time
import random
@app.task
def shoping(id):
# 秒杀任务前
print('开始秒杀商品 %s' % id)
time.sleep(10)
print('结束秒杀商品 %s' % id)
# 秒杀任务后
return random.choice([True, False])
# Seckill.vue 前端
<template>
<div>
<img src="https://img2.baidu.com/it/u=1215965897,3514702558&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1678640400&t=c4c880d6ad208e24f69c0a731a3fd055" alt="" height="300px" width="300px">
<br>
<el-button type="danger" plain @click.once="handleClick">秒杀</el-button>
</div>
</template>
<script>
export default {
name: "Seckill",
methods: {
handleClick() {
this.$axios.get(this.$settings.BASE_URL + 'api/v1/course/seckill/seckill/').then(res => {
if (res.data.code == 0) {
let task_id = res.data.id
console.log(res.data.msg)
this.$message({
message: res.data.msg,
type: 'error'
});
// 起个定时任务,每隔5s向后端查询一下是否秒杀成功
let t = setInterval(() => {
this.$axios.get(this.$settings.BASE_URL + 'api/v1/course/seckill/get_result/?id=' + task_id).then(
res => {
if (res.data.code == 101) { //秒杀结束了,要么成功,要么失败了
console.log(res.data.msg)
// 销毁掉定时任务
} else{
//什么事都不干
alert(res.data.msg)
clearInterval(t)
}
}
)
}, 5000)
}
})
}
}
}
</script>
4.在 Django 中使用 celery
# 使用步骤:
-1 把咱们写的包,复制到项目目录下
-luffy_api
-celery_task # celery的包路径
celery.py # 此文件中 一定不要忘了一句话
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
-luffy_api #源代码路径
-2 在使用提交异步任务的位置,导入使用即可
-视图函数中使用,导入任务
-任务.delay() # 提交异步任务
-任务.apply_async() # 提交延时任务
-定时任务配置
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用 UTC
app.conf.enable_utc = False
app.conf.beat_schedule = {
'send_sms': {
'task': 'celery_task.test_task.send_sms',
'schedule': timedelta(seconds=5),
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
# 'schedule': crontab(hour=9, minute=43), # 每周一早八点
'args': ('15066662222', '8965')
},
# 配置多个定时任务
}
-3 启动worker,如果有定时任务,启动beat
-4 等待任务被worker执行
-5 在视图函数中,查询任务执行的结果
# 重点:celery 中使用 djagno,有时候,任务中会使用 django 的 orm,缓存,表模型。。。。一定要加
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
三:实际应用
1.轮播图加入缓存
from rest_framework.mixins import ListModelMixin
class BannerView(GenericViewSet, ListModelMixin):
queryset = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[
:common_settings.BANNER_COUNT]
serializer_class = BannerModelSerializer
def list(self, request, *args, **kwargs):
redis_banner = cache.get('banner_list')
if not redis_banner:
print('走了数据库')
res = super().list(self, request, *args, **kwargs)
cache.set('banner_list', res.data)
return APIResponse(data=res.data)
print('走了缓存')
return APIResponse(data=redis_banner)
2.双写一致性
# 加入缓存后,缓存中有数据,先去缓存拿,但是如果mysql中数据变了,缓存不会自动变化,出现数据不一致的问题
mysql 和 缓存数据库 数据不一致
# 双写一致性
写入mysql,redis没动,数据不一致存在问题
#如何解决
1 修改数据,删除缓存
2 修改数据,更新缓存
3 定时更新缓存 ---》实时性差
# 定时任务:celery
轮播图案例
# celery.py
# 第一步:在 celery 配置定时任务
app.conf.beat_schedule = {
'banner_list': {
'task': 'celery_task.home_task.banner_list',
'schedule': timedelta(seconds=5),
# 'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
# 'schedule': crontab(hour=9, minute=43), # 每周一早八点
# 'args': ('15066662222', '8965')
},
}
# 第二步:启动worker,启动beat
# home_task.py
# 第二步:在 celery_task 包内新建 home_task.py 文件创建 banner_list 任务,启动worker,启动beat
from celery_task.celery import app
from home.models import Banner
from home.serializer import BannerModelSerializer
from django.conf import settings
from django.core.cache import cache
@app.task
def banner_list():
banner_obj = Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')
ser = BannerModelSerializer(instance=banner_obj, many=True)
for s in ser.data:
s['pic_addr'] = settings.BACKEND_URL + s['pic_addr']
print(s['pic_addr'])
return True
# 后端地址
# BACKEND_URL = 'http://127.0.0.1:8000'