zoukankan      html  css  js  c++  java
  • Celery

    在程序的运行过程中,我们经常会碰到一些耗时耗资源的操作,为了避免它们阻塞主程序的运行,我们经常会采用多线程或异步任务。比如,在 Web 开发中,对新用户的注册,我们通常会给他发一封激活邮件,而发邮件是个 IO 阻塞式任务,如果直接把它放到应用当中,就需要等邮件发出去之后才能进行下一步操作,此时用户只能等待再等待。更好的方式是在业务逻辑中触发一个发邮件的异步任务,而主程序可以继续往下运行。

    Celery 是一个强大的分布式任务队列,它可以让任务的执行完全脱离主程序,甚至可以被分配到其他主机上运行。我们通常使用它来实现异步任务(async task)和定时任务(crontab)。它的架构组成如下图:

    可以看到,Celery 主要包含以下几个模块:

    • 任务模块

      包含异步任务和定时任务。其中,异步任务通常在业务逻辑中被触发并发往任务队列,而定时任务由 Celery Beat 进程周期性地将任务发往任务队列。

    • 消息中间件 Broker

      Broker,即为任务调度队列,接收任务生产者发来的消息(即任务),将任务存入队列。Celery 本身不提供队列服务,官方推荐使用 RabbitMQ 和 Redis 等。

    • 任务执行单元 Worker

      Worker 是执行任务的处理单元,它实时监控消息队列,获取队列中调度的任务,并执行它。

    • 任务结果存储 Backend

      Backend 用于存储任务的执行结果,以供查询。同消息中间件一样,存储也可使用 RabbitMQ, Redis 和 MongoDB 等。

    异步任务

    使用 Celery 实现异步任务主要包含三个步骤:

    1. 创建一个 Celery 实例
    2. 启动 Celery Worker
    3. 应用程序调用异步任务
    $ pip install celery
    # 安装好redis
    

    创建tasks.py

    import time
    from celery import Celery
    
    broker = 'redis://127.0.0.1:6379'
    backend = 'redis://127.0.0.1:6379/0'
    # 密码 redis://:password@127.0.0.1:6379 
    
    app = Celery('my_task', broker=broker, backend=backend)
    
    @app.task
    def add(x, y):
        time.sleep(5)     # 模拟耗时操作
        return x + y
    

    启动celery worker

    在当前路径下执行

    $ celery worker -A tasks --loglevel=info
    

    调用任务

    在当前目录下打开控制台

    >>> from tasks import add
    
    >>> add.delay(2,3)
    

    在上面,我们从 tasks.py 文件中导入了 add 任务对象,然后使用 delay() 方法将任务发送到消息中间件(Broker),Celery Worker 进程监控到该任务后,就会进行执行。

    另外,我们如果想获取执行后的结果,可以这样做:

    >>> result = add.delay(2,5)
    >>> result.ready()
    >>> False
    >>> result.ready()
    >>> True
    >>> result.get()
    >>> 7
    

    使用配置

    在上面的例子中,我们直接把 Broker 和 Backend 的配置写在了程序当中,更好的做法是将配置项统一写入到一个配置文件中,通常我们将该文件命名为 celeryconfig.py。Celery 的配置比较多,可以在官方文档查询每个配置项的含义。

    celery_demo                    # 项目根目录
        ├── celery_app             # 存放 celery 相关文件
        │   ├── __init__.py
        │   ├── celeryconfig.py    # 配置文件
        │   ├── task1.py           # 任务文件 1
        │   └── task2.py           # 任务文件 2
        └── client.py              # 应用程序
    

    __init__.py 代码如下:

    from celery import Celery
    
    app = Celery('demo')                                # 创建 Celery 实例
    app.config_from_object('celery_app.celeryconfig')   # 通过 Celery 实例加载配置模块
    

    celeryconfig.py 代码如下:

    BROKER_URL = 'redis://127.0.0.1:6379'               # 指定 Broker
    CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'  # 指定 Backend
    
    CELERY_TIMEZONE='Asia/Shanghai'                     # 指定时区,默认是 UTC
    # CELERY_TIMEZONE='UTC'                             
    
    CELERY_IMPORTS = (                                  # 指定导入的任务模块
        'celery_app.task1',
        'celery_app.task2'
    )
    

    task1.py 代码如下:

    import time
    from celery_app import app
    
    @app.task
    def add(x, y):
        time.sleep(2)
        return x + y
    

    task2.py 代码如下:

    import time
    from celery_app import app
    
    @app.task
    def multiply(x, y):
        time.sleep(2)
        return x * y
    

    client.py 代码如下

    from celery_app import task1
    from celery_app import task2
    
    task1.add.apply_async(args=[2, 8])        # 也可用 task1.add.delay(2, 8)
    task2.multiply.apply_async(args=[3, 7])   # 也可用 task2.multiply.delay(3, 7)
    
    print('hello world')

    现在,让我们启动 Celery Worker 进程,在项目的根目录下执行下面命令

    $ celery -A celery_app worker --loglevel=info
    

    接着,运行 $ python client.py

    定时任务

    Celery 除了可以执行异步任务,也支持执行周期性任务(Periodic Tasks),或者说定时任务。Celery Beat 进程通过读取配置文件的内容,周期性地将定时任务发往任务队列。

    让我们看看例子,项目结构如下:

    celery_demo                    # 项目根目录
        ├── celery_app             # 存放 celery 相关文件
            ├── __init__.py
            ├── celeryconfig.py    # 配置文件
            ├── task1.py           # 任务文件
            └── task2.py           # 任务文件
    

    __init__.py 代码如下:

    from celery import Celery
    
    app = Celery('demo')
    app.config_from_object('celery_app.celeryconfig')
    

    celeryconfig.py 代码如下:

    from datetime import timedelta
    from celery.schedules import crontab
    
    # Broker and Backend
    BROKER_URL = 'redis://127.0.0.1:6379'
    CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/0'
    
    # Timezone
    CELERY_TIMEZONE='Asia/Shanghai'    # 指定时区,不指定默认为 'UTC'
    # CELERY_TIMEZONE='UTC'
    
    # import
    CELERY_IMPORTS = (
        'celery_app.task1',
        'celery_app.task2'
    )
    
    # schedules
    CELERYBEAT_SCHEDULE = {
        'add-every-30-seconds': {
             'task': 'celery_app.task1.add',
             'schedule': timedelta(seconds=30),       # 每 30 秒执行一次
             'args': (5, 8)                           # 任务函数参数
        },
        'multiply-at-some-time': {
            'task': 'celery_app.task2.multiply',
            'schedule': crontab(hour=9, minute=50),   # 每天早上 9 点 50 分执行一次
            'args': (3, 7)                            # 任务函数参数
        }
    }
    

    task1.py 代码如下:

    import time
    from celery_app import app
    
    @app.task
    def add(x, y):
        time.sleep(2)
        return x + y
    

    task2.py 代码如下:

    import time
    from celery_app import app
    
    @app.task
    def multiply(x, y):
        time.sleep(2)
        return x * y
    

    启动 Celery Worker 进程,在项目的根目录下执行下面命令:

    $ celery -A celery_app worker --loglevel=info
    

    接着,启动 Celery Beat 进程,定时将任务发送到 Broker,在项目根目录下执行下面命令:

    $ celery beat -A celery_app
    

    在 Worker 窗口我们可以看到,任务 task1 每 30 秒执行一次,而 task2 每天早上 9 点 50 分执行一次。

    我们用两个命令启动了 Worker 进程和 Beat 进程,我们也可以将它们放在一个命令中:

    $ celery -B -A celery_app worker --loglevel=info
    

      

  • 相关阅读:
    [LeetCode]113. Maximum Depth of Binary Tree二叉树的最大深度
    [LeetCode]112. Maximum Subarray最大和连续子序列
    [LeetCode]111. Find Minimum in Rotated Sorted Array II旋转数组最小值II
    [LeetCode]110. Find Minimum in Rotated Sorted Array旋转数组最小值
    [LeetCode]109. Construct Binary Tree from Inorder and Postorder Traversal由中序序列和后序序列重建二叉树
    [LeetCode]108. Construct Binary Tree from Preorder and Inorder Traversal由前序序列和中序序列重建二叉树
    [LeetCode]107. Best Time to Buy and Sell Stock III股票买卖III
    [LeetCode]106. Best Time to Buy and Sell Stock II股票买卖II
    [LeetCode]105. Word Search单词查找
    一些杂事之后,该收心了
  • 原文地址:https://www.cnblogs.com/zenan/p/10684688.html
Copyright © 2011-2022 走看看