celery

  • web应用:当用户在网站进行某个操作需要很长时间完成时,可以将这种操作交由celery执行,直接返回给用户,等到celery执行完成以后通知用户
  • 任务场景:比如在运维场景下需要批量在几百台机器上执行某些命令或者任务,此时celery可以轻松处理
  • 定时任务:向定时导数据报表、定时发送通知类似场景,虽然Linux的计划任务可以帮我实现,但是非常不利于管理,而Celery可以提供管理接口和丰富的API。

celery架构

  • 消息中间件:broker
    • 提交的任务【函数】都放在这里, celery本身不能提供消息中间件
    • 需要借助于第三方: redis或rabbitmq
  • 任务执行单元:worker
    • 真正执行任务的的地方,一个个进程中执行函数
  • 结果储存:backend
    • 函数return的结果都存储在这里, celery本身不提供结果存储
    • 需要借助于第三方: redis或rabbitmq

celery快速使用

celery不支持在Windows上使用,可以通过eventlet在windows上运行

pip install  celery
pip install eventlet

简单使用

目录结构

project/
├── __init__.py  
├── config.py
└── tasks.py

各目录文件说明

  • __init__.py 初始化celery以及加载配置文件

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    # Author:wd
    from celery import Celery
    app = Celery('project')                                # 创建 Celery 实例
    app.config_from_object('project.config')               # 加载配置模块
    
  • config.py celery相关配置文件

    更多配置参考:http://docs.celeryproject.org/en/latest/userguide/configuration.html

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    # Author:wd
    
    BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件
    
    CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis
    
    CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案
    
    CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间
    
    CELERY_TIMEZONE='Asia/Shanghai'   # 时区配置
    
    CELERY_IMPORTS = (     # 指定导入的任务模块,可以指定多个
        'project.tasks',
    )
    
  • task.py文件

    #!/usr/bin/env python3
    # -*- coding:utf-8 -*-
    # Author:wd
    
    from project import app
    @app.task
    def show_name(name):
        return name
    

启动Worker

celery worker -A project -l debug

各个参数含义:

  worker: 代表第启动的角色是work当然还有beat等其他角色;

  -A :项目路径,这里我的目录是project

  -l:启动的日志级别,更多参数使用celery --help查看

查看日志输出,会发现我们定义的任务,以及相关配置:

img

虽然启动了Worker,但是还需要通过delay或apply_async来将任务添加到Worker中,这里我们通过交互式方式添加任务,并返回AsyncResult对象,通过AsyncResult对象获取结果:

img

AsyncResult除了get方法用于常用获取结果方法外还提以下常用方法或属性:

  • state: 返回任务状态;
  • task_id: 返回任务id;
  • result: 返回任务结果,同get()方法;
  • ready(): 判断任务是否以及有结果,有结果为True,否则False;
  • info(): 获取任务信息,默认为结果;
  • wait(t): 等待t秒后获取结果,若任务执行完毕,则不等待直接获取结果,若任务在执行中,则wait期间一直阻塞,直到超时报错;
  • successfu(): 判断任务是否成功,成功为True,否则为False;

celery 包结构

创建celery包结构

什么是包结构:通过将celery服务封装成包的形式,放在项目需要使用的时候导入即可

project
    ├── celery_task  	  # celery包
    │   ├── __init__.py  # 包文件
    │   ├── celery.py   # celery连接和配置相关文件,且名字必须交celery.py
    │   └── tasks.py   # 所有任务函数
    │   └── settings.py
    ├── add_task.py  	 # 添加任务
    └── get_result.py   # 获取结果
- 1、第一步:在包下创建py文件(名字必须为celery.py)
    # 导入celery模块
    from celery import Celery
    # 导入配置broker和backend
    from .settings import BACKEND, BROKER

    # 实例化celery对象
    app = Celery('test',
                 broker=BROKER, 
                 backend=BACKEND,
                 include=['celery_task.order_task', 
                          'celery_task.user_task'])

- 2、第二步:创建settings.py,用于存放配置
    BROKER = 'redis://127.0.0.1:6379/1'
    BACKEND = 'redis://127.0.0.1:6379/2'
    
