zoukankan      html  css  js  c++  java
  • Celery架构

    Celery

    官方

    # Celery 官网:http://www.celeryproject.org/
    
    # Celery 官方文档英文版:http://docs.celeryproject.org/en/latest/index.html
    
    # Celery 官方文档中文版:http://docs.jinkan.org/docs/celery/

    cerely是什么?

      cerely被用来稍后执行某些代码,或者调度器调度这些代码。

    Celery架构

      Celery的架构由三部分组成,消息中间件(message broker)、任务执行单元(worker)和 任务执行结果存储(backend - task result store)组成。

    消息中间件

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

    任务执行单元

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

    任务结果存储

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

    总结:

    """
    1、celery框架自带socket,所以自身是一个独立运行的服务
    2、启动celery服务,是来执行服务中的任务的,服务中带一个执行任务的对象,会执行准备就绪的任务,将执行任务的结果保存起来
    3、celery框架由三部分组成:存放要执行的任务broker,执行任务的对象worker,存放任务结果的backend
    4、安装的celery主体模块,默认只提供worker,要结合其他技术提供broker和backend(两个存储的单位)
    """

    工作的基本原理

    1、准备配置了broker与backend的worker(任务的来源),并启动。

    2、添加任务到broker,worker就会执行任务,将结果存储到backend中。

    3、想查看任务的执行结果,根据任务的id去bckend中查询。

    使用场景

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

      定时任务:定时执行某件事情,比如每天数据统计

    Celery的安装配置

    pip install celery
    
    消息中间件:RabbitMQ/Redis
    
    app=celery.Celery('任务名', broker='xxx', backend='xxx', include=['xxx', 'xxx'])

    Celery执行异步任务

    包架构封装

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

    基本使用

    celery.py

    # 1)创建app + 任务
    
    # 2)启动celery(app)服务:
    # 非windows
    # 命令:celery worker -A celery_task -l info
    # windows:
    # pip3 install eventlet
    # celery worker -A celery_task -l info -P eventlet
    
    # 3)添加任务:手动添加,要自定义添加任务的脚本,右键执行脚本
    
    # 4)获取结果:手动获取,要自定义获取任务的脚本,右键执行脚本
    
    
    from celery import Celery
    
    broker = 'redis://127.0.0.1:6379/1'
    backend = 'redis://127.0.0.1:6379/2'
    app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
    from celery import Celery
    
    # 通过Celery功能产生一个celery应用
    broker = 'redis://127.0.0.1:6379/14'  # 任务仓库
    backend = 'redis://127.0.0.1:6379/15'  # 结果仓库
    include = ['celery_task.task1', 'celery_task.task2']  # 任务们,完成需求的函数所在的文件
    app = Celery(broker=broker, backend=backend, include=include)
    
    # 启动worker:celery worker -A celery_task -l info -P eventlet
    # 手动添加任务

    celery内部的源码__init__

     tasks.py

    from .celery import app
    import time
    @app.task
    def add(n, m):
        print(n)
        print(m)
        time.sleep(10)
        print('n+m的结果:%s' % (n + m))
        return n + m
    
    @app.task
    def low(n, m):
        print(n)
        print(m)
        print('n-m的结果:%s' % (n - m))
        return n - m

    add_task.py

    from celery_task import tasks
    
    # 添加立即执行任务
    t1 = tasks.add.delay(10, 20)
    t2 = tasks.low.delay(100, 50)
    print(t1.id)
    
    
    # 添加延迟任务
    from datetime import datetime, timedelta
    def eta_second(second):
        ctime = datetime.now()
        utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
        time_delay = timedelta(seconds=second)
        return utc_ctime + time_delay
    
    tasks.low.apply_async(args=(200, 50), eta=eta_second(10))

    get_result.py

    from celery_task.celery import app
    
    from celery.result import AsyncResult
    
    id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
    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('任务已经开始被执行')

    高级使用

    celery.py
    # 1)创建app + 任务
    # 2)启动celery(app)服务:
    # 非windows
    # 命令:celery worker -A celery_task -l info
    # windows:
    # pip3 install eventlet
    # celery worker -A celery_task -l info -P eventlet
    # 3)添加任务:自动添加任务,所以要启动一个添加任务的服务
    # 命令:celery beat -A celery_task -l info
    # 4)获取结果
    ​
    ​
    from celery import Celery
    ​
    broker = 'redis://127.0.0.1:6379/1'
    backend = 'redis://127.0.0.1:6379/2'
    app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
    ​
    ​
    # 时区
    app.conf.timezone = 'Asia/Shanghai'
    # 是否使用UTC
    app.conf.enable_utc = False
    ​
    # 任务的定时配置
    from datetime import timedelta
    from celery.schedules import crontab
    app.conf.beat_schedule = {
        'low-task': {
            'task': 'celery_task.tasks.low',
            'schedule': timedelta(seconds=3),
            # 'schedule': crontab(hour=8, day_of_week=1),  # 每周一早八点
            'args': (300, 150),
        }
    }

    tasks.py

    from .celery import app
    ​
    import time
    @app.task
    def add(n, m):
        print(n)
        print(m)
        time.sleep(10)
        print('n+m的结果:%s' % (n + m))
        return n + m
    ​
    ​
    @app.task
    def low(n, m):
        print(n)
        print(m)
        print('n-m的结果:%s' % (n - m))
        return n - m

    get_result.py

    from celery_task.celery import app
    ​
    from celery.result import AsyncResult
    ​
    id = '21325a40-9d32-44b5-a701-9a31cc3c74b5'
    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('任务已经开始被执行')
     

    django中使用

    celery.py

    # 重点:要将 项目名.settings 所占的文件夹添加到环境变量
    # import sys
    # sys.path.append(r'项目绝对路径')
    # 开启django支持
    import os
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名.settings')
    import django
    django.setup()
    ​
    ​
    ​
    # 1)创建app + 任务
    # 2)启动celery(app)服务:
    # 非windows
    # 命令:celery worker -A celery_task -l info
    # windows:
    # pip3 install eventlet
    # celery worker -A celery_task -l info -P eventlet
    # 3)添加任务:自动添加任务,所以要启动一个添加任务的服务
    # 命令:celery beat -A celery_task -l info
    # 4)获取结果
    ​
    ​
    from celery import Celery
    ​
    broker = 'redis://127.0.0.1:6379/1'
    backend = 'redis://127.0.0.1:6379/2'
    app = Celery(broker=broker, backend=backend, include=['celery_task.tasks'])
    ​
    ​
    # 时区
    app.conf.timezone = 'Asia/Shanghai'
    # 是否使用UTC
    app.conf.enable_utc = False
    ​
    # 任务的定时配置
    from datetime import timedelta
    from celery.schedules import crontab
    app.conf.beat_schedule = {
        'django-task': {
            'task': 'celery_task.tasks.test_django_celery',
            'schedule': timedelta(seconds=3),
            'args': (),
        }
    }

    tasks.py

    from .celery import app
    ​
    from home.models import Banner
    from settings.const import BANNER_COUNT  # 轮播图最大显示条数
    from home.serializers import BannerModelSerializer
    from django.core.cache import cache
    @app.task
    def update_banner_list():
        # 获取最新内容
        banner_query = Banner.objects.filter(is_delete=False, is_show=True).order_by('-orders')[:BANNER_COUNT]
        # 序列化
        banner_data = BannerModelSerializer(banner_query, many=True).data
        for banner in banner_data:
            banner['image'] = 'http://127.0.0.1:8000' + banner['image']
        # 更新缓存
        cache.set('banner_list', banner_data)
        return True
     

    celery异步任务提交框架

      安装:

    pip install celery

    使用消息中间件:

    RabbitMQ/Redis

    创建任务:

    app=Celery('任务名',backend='xxx',broker='xxx')

    基本使用

    import celery
    import time
    # broker='redis://127.0.0.1:6379/2' 不加密码
    backend='redis://:123456@127.0.0.1:6379/1'
    broker='redis://:123456@127.0.0.1:6379/2'
    cel=celery.Celery('test',backend=backend,broker=broker)
    @cel.task
    def add(x,y):
        return x+y

    框架结构

    复制代码
    pro_cel
        ├── celery_task# celery相关文件夹
        │   ├── celery.py   # celery连接和配置相关文件,必须叫这个名字
        │   └── tasks1.py    #  所有任务函数
        │    └── tasks2.py    #  所有任务函数
        ├── check_result.py # 检查结果
        └── send_task.py    # 触发任务
    复制代码

    手动添加任务

    from celery_app_task import add
    result = add.delay(4,5)
    print(result.id)

    result查看结果

    from celery.result import AsyncResult
    from celery_app_task import cel
    
    async = AsyncResult(id="e919d97d-2938-4d0f-9265-fd8237dc2aa3", app=cel)
    
    if async.successful():
        result = async.get()
        print(result)
        # result.forget() # 将结果删除
    elif async.failed():
        print('执行失败')
    elif async.status == 'PENDING':
        print('任务等待中被执行')
    elif async.status == 'RETRY':
        print('任务异常后正在重试')
    elif async.status == 'STARTED':
        print('任务已经开始被执行')

    定时任务

    from celery_app_task import add
    from datetime import datetime
    
    # 方式一
    # v1 = datetime(2019, 2, 13, 18, 19, 56)
    # print(v1)
    # v2 = datetime.utcfromtimestamp(v1.timestamp())
    # print(v2)
    # result = add.apply_async(args=[1, 3], eta=v2)
    # print(result.id)
    
    # 方式二
    ctime = datetime.now()
    # 默认用utc时间
    utc_ctime = datetime.utcfromtimestamp(ctime.timestamp())
    from datetime import timedelta
    time_delay = timedelta(seconds=10)
    task_time = utc_ctime + time_delay
    
    # 使用apply_async并设定时间
    result = add.apply_async(args=[4, 3], eta=task_time)
    print(result.id)

    循环定时提交任务

    from datetime import timedelta
    from celery import Celery
    from celery.schedules import crontab
    
    cel = Celery('tasks', broker='redis://127.0.0.1:6379/1', backend='redis://127.0.0.1:6379/2', include=[
        'celery_task.tasks1',
        'celery_task.tasks2',
    ])
    cel.conf.timezone = 'Asia/Shanghai'
    cel.conf.enable_utc = False
    
    cel.conf.beat_schedule = {
        # 名字随意命名
        'add-every-10-seconds': {
            # 执行tasks1下的test_celery函数
            'task': 'celery_task.tasks1.test_celery',
            # 每隔2秒执行一次
            # 'schedule': 1.0,
            # 'schedule': crontab(minute="*/1"),
            'schedule': timedelta(seconds=2),
            # 传递参数
            'args': ('test',)
        },
        # 'add-every-12-seconds': {
        #     'task': 'celery_task.tasks1.test_celery',
        #     每年4月11号,8点42分执行
        #     'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
        #     'schedule': crontab(minute=42, hour=8, day_of_month=11, month_of_year=4),
        #     'args': (16, 16)
        # },
    }

    使用 Celery Once 来防止 Celery 重复执行同一个任务

    在使用 Celery 的时候发现有的时候 Celery 会将同一个任务执行两遍,我遇到的情况是相同的任务在不同的 worker 中被分别执行,并且时间只相差几毫秒。这问题我一直以为是自己哪里处理的逻辑有问题,后来发现其他人 也有类似的问题,然后基本上出问题的都是使用 Redis 作为 Broker 的,而我这边一方面不想将 Redis 替换掉,就只能在 task 执行的时候加分布式锁了。

    不过在 Celery 的 issue 中搜索了一下,有人使用 Redis 实现了分布式锁,然后也有人使用了 Celery Once。 大致看了一下 Celery Once ,发现非常符合现在的情况,就用了下。

    Celery Once 也是利用 Redis 加锁来实现, Celery Once 在 Task 类基础上实现了 QueueOnce 类,该类提供了任务去重的功能,所以在使用时,我们自己实现的方法需要将 QueueOnce 设置为 base

    @task(base=QueueOnce, once={'graceful': True})

    后面的 once 参数表示,在遇到重复方法时的处理方式,默认 graceful 为 False,那样 Celery 会抛出 AlreadyQueued 异常,手动设置为 True,则静默处理。

    另外如果要手动设置任务的 key,可以指定 keys 参数

    @celery.task(base=QueueOnce, once={'keys': ['a']})
    def slow_add(a, b):
      sleep(30)
      return a + b

    总得来说,分为几步

    第一步,安装
    pip install -U celery_once
    第二步,增加配置
    from celery import Celery
    from celery_once import QueueOnce
    from time import sleep

    celery = Celery('tasks', broker='amqp://guest@localhost//')
    celery.conf.ONCE = {
    'backend': 'celery_once.backends.Redis',
    'settings': {
      'url': 'redis://localhost:6379/0',
      'default_timeout': 60 * 60
    }
    }
    第三步,修改 delay 方法
    example.delay(10)
    # 修改为
    result = example.apply_async(args=(10))
    第四步,修改 task 参数
    @celery.task(base=QueueOnce, once={'graceful': True, keys': ['a']})
    def slow_add(a, b):
      sleep(30)
      return a + b

    参考链接 https://github.com/cameronmaske/celery-once

    
    

     

     

     

     

     

  • 相关阅读:
    HTTP Status 500
    HTTP Status 500
    HTTP Status 500
    测试错误ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.问题的解决
    页面报错误:HTTP Status 500
    eclipse怎么设置在新建JSP文件的编码为UTF-8?
    linux 开启oracle监听
    linux 修改环境变量
    linux 修改oracle的字符集
    Cannot change version of project facet Dynamic Web Module to 2.5
  • 原文地址:https://www.cnblogs.com/Gaimo/p/11774506.html
Copyright © 2011-2022 走看看