1 Celery介绍
1.1简介
# 简介:
1.Celery(中文名:芹菜):分布式异步任务框架,是一个独立的框架,跟其他web框架无关
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.
翻译:原生Celery不支持windows
3.celery是为其他项目服务提供异步解决任务需求的
4.celery能够做的事:(*****)
-异步任务(主打功能)
-延迟任务
-定时任务
5.会有两个服务同时运行,一个是项目服务(django服务),一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求
# 补充事项:
1.celery主要是用来解决异步任务的,延迟任务和定时任务只是附带的小功能。如果只要定时功能的话,APScheduler框架更小,更合适。
1.2架构
# 主要分3部分
1.broker:任务中间件,用户提交的任务,存在这个里面(redis,rabbitmq)
2.worker:任务执行者,消费者,真正执行任务的进程(真正干活的人)
3.backend:任务结果存储,任务执行后的结果(redis,rabbitmq)
2 Celery简单使用
2.1安装
# 安装:pip install celery==5.1.2
2.2新建任务celery_task.py
# 本方式采用单独文件开发,新建一个celery_task.py文件
from celery import Celery
# 1 配置消息队列,这里用redis
broker = 'redis://127.0.0.1:6379/1' # redis地址
backend = 'redis://127.0.0.1:6379/2' # redis地址
# backend='redis://:密码@127.0.0.1:6379/1' 如果有密码,这么写
# 2 实例化得到celery对象
app = Celery(__name__, backend=backend, broker=broker) # 注释1
# 3 写一堆任务函数,使用装饰器包裹任务(函数)
@app.task()
def add(a, b):
import time
time.sleep(2)
return a + b
# 注释:
1.__name__:以本文件执行,就是__main__,反之,就是__文件名__
2.3提交任务并执行
"""""""""同步执行(以前采用这种方案,淘汰它)"""""""""
import celery_task
res = celery_task.add(2, 3) # 普通的同步任务,同步执行任务
print(res)
"""""""""""""""""""""异步任务"""""""""""""""""""""
1.提交任务
#任务名.apply_async(参数)
# 返回值是任务id号,唯一标识这个任务
import celery_task
res = celery_task.add.delay(2,3)
# 还可以使用res = celery_task.add.apply_async(kwargs={'a':2,'b':3})
# 还可以通过列表传参res = celery_task.add.apply_async(args=[2, 3])
print(res)
# 返回值:abab1ad3-0e58-4faa-bc05-14d157dc8217
2.让worker执行--->结果存到消息队列(redis)
# 通过命令启动
# 非windows
# 5.x之前这么启动
# 命令:celery worker -A celery_task -l info
# 5.x以后
# celery -A celery_task worker -l info
# windows:
# pip3 install eventlet
# 5.x之前这么启动
# celery worker -A celery_task -l info -P eventlet
# 5.x以后
celery -A celery_task worker -l info -P eventlet
2.4查看任务结果
from celery_task import app
from celery.result import AsyncResult
id = 'abab1ad3-0e58-4faa-bc05-14d157dc8217' # 任务id
if __name__ == '__main__':
a = AsyncResult(id=id, app=app)
if a.successful():
print('任务执行成功了')
result = a.get() # 异步任务的返回值
print(result)
elif a.failed():
print('任务失败')
elif a.status == 'PENDING':
print('任务等待中被执行')
elif a.status == 'RETRY':
print('任务异常后正在重试')
elif a.status == 'STARTED':
print('任务已经开始被执行')
# 注意事项:
1.任务id应该动态获取
3 Celery的包(进阶使用)
# 进阶使用和简单使用的区别就是将任务分开,写到了单独的.py文件中。它们的任务提交和结果查看没有区别。
3.1celery包结构
# 目录结构
-celery_task # 包名
__init__.py
celery.py # 核心文件,在此实例化
course_task.py # 任务1
order_task.py # 任务2
user_task.py # 任务3
提交任务.py # 提交任务
查看结果.py # 查看结果
# 注意事项:
1.实际使用中,任务是变化的,自己重新写,写完要注册
2.实际使用中,提交任务和查看结果是嵌入到项目代码中的
3.2 celery.py
from celery import Celery
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
# include 是一个列表,放被管理的任务文件
app = Celery(__name__, backend=backend, broker=broker,include=[
'celery_task.course_task',
'celery_task.order_task',
'celery_task.user_task',
])
3.3 任务.py
# 任务文件中写自己的任务
# 例:
from .celery import app
@app.task()
def add(a,b):
return a+b
4 定时任务和延时任务
4.1 延时任务
# 延时任务和普通异步任务的区别:提交任务的方式不同
from datetime import datetime, timedelta
eta=datetime.utcnow() + timedelta(seconds=50) # eta =当前的utc时间+ 50s延时
res=user_task.send_sms.apply_async(args=(200, 50), eta=eta) # 50s后,发送短信
print(res)
# 补充事项:
1.apply_async() # 也可以不写时间,表示立即执行,相当于delay()
4.2 定时任务
# 定时任务和普通异步任务的区别:提交任务的方式不同。但延时任务、普通异步任务都是自己提交任务,定时任务是借助beat来定时提交任务。
4.2.1 配置定时任务
# 在celey.py中进行配置
# 1 修改celery的配置信息
# 修改时区
app.conf.timezone = 'Asia/Shanghai' # app.conf是整个celery的配置信息
# 是否使用UTC时间
app.conf.enable_utc = False
# 2 配置定时任务
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
'send_sms_every_3_seconds': {
'task': 'celery_task.user_task.send_sms', # 指定执行的是哪个任务
'schedule': timedelta(seconds=3), # 每隔3秒执行一次
'args': ('18953675221', '8888'),
},
'add_every_1_seconds': {
'task': 'celery_task.course_task.add', # 指定执行的是哪个任务
'schedule': crontab(hour=8, day_of_week=1), # 每周一早八点
'args': (3, 5),
},
}
4.2.2 启动worker
# 执行任务
# 命令:
celery -A celery_task worker -l info -P eventlet
# 注意事项:
1.如果beat没有启动,worker是没有活干的,需要启动beat,worker才能干活
2.worker和beat启动顺序无先后
4.2.3 启动beat
# 定时提交任务
celery -A celery_task beat -l info
5 Django中集成Celery
5.1 django-celery(不推荐)
# django-celery模块
第三方把django和celery集成起来,方便我们使用。但是,第三方写的包的版本,跟celery和django版本要完全对应才能使用,很不方便,所以我们不用。
5.2 自己集成
# 我们自己使用包结构集成到django中
# 第一步,把写好的包,直接复制到项目根路径
# 第二步,在视图类(函数)中使用
from celery_task.user_task import send_sms
def test(request):
mobile = request.GET.get('mobile')
code = '9999'
res = send_sms.delay(mobile, code) # 同步发送假设3分支钟,异步发送,直接就返回id了,是否成功不知道,后期通过id查询
print(res)
return HttpResponse(res)
6 实战:定时更新首页轮播图
6.1 给轮播图加入缓存
# 介绍:
1.如果不加缓存,每次用户访问首页,都去查一次数据库,对数据库压力大
2.第一次访问查数据库,拿到数据,放到缓存(redis),以后再访问,直接从redis中能够获取
3.加入缓存会导致:双写一致性问题(redis缓存和mysql数据不同步)
4.加入缓存会导致:缓存穿透,缓存击穿,缓存雪崩问题(暂时不讲)
# 代码
class BannerView(ViewSetMixin, ListAPIView):
queryset = models.Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[
:settings.BANNER_COUNT]
serializer_class = serializer.BannerSerializer
def list(self, request, *args, **kwargs):
# 先去缓存中获取,如果缓存有,直接返回,如果没有,去数据库查询,放到缓存
# 先去缓存中获取
banner_list = cache.get('banner_list_cache')
if not banner_list: # 去数据库中获取
# 没有走缓存
print('查了数据库')
res = super().list(request, *args, **kwargs)
banner_list = res.data # res是Response对象
# 放入到缓存中
cache.set('banner_list_cache', banner_list)
return Response(data=banner_list)
6.2 轮播图缓存定时更新
6.2.1 任务home_task.py
# 更新轮播图缓存的任务
from celery_task.celery import app
from home import models
from django.conf import settings
from home import serializer
from django.core.cache import cache
@app.task()
def update_banner():
# 1 从mysql中取出轮播图数据
queryset = models.Banner.objects.all().filter(is_delete=False, is_show=True).order_by('orders')[
:settings.BANNER_COUNT]
# 2 序列化
ser = serializer.BannerSerializer(instance=queryset, many=True)
# 3 获取到字典,手动拼上前面的地址
banner_list = ser.data
for banner in banner_list:
banner['image'] = settings.BACKEND_URL % str(banner['image'])
# 4 放到缓存中
cache.set('banner_list_cache', banner_list)
return True
6.2.2 配置celery.py
from celery import Celery
# 由于celery和django 是独立的两个服务,要想在celery服务中使用django,必须加这4句
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "luffy_api.settings.dev")
import django
django.setup()
broker = 'redis://127.0.0.1:6379/1'
backend = 'redis://127.0.0.1:6379/2'
# include 是一个列表,放被管理的task 的py文件
app = Celery(__name__, backend=backend, broker=broker, include=[
'celery_task.course_task',
'celery_task.order_task',
'celery_task.user_task',
'celery_task.home_task', #新写的task,一定要注册
])
# 原来,任务写在这个py文件中
# 后期任务非常多,可能有用户相关任务,课程相关任务,订单相关任务。。。
#### 注册定时任务
###修改celery的配置信息 app.conf整个celery的配置信息
# 时区
app.conf.timezone = 'Asia/Shanghai'
# 是否使用UTC
app.conf.enable_utc = False
# 配置定时任务
from datetime import timedelta
app.conf.beat_schedule = {
'update_banner_every_3_seconds': {
'task': 'celery_task.home_task.update_banner', # 指定执行的是哪个任务
'schedule': timedelta(seconds=3),
},
}
6.3双写一致性问题
# redis缓存和mysql数据不同步
# 缓存更新策略(解决方案)
1.先更新数据库,再更新缓存(可靠性高一些)
2.先更新数据库,再删缓存(可靠性高一些)
3.先删缓存,再更新数据库(缓存删了,数据库还没更新,来了一个请求,缓存了老数据)
4.定时更新(对实时性要求不高)
每隔12个小时,更新一下缓存