- 3、第三步,创建py文件(task.py),用于存放需要执行的异步任务
    # 导入celery实例对象
    from .celery import app

    # 计算函数
    @app.task()
    def add(a, b):
        print('计算结果为:', a + b)
        return True

    # 模拟发送短信
    @app.task()
    def send_sms(mobile, code):
        print('已向手机号:%s 发送短信,验证码为:%s' % (mobile, code))
        return True
    
- 4、第四步:开启worker
	切换到celery所在的目录下,开启worker命令
	celery -A celery_task worker -l info -P eventlet
	

- 5、第五步:提桥任务: # add_task.py 文件下
    # 提交任务,这里模拟的是异步任务的提交
    res = add.delay(a, b)  # 提交后可以接收任务的ID
    res1 = send_sms.delay(mobile, code)
    
                                
- 6、第六步:查看任务执行结果: # get_result.py 文件下
    # 导入celery实例
    from celery_task.celery import app
    from celery.result import AsyncResult
    
	 id = res
    id1 = res1
		
    # 通过传入任务的ID就可以查询到任务的执行结果
    def res_func(id):
        id = id
        a = AsyncResult(id=id, app=app)
        if a.successful():  # 执行完了
            result = a.get()
            if result: return '执行完成'
        elif a.failed():
            return '任务失败,失败的原因可能是未开启worker'
        elif a.status == 'PENDING':
            return '任务等待中被执行,当前任务较多或未开启worker'
        elif a.status == 'RETRY':
            return '任务异常后正在重试'
        elif a.status == 'STARTED':
            return '任务已经开始被执行,请稍后查询'

Celery执行异步任务、延迟任务、定时任务

定时任务&计划任务

celery的提供的定时任务主要靠schedules完成

执行定时任务需要启动beat和worker

  • beat:定时提交任务的进程---》配置在app.conf.beat_schedule的任务
  • worker:执行任务

period_task.py:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from project import app
from celery.schedules import crontab

@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(10.0, add.s(1,3), name='1+3=') # 每10秒执行add
    sender.add_periodic_task(
        crontab(hour=16, minute=56, day_of_week=1),      #每周一下午四点五十六执行sayhai
        sayhi.s('wd'),name='say_hi'
    )

@app.task
def add(x,y):
    print(x+y)
    return x+y


@app.task
def sayhi(name):
    return 'hello %s' % name

config.py

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd

BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件

CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis

CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案

CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间

CELERY_TIMEZONE='Asia/Shanghai'   # 时区配置

CELERY_IMPORTS = (     # 指定导入的任务模块,可以指定多个
    'project.tasks',
    'project.period_task', #定时任务
)

启动Worker和beat:

celery worker -A project -l debug #启动work
celery beat -A  project.period_task -l  debug #启动beat,注意此时对应的文件路径

或者通过配置文件方式指定定时和计划任务,此时的配置文件如下:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd

from project import app
from celery.schedules import crontab

BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件

CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis

CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案

CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24 # 任务过期时间

CELERY_TIMEZONE='Asia/Shanghai'   # 时区配置

CELERY_IMPORTS = (     # 指定导入的任务模块,可以指定多个
    'project.tasks',
    'project.period_task',
)

app.conf.beat_schedule = {
    'period_add_task': {    # 计划任务
        'task': 'project.period_task.add',  #任务路径
        'schedule': crontab(hour=18, minute=16, day_of_week=1),
        'args': (3, 4),
    },
    'add-every-30-seconds': {          # 每10秒执行
            'task': 'project.period_task.sayhi',  #任务路径
            'schedule': 10.0,
            'args': ('wd',)
        },
}

此时的period_task.py只需要注册到woker中就行

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from project import app

@app.task
def add(x,y):
    print(x+y)
    return x+y


@app.task
def sayhi(name):
    return 'hello %s' % name

同样启动worker和beat结果和第一种方式一样。

