celery

celery

celery框架来实现异步任务,分布式任务调度框架。

它有几个主要的概念:

  • celery应用
    • 用户编写的代码脚本,用来定义要执行的任务,然后通过broker将任务发送到消息队列
  • broker
    • 代理,通过消息队列在客户端和worker之前进行协调
    • celery本身不包含消息队列,它支持以下消息队列
      • RabbitMQ
      • Redis
  • worker
    • 工人,用来执行broker分别任务的进程
  • 任务
    • 任务,定义的需要执行的任务

安装
pip install celery

安装依赖
pip install "celery[redis,eventlet]"

简单的使用

1. 选择一个broker

首先要选择一个消息队列,安装任意你熟悉的前面提到的celery支持的消息队列。

以redis为例,启动一个redis的容器。(云服务器)

  1. 首先写一个配置文件,为reids设置密码,不设置密码在云服务器上redis就是裸奔
cd ~
echo "requirepass pythonvip" > redis.conf
  1. 下载一个redis的镜像
docker pull redis:alpine
  1. 启动一个容器
docker run -v /root/redis.conf:/usr/local/etc/redis/redis.conf -p 4002:6379 -d --name test_redis redis:alpine redis-server /usr/local/etc/redis/redis.conf

2.编写celery应用

用来创建任务和管理worker是,它要能够被其他的模块导入。

创建一个tasks.py文件,内容如下:

from celery import Celery

app = Celery('tasks', broker='redis://:pythonvip@101.200.88.234:4002/0')

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

第一个参数 tasks 是当前模块的名称,它可以省略,建议以当前模块名为名称。

第二个关键字参数 broker='redis://localhost:6379/0' 指定我们使用 Redis 作为消息队列,并指定连接地址。

3. 运行woker服务

cd到tasks.py文件所在的目录,然后通过下面的命令来启动worker服务

celery -A tasks worker -l INFO -P eventlet

4. 调度任务

from tasks import add
add.delay(4,4)

通过调用任务的 delay 来执行对应的任务。celery 会把执行命令发送到 broker,broker 再将消息发送给 worker 服务来执行,如果一切正常你将会在 worker 服务的日志中看到接收任务和执行任务的日志。

5. 保存结果

如果需要跟踪任务的结果,需要配置结果后端backend

在创建app的时候加上结果后端的参数

app = Celery('tasks',
             broker='redis://:pythonvip@www.hhxpython.com:4001/0',
             backend='redis://:pythonvip@www.hhxpython.com:4001/1'
             )

更多结果后端见官方文档

重新启动 worker 服务,重新打开 python 解释器

from tasks import add
result = add.delay(4,4) 

ready() 方法返回任务是否执行完成:

result.ready()
False

还可以等待结果完成,但很少使用这种方法,因为它将异步调用转换为同步调用

result.get(timeout=1)
8

在应用中使用celery

创建项目

项目结构:

proj/__init__.py
    /celery.py
    /tasks.py
# proj/celery.py
from celery import Celery

app = Celery('proj',
             broker='redis://localhost:6379/0',
             backend='redis://localhost:6379/1',
             include=['proj.tasks'])

# 配置
app.conf.update(
    result_expires=3600, # 结果过期时间
)

在这个模块中我们创建了一个 Celery 模块。要在你的项目中使用 celery 只需要导入此实例。

# proj/tasks.py
from .celery import app

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

@app.task
def mul(x, y):
    return x * y

@app.task
def xsum(numbers):
    return sum(numbers)

启动 worker

celery -A tasks worker -l INFO -P eventlet

调用任务

from proj.tasks import add
add.delay(2, 2)

在django中使用celery

1. 创建celery应用

如果你有django项目如下:

- proj/
  - manage.py
  - proj/
    - __init__.py
    - settings.py
    - urls.py

推荐的方法是创建一个新的proj/proj/celery.py模块来定义celery应用:

# proj/proj/celery.py
import os
from celery import Celery

# 为celery设置默认的django设置模块,加载当前 django 项目的环境设置
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')

# 创建一个celery实例
app = Celery('proj')

# 设置配置来源
app.config_from_object('django.conf:settings', namespace='CELERY')

# 加载所有的已注册django应用中的任务
app.autodiscover_tasks()

为了保证django项目启动的时候加载celery应用程序,以便于后面自动加载任务。

# proj/proj/__init__.py
from .celery import app as celery_app
__all__ = ('celery_app', )

配置

# proj/settings.py
...
# celery配置
CELERY_TIMEZONE = "Asia/Shanghai"
CELERY_BROKER_URL = 'redis://:pythonvip@101.200.88.234:4002/0'
CELERY_RESULT_BACKEND = 'redis://:pythonvip@101.200.88.234:4002/1'
CELERY_TASK_SERIALIZER = 'pickle'  # 指定序列化的方式,纯python项目建议使用pickle
CELERY_RESULT_SERIALIZER = 'pickle'
CELERY_ACCEPT_CONTENT = ['pickle']  # 指定信任内容格式
CELERY_WORKER_HIJACK_ROOT_LOGGER = False
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'  # 配置django_celery_beat的调度程序

