Celery框架


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)

celery架构图


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个小时,更新一下缓存
posted @ 2021-10-08 18:58  hai437  阅读(588)  评论(0编辑  收藏  举报