Celery基本使用及其在django项目中的应用

Celery简介

官方文档

Celery 官网:http://www.celeryproject.org/

Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html

Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/

celery是异步任务框架,执行异步任务,执行延迟任务,执行定时任务

# 注意:celery在windows上有bug,若bug无法解决时推荐在linux上使用celery

使用场景

异步执行:解决耗时任务,将耗时操作任务提交给Celery去异步执行,比如发送短信/邮件、消息推送、音视频处理等等

延迟执行:解决延迟任务

定时执行:解决周期(周期)任务,比如每天数据统计

Celery异步任务框架

  • 可以不依赖任何服务器,通过自身命令,启动服务(内部支持socket)
  • celery服务为为其他项目服务提供异步解决任务需求的
  • 会有两个服务同时运行,一个是项目服务,一个是celery服务,项目服务将需要异步处理的任务交给celery服务,celery就会在需要时异步完成项目的需求

Celery架构

Celery的架构由三部分组成:消息中间件(Broker)、任务执行单元(Worker)和 任务执行结果存储(Backend)组成。

img

#### 消息中间件BrokeR
Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQ, Redis等等


#### 任务执行单元Worker
Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。


#### 任务结果存储Backend
Backend用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, redis等

Celery使用方式

下载安装celery模块

pip install celery

celery有两种使用方式:第一种方式是在一个py文件中使用;第二种是在一个包结构(包管理,【推荐】)

第一种使用方式:py文件

第一步:任务和worker放在一个py文件中,然后通过命令行的方式启动worker

# celery_task.py

from celery import Celery

# 配置broker和 backend,全部使用redis数据库
broker = 'redis://127.0.0.1:6379/1'  		# broker任务队列位置
backend = 'redis://127.0.0.1:6379/2'  		# 存储位置,执行完的结果存在这

# 实例化一个应用对象app
app = Celery(__name__, broker=broker, backend=backend)


@app.task  				# 使用app.task这个装饰器添加my_add这个任务
def my_add(x, y):  		# 具体需要被执行的任务
    print(x, y)
    return x + y


"""
用命令来执行, 启动worker
    # linux/mac
        celery worker -A celery_task -l info
        
    # windows:
        windows上如果通过上述命令启动失败或者后期执行任务失败则借助eventlet模块启动
        pip3 install eventlet
        celery worker -A celery_task -l info -P eventlet

"""

第二步:提交任务(执行该py文件)

# run_task.py

# 导入被执行的任务
from celery_task import my_add

# 异步调用,将该任务提交到broker中,并返回一个任务编号(uuid格式)
ret = my_add.delay(5, 4)


# my_add(3,4)  # 这样的操作是直接执行my_add函数,不是异步调用,不会被添加到broker中

第三步:查看任务执行的结果

# get_results.py


from celery.result import AsyncResult
from celery_task import app

# 通过任务编号查找任务结果
id = 'a43aa718-e617-4ec4-8afe-220a8df0d4e7'

if __name__ == '__main__':
    async = AsyncResult(id=id, app=app)
    if async.successful():
        result = async.get()		# 拿到数据结果
        print(result)
    elif async.failed():
        print('任务失败')
    elif async.status == 'PENDING':
        print('任务等待中被执行')
    elif async.status == 'RETRY':
        print('任务异常后正在重试')
    elif async.status == 'STARTED':
        print('任务已经开始被执行')

第二种使用方式:包管理结构

提倡使用用包管理的方式,好处是结构更清晰,将worker和具体的任务分开放在不同的py文件内。

包目录结构

project
    ├── celery_task  			# celery包
    │   ├── __init__.py 		# 包文件
    │   ├── celery.py   		# celery连接和配置相关文件,且名字必须叫celery.py
    │   └── tasks.py    		# 所有任务函数
    ├── run_celery_task.py  	        # 提交任务
    └── get_result.py   		# 获取结果

第一步:新建一个包,包名任意,如:celery_task,在包内新建一个名字为celery.py的文件,在该文件内书写worker

# celery_task/celery.py

from celery import Celery

# 配置broker和 backend,全部使用redis
broker = 'redis://127.0.0.1:6379/1'  # broker任务队列位置
backend = 'redis://127.0.0.1:6379/2'  # 存储位置,执行完的结果存在这

# 实例化app对象,通过include这个列表反射出这个包下面需要执行的任务文件(即具体的任务书写在task1这个文件中)
app = Celery(
    __name__,
    broker=broker,
    backend=backend,
    include=['celery_task.task1', 'celery_task.task2']
)

# 注意:该文件的名字必须是 celery.py

第二步:在包内的其他py文件内书写具体的任务

# task1.py
from .celery import app		# 从包内的celery.py文件中导入app
@app.task
def my_add(x, y):
    print(x, y)
    return x + y


# task2.py
from .celery import app		# 从包内的celery.py文件中导入app
@app.task
def my_multi(x, y):
    print(x, y)
    return x * y

第三步:启动worker,等待任务被提交后执行任务