Celery 将按照 tasks.py 约定自动从所有已安装的应用程序中发现任务:

- app1/
    - tasks.py
    - models.py
- app2/
    - tasks.py
    - models.py

2. 创建任务

app1/tasks.py

from celery import shared_task
from .models import TestCase

@shared_task
def add(x, y):
    return x + y

@shared_task
def search():
    return TestCase.objects.all()

3.启动woker

在proj/目录下运行

celery -A tasks worker -l INFO -P eventlet

4. 调用任务

from app1.tasks import add
add.delay(2, 2)

保存结果在django的数据库参考https://docs.celeryproject.org/en/stable/django/first-steps-with-django.html#django-celery-results

celery定时任务

celery beat 是一个调度程序,它会定时启动任务,然后再交由woker去执行这些任务。

配置定时任务

# tasks.py
from celery import Celery
from celery.schedules import crontab

app = Celery('tasks', broker='redis://:pythonvip@101.200.88.234:4002/0')
app.conf.timezone = 'Asia/Shanghai'  # 设置时区
app.conf.enable_utc = True

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

app.conf.beat_schedule = {
    'time_10_seconds': {
        'task': 'tasks.add',
        'schedule': 5,  # 每隔5秒执行一次
        'args': (1, 3)
    },
    'time_hour': {
        'task': 'tasks.add',  # 任务
        'schedule': crontab(hour=21, minute=15),  # 21:15时执行
        'args': (4, 4)  # 传给任务的参数
    },
}

启动woker

tasks.py所在目录执行命令:

celery -A tasks worker -l INFO -P eventlet

启动调度器

启动celery beat服务命令如下:

celery -A tasks beat -l info

beat服务会根据定义的时间策略去发送对应的任务到消息队列交由空闲的worker去执行。

所以beat服务会维护一时间策略表存储在本地数据库文件中,还有任务最后一次执行的时间。

定时计划crontab

如果您想要更多地控制任务何时执行,例如,一天中的特定时间或一周中的某一天,您可以使用crontab调度类型,这些Crontab表达式的语法非常灵活。

案例 解释
crontab() 每分钟执行
crontab(minute=0, hour=0) 每天的0点执行
crontab(minute=0, hour='*/3') 每3小时执行: 0点, 3点, 6点, 9点, 12点, 15点, 18点, 21点.
crontab(minute=0,hour='0,3,6,9,12,15,18,21') 同上
crontab(minute='*/15') 每15分钟执行
crontab(day_of_week='sunday') 星期天每分钟执行一次
crontab(minute='*',``hour='*', day_of_week='sun') 同上
crontab(minute='*/10',``hour='3,17,22', day_of_week='thu,fri') 每十分钟执行一次, 但只在星期四或星期五的3-4点,17-18点,22-23点之间
crontab(minute=0, hour='*/2,*/3') 每个能被2或3整除的小时执行。这意味着:每个小时执行一次,除了1点,5点,7点,11点,13点,17点,19点,23点。
crontab(minute=0, hour='*/5') 每个能被5整除的小时执行。0点,5点,10点,15点,20点
crontab(minute=0, hour='*/3,8-17') 每个能被3整除的小时执行和工作期间(8-17)每小时执行。
crontab(0, 0, day_of_month='2') 每个月的第二天执行
crontab(0, 0,``day_of_month='1-7,15-21') 每个月的第一周和第三周执行。
crontab(0, 0, day_of_month='11',``month_of_year='5') 每年的5月11号执行
crontab(0, 0,``month_of_year='*/3') 每季度的第一天执行

更多内容请参加文档

django使用定时任务

django-celery-beat

celery默认使用的调度程序是celery.beat.PersistentScheduler.

dcb会将数据存储在django的数据中,提供了一个方便的管理界面来运行管理周期性任务。

安装django-celery-beat

pip install django-celery-beat

注册

INSTALLED_APPS = [
  'django_celery_beat', 
  .....
]

数据库迁移

python .\manage.py migrate

启动worker

celery -A TestProject worker -l INFO -P eventlet  # TestProject为django工程目录的名称

启动调度器

celery -A TestProject beat -l info  # TestProject为django工程目录的名称

创建任务

方式一:diango-admin配置

进入django-admin后台,配置Periodic tasks

方式二:自定义创建定时

复写django-celery-beat,通过创建crontab的时间策略,然后在periodic_task中创建定时任务来自定义

创建tasks.py

from celery import shared_task

@shared_task
def async_case(x,y):
  return x + y

创建定时任务的模型models.py

from django.db import models

class CrontabTask(models.Model):
     c_time = models.DateTimeField('创建时间', help_text='创建时间', auto_now_add=True)
    u_time = models.DateTimeField('更新时间', help_text='更新时间', auto_now=True)
    crontab = models.CharField('定时规则', max_length=128, help_text='定时规则,例如:* * * * *')
    enabled = models.BooleanField(default=True, verbose_name='是否启用', help_text='True启用、False停用')
    version = models.CharField('版本号', max_length=48, help_text='版本号')
    periodic_task = models.OneToOneField('django_celery_beat.PeriodicTask', on_delete=models.PROTECT, **** null=True,blank=True)

    class Meta:
        db_table = 'tb_crontab_task'
        verbose_name = '定时任务'
        verbose_name_plural = verbose_name
        ordering = ['id']