更多详细的内容请参考:http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html#crontab-schedules

  • 执行异步任务

    # 代码用法:
    函数名.delay('函数执行需要的参数')
    res = func.delay(*args,**kwargs)   # res 用于接收提交任务的ID
    
  • 执行延迟任务

    # 代码用法:
    # 1、执行延迟任务
    from datetime import datetime, timedelta
    
    # 设置延迟后的时间,一分钟后执行
    eat = datetime.utcnow() + timedelta(minutes=1)
    
    # 提交任务
    res = send_sms.apply_async(args=['13855411111', '123'], eta=eta)
    

django中使用celery

Django的请求处理过程都是同步的无法实现异步任务,若要实现异步任务需要通过其他方式。

补充:

如果在公司中,只做定时任务可以使用更简单一点的框架

APSchedule:https://blog.csdn.net/qq_41341757/article/details/118759836

请求过程简单说明:浏览器发起请求 --> 请求处理 --> 请求经过中间件 ---> 路由映射 --> 视图处理业务逻辑 ---> 响应请求(template或Response)

配置使用

celery很容易集成到Django框架中,要实现定时任务还需要安装django-celery-beta插件,需要注意的是Celery4.0只支持Django版本>=1.8的,如果是小于1.8版本需要使用Celery3.1。

配置

新建taskproj,目录结构(每个app下多了一个tasks文件,用于定义任务)

taskproj
├── app01
│   ├── __init__.py
│   ├── apps.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tasks.py
│   └── views.py
├── manage.py
├── taskproj
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── templates

在项目目录taskproj/taskproj/目录下新建celery.py:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Author:wd
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'taskproj.settings')  # 设置django环境

app = Celery('taskproj')

app.config_from_object('django.conf:settings', namespace='CELERY') #  使用CELERY_ 作为前缀,在settings中写配置

app.autodiscover_tasks()  # 发现任务文件每个app下的task.py

taskproj/taskproj/init.py:

from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
__all__ = ['celery_app']

taskproj/taskproj/settings.py

CELERY_BROKER_URL = 'redis://10.1.210.69:6379/0' # Broker配置,使用Redis作为消息中间件

CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis

CELERY_RESULT_SERIALIZER = 'json' # 结果序列化方案

进入项目的taskproj目录启动worker:

celery worker -A taskproj -l debug

定义与触发任务

任务定义在每个tasks文件中,app01/tasks.py:

from __future__ import absolute_import, unicode_literals
from celery import shared_task
from taskproj import celery_app

@celery_app.task(bind=True)
def add(self, x, y):
    return x + y


@shared_task
def mul(x, y):
    return x * y

视图中触发任务

from django.http import JsonResponse
from app01 import tasks

# Create your views here.

def index(request,*args,**kwargs):
    res=tasks.add.delay(1,3)
    #任务逻辑
    return JsonResponse({'status':'successful','task_id':res.task_id})

若想获取任务结果,可以通过task_id使用AsyncResult获取结果,还可以直接通过backend获取:

img

模拟写一个异步秒杀任务

# view.py
from celery.result import AsyncResult
from celery_task.celery import app
from celery_task.task import sckill_task


# 秒杀接口
class SeckillView(ViewSet):

    # 开启秒杀
    @action(methods=['GET'], detail=False)
    def seckill(self, request):
        # 获取商品链接
        goods_id = request.query_params.get('goods_id')
        # 将任务提交给worker
        res = sckill_task.delay(goods_id)
        # 将任务的ID反馈给前端
        return APIResponse(task_id=str(res))

    # 查询秒杀结果
    @action(methods=['GET'], detail=False)
    def get_result(self, request):
        # 前端将任务ID产过来,用于接收结果
        task_id = request.query_params.get('task_id')
        # 调用接口,查询结果
        a = AsyncResult(id=task_id, app=app)
        if a.successful():
            result = a.get()
            if result:
                return APIResponse(msg='秒杀成功')
            else:
                return APIResponse(code=101, msg='手速满了,秒杀失败')
        elif a.status == 'PENDING':
            return APIResponse(code=666, msg='加速秒杀中')
        return APIResponse(msg='错误')

celery.py

import random

# 秒杀函数
@app.task()
def sckill_task(goods_id):
    print('商品正在秒杀中')
    time.sleep(random.choice([6, 7, 8, 9]))
    print('商品秒杀结束')
    return random.choice([True, False])