# 通过命令行的方式启动:切换到celery_task包所在的路径下,执行如下命令
# linux/mac
        celery worker -A celery_task -l info		# celery_task是包名
        
# windows:
        windows上如果通过上述命令启动失败或者后期执行任务失败则借助eventlet模块启动
        pip3 install eventlet
        celery worker -A celery_task -l info -P eventlet

第四步:提交任务:异步任务,延迟任务(手动提交,右键执行py文件)

在需要提交任务的位置,引入celery_task包内具体的任务文件,导入具体的任务函数,然后异步执行该函数。

# run_celery_task.py
# 执行任务

# 从包内导入具体的任务函数
from celery_task.task1 import my_add
from celery_task.task2 import my_multi


#  提交异步任务,delay()
ret1 = my_add.delay(6, 7)
ret2 = my_multi.delay(1, 9)
print(ret1)
print(ret2)



# 提交延迟任务, apply_async
from datetime import datetime, timedelta
# 需要utc时间
eta = datetime.utcnow() + timedelta(seconds=10)

# 任务参数通过args传递,eta是执行任务的utc时间
ret = my_add.apply_async(args=(240, 50), eta=eta)
print(ret)

提交定时任务的设置:定时任务在worker启动时就应该被设置好,即将定时任务的设置写在celety.py文件内【自动提交任务】

# celery_task/celery.py

from celery import Celery

broker = 'redis://127.0.0.1:6379/1' 
backend = 'redis://127.0.0.1:6379/2' 

app = Celery(
    __name__,
    broker=broker,
    backend=backend,
    include=['celery_task.task1', 'celery_task.task2']
)


# 执行定时任务
# 选择本地时区,并不使用UTC
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False

# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab

app.conf.beat_schedule = {
    'add-task': {
        'task': 'celery_task.task1.my_add',
        'schedule': timedelta(seconds=3),   				# 每3秒执行一次
        # 'schedule': crontab(hour=8, day_of_week=1), 		# 每周一早八点
        'args': (300, 150),         						# 该定时任务需要的参数
    }
}

执行定时任务的命令

# 启动worker
	celery worker -A celery_task -l info -P eventlet
    
# 启动beat 【开启定时器】
	celery beat -A celery_task -l info		# 此时,windows上不用需要借助eventlet模块

第五步:获取任务结果,获取任务结果同第一种使用方式一致,通过任务id查看任务状态及任务结果


django中使用celery

#### 重点:由于采用了django的反射机制,使用celery.py所在的celery_task包必须放置在django项目的根目录下

"""
celery框架django项目工作流程
    1)加载django配置环境
    2)创建Celery框架对象app,配置broker和backend,得到的app就是worker
    3)给worker对应的app添加可处理的任务函数,用include配置给worker的app
    4)完成提供的任务的定时配置app.conf.beat_schedule
    5)启动celery服务,运行worker,执行任务
    6)启动beat服务,运行beat,添加任务
"""

首页轮播图:定时更新缓存

设置celery工作包内的celery.py文件以及任务文件tasks.py

# ############################################################### celery.py


from celery import Celery
broker = 'redis://127.0.0.1:6379/0'
backend = 'redis://127.0.0.1:6379/1'

app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])


# 定时任务设置
# 本地时区, 不使用UTC
app.conf.timezone = 'Asia/Shanghai'
app.conf.enable_utc = False

# 任务的定时配置
from datetime import timedelta
from celery.schedules import crontab
app.conf.beat_schedule = {
    'update-banner-list': {
        'task': 'celery_task.tasks.update_banner_list',
        'schedule': timedelta(seconds=5),					# 测试:5s更新一次缓存,即5s调用一次定时的任务
        # 'args': (),										# update-banner-list()函数不需要参数
    }
}


# ############################################################### tasks.py

from django.core.cache import cache

# 加载 配置django环境,这样才可以在本文件内调用django项目中的文件以及模型等
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Luffyapi.settings.dev")
import django
django.setup()



@app.task
def update_banner_list():		# 任务:更新缓存内的轮播图
    
    # 导入需要的模块文件
    from home import models, ser
    from .celery import app

    queryset = models.Banner.objects.filter(is_delete=False, is_show=True).order_by('-orders')[:3]
    banner_list = ser.BannerSerializer(queryset, many=True).data

    # 拿不到request对象,所以头像的连接base_url要自己组装
    for banner in banner_list:
        banner['image'] = 'http://127.0.0.1:8000%s' % banner['image']

    cache.set('banner_list', banner_list, 30)
    return True

轮播图接口视图函数从缓存中取数据

# views.py

def list(self, request, *args, **kwargs):
    # 1 先去缓存拿数据
    banner_list=cache.get('banner_list')
    if not banner_list:
        # 缓存中没有,再去数据库拿;有缓存直接返回
        response = super().list(request, *args, **kwargs)
        # 同步到缓存
        cache.set('banner_list', response.data, 60*60*24)	# 加一天的过期时间
        return response

    return Response(data=banner_list)
posted @ 2020-07-25 17:22  the3times  阅读(586)  评论(3编辑  收藏  举报