创建定时任务对应的序列化器serializers.py

import datetime
from rest_framework import serializers
from django_celery_beat.models import CrontabSchedule, PeriodicTask
from . import models

class CrontabTaskSerializer(serializers.ModelSerializer):
    """定时任务创建序列化器"""

    class Meta:
        model = models.CrontabTask
        exclude = ['is_delete', 'u_time', 'periodic_task']  # 排除字段
        
    def validate_crontab(self, value):
        """校验定时规则的格式"""
        value = value.strip()  # 去掉前后空格
        if len(value.split(' ')) != 5:  # 去掉空格后长度不为5则报错
            raise serializers.ValidationError('定时规则字符串格式错误')
        return value

    def get_periodic_task(self):
        """获取或者创建一个django-celery-beat中的periodic_task对象"""
        # 创建或获取一个crontab时间策略
        crontab = self.get_crontab()
        # 创建定时任务periodic task
        name = f"{self.validated_data['task'].name}_{datetime.datetime.now()}"  # 任务名字
        enabled = self.validated_data.get('enabled') or False  # 任务的状态
        kwargs = {  # 传递给定时任务的参数         
            'version': self.validated_data['version'],
            'x': '',
            'y': ''
        }
        return PeriodicTask.objects.create(
            name=name, crontab=crontab, task="tasks.async_case", kwargs=json.dumps(kwargs), enabled=enabled
        )

    def get_crontab(self):
        """获取或者创建一个django-celery-beat中的crontab对象"""
        # 提取crontab并根据' '分割,然后赋值
        minute,hour,day_of_week,day_of_month,month_of_year = self.validated_data.get('crontab').split(' ')
        # 查询是否有已经存在的时间策略
        queryset = CrontabSchedule.objects.filter(minute=minute, hour=hour, day_of_week=day_of_week, day_of_month=day_of_month, month_of_year=month_of_year)
        if queryset:  # 如果存在直接返回
            crontab = queryset.first()
        else:  # 不存在则创建
            crontab = CrontabSchedule.objects.create(
                minute=minute, hour=hour, day_of_week=day_of_week, day_of_month=day_of_month,
                month_of_year=month_of_year
            )
        return crontab

创建定时任务对应的视图views.py

from django.db import transaction
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from . import models, serializers

class CrontabTaskViewSet(ModelViewSet):
    """定时任务视图集"""
    queryset = models.CrontabTask.objects.all()
    serializer_class = serializers.CrontabTaskSerializer

    def get_serializer_class(self):
        # 根据方法使用不同的序列化器
        if self.action in ('list', 'retrieve'):
            return serializers.CrontabTaskListSerializer
        elif self.action == 'update':
            return serializers.CrontabTaskUpdateSerializer
        else:
            return self.serializer_class

    def perform_create(self, serializer):
        """
        创建定时任务,在django-celery-beat中创建时间策略crontab,然后创建定时任务periodic task
        """
        try:
            with transaction.atomic():  # 开启事务
                #  1. 创建或者是获取一个periodic_task
                periodic_task = serializer.get_periodic_task()
                #  2. 保存当前的定时任务
                serializer.save(periodic_task=periodic_task)
        except Exception as e:
            return Response({'msg': '创建失败', 'error': str(e)}, status=400)

    def perform_update(self, serializer):
        """修改定时任务,修改CrontabTask和periodic_task的enabled字段"""
        try:
            with transaction.atomic():
                instance = serializer.save()  # instance为当前的CrontabTask
                periodic_task = instance.periodic_task
                periodic_task.enabled = instance.enabled
                periodic_task.save()
        except Exception as e:
            return Response({'msg': '修改失败', 'error': str(e)}, status=400)

    def perform_destroy(self, instance):
        """删除定时任务,删除CrontabTask和periodic_task数据"""
        try:
            with transaction.atomic():
                periodic_task = instance.periodic_task
                instance.delete()
                periodic_task.delete()
        except Exception as e:
            return Response({'msg': '删除失败', 'error': str(e)}, status=400)
        
    @action(detail=True, methods=['POST'])
    # @action为当前视图集添加run方法, detail=True方法run传pk时需要设置
    # methods此方法支持的请求类型, url_path定义此操作的url段,默认为方法的名称(/tasks/<pk>/run)
    def run(self, request, pk):
        """执行测试任务"""
        async_case.delay(3,5)  # 异步执行任务              

配置路由urls.py

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views

router = DefaultRouter()
router.register('crontabtasks', views.CrontabTaskViewSet)

urlpatterns = [
    path('', include(router.urls))
]
posted @ 2022-07-20 14:46  北京测试菜鸟  阅读(84)  评论(0编辑  收藏  举报