总结

第一步:将celery包复制到项目路径下

-luffy_api
    -celery_task #celery的包路径
    celery.py  # 一定不要忘了一句话
        import os
         # 重点:celery中使用djagno,任务中可能会使用django的orm,缓存,表模型。。。。一定要加
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'luffy_api.settings.dev')
        -luffy_api  #源代码路径

第二步:在需要使用异步的地方导入celery实例即可使用

-视图函数中使用,导入任务
-任务.delay()  # 提交任务

第三步:启动worker,如果有定时任务,启动beat

第四步: 等待任务被worker执行

第五步:在视图函数中,查询任务结果

扩展

除了redis,RabbitMq能做结果存储外,还可以使用Django的orm作为结果存储,当然需要安装依赖插件,这样的好处在于我们可以直接通过Django的数据查看到任务状态,同时为可以定制更多的操作,下面介绍如何使用orm作为结果存储

  1. 安装

    pip install django-celery-results
    
  2. 配置settings.py 注册app

    INSTALLED_APPS = (
        ...,
        'django_celery_results',
    )
    
  3. 修改backend配置,将redis改为django-db

    #CELERY_RESULT_BACKEND = 'redis://10.1.210.69:6379/0' # BACKEND配置,这里使用redis
    
    CELERY_RESULT_BACKEND = 'django-db'  #使用django orm 作为结果存储
    
  4. 修改数据库

    python3 manage.py migrate django_celery_results
    

    此时会看到数据库会多创建django_celery_results_taskresult

  5. 当然你有时候需要对task表进行操作,以下源码的表结构定义:

    class TaskResult(models.Model):
        """Task result/status."""
    
        task_id = models.CharField(_('task id'), max_length=255, unique=True)
        task_name = models.CharField(_('task name'), null=True, max_length=255)
        task_args = models.TextField(_('task arguments'), null=True)
        task_kwargs = models.TextField(_('task kwargs'), null=True)
        status = models.CharField(_('state'), max_length=50,
                                  default=states.PENDING,
                                  choices=TASK_STATE_CHOICES
                                  )
        content_type = models.CharField(_('content type'), max_length=128)
        content_encoding = models.CharField(_('content encoding'), max_length=64)
        result = models.TextField(null=True, default=None, editable=False)
        date_done = models.DateTimeField(_('done at'), auto_now=True)
        traceback = models.TextField(_('traceback'), blank=True, null=True)
        hidden = models.BooleanField(editable=False, default=False, db_index=True)
        meta = models.TextField(null=True, default=None, editable=False)
    
        objects = managers.TaskResultManager()
    
        class Meta:
            """Table information."""
    
            ordering = ['-date_done']
    
            verbose_name = _('task result')
            verbose_name_plural = _('task results')
    
        def as_dict(self):
            return {
                'task_id': self.task_id,
                'task_name': self.task_name,
                'task_args': self.task_args,
                'task_kwargs': self.task_kwargs,
                'status': self.status,
                'result': self.result,
                'date_done': self.date_done,
                'traceback': self.traceback,
                'meta': self.meta,
            }
    
        def __str__(self):
            return '<Task: {0.task_id} ({0.status})>'.format(self)
    

配置定时任务

settings.py中配置

CELERY_BEAT_SCHEDULE = {
    # 'check-application': {
    #     'task': 'application.tasks.get_api',
    #     'schedule': timedelta(days=7),
    # },
    'check-server-alarm': {
        'task': 'application.tasks.service_auto_start',
        'schedule': timedelta(minutes=1)
    },
    'auto_calcu_app_stats': {
        'task': 'application.tasks.auto_calcu_app_stats',
        'schedule': crontab(hour='0', minute='30'),
    },
    'auto_calcu_api_stats': {
        'task': 'application.tasks.auto_calcu_api_stats',
        'schedule': crontab(hour='0', minute='30'),
    },
    'auto_calcu_server_stats': {
        'task': 'application.tasks.auto_calcu_server_stats',
        'schedule': crontab(hour='0', minute='30'),
    }
}
posted @ 2025-04-23 22:41  小郑[努力版]  阅读(45)  评论(0)    收藏  举报