浅谈异步任务队列Celery

Celery简介

Celery与任务队列

Celery是一个简单、灵活且可靠的,能够处理大量消息的分布式系统。它专注于实时处理异步任务队列,同时也支持任务调度。

Celery的架构组成

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

  • 消息中间件

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

  • 任务执行单元

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

  • 任务结果存储

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

    另外,Celery还支持不同的并发和序列化的手段。

任务队列

任务队列一般用于线程或计算机之间分配工作。

任务队列的输入是一个称为任务的工作单元,有专门的职程(Worker)进行不断的监视任务队列,进行执行新的任务工作。

Celery通过消息机制进行通信,通常使用中间人(Broker)作为客户端和职程(Worker)调节。启动一个任务,客户端向消息队列发送一条消息,然后Broker将消息传递给一个Worker,最后由Worker进行执行Broker分配的任务。

Celery可以有多个Worker和Broker,用来提高Celery的高可用性以及横向扩展能力。

Celery是用Python编写的,但协议可以用任何语言实现。除了Python语言实现外,还有Node.js的node-celery和php的celery-php。

Celery的使用场景

  • Web应用

    当用户触发一个需要长时间执行的操作时,可以把它作为任务交给Celery去异步执行,执行完再返回给用户。这段时间用户不需要等待,提高网站的整体吞吐量和响应时间。

  • 定时任务

    生产环境经常会跑一些定时任务。假如你有大量服务器,每台服务器是设置各种定时任务,管理起来比较困难,这时就可以使用Celery来统一管理和设置不同的定时任务。

  • 异步任务

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

Celery的优点

  • 简单

    Celery使用和维护都十分简单,并且不需要配置文件。

  • 高可用

    worker和client会在网络连接丢失或者失败时,自动进行重试。并且有的brokers也支持“双主”或者“主/从”的方式实现高可用。

  • 快速

    单个的Celery进程每分钟可以处理百万级的任务,并且只需要毫秒级的往返延迟(使用RabbitMQ,librabbitmq和优化设置时)

  • 灵活

    Celery几乎每个部分都可以扩展使用,自定义池实现,序列化、压缩方案、日志记录、调度器、消费者、生产者、broker传输等等。

选择中间人(Broker)

Celery需要一个中间件来进行接收和发送消息,通常以独立的服务形式出现,成为消息中间人(Broker)。

中间人可以使用RabbitMQRedis等,官方推荐使用RabbitMQ。本文为了方便演示,选用Redis作为Broker。

开始前你需要安装redis数据库和python的redis包。

pip3 install redis

安装

pip3 install -U Celery

python3 -m pip install Celery

Celery的基本使用

简单使用

创建一个Celery实例app,它可能包含Celery中执行操作的所有入口,如创建任务、管理worker等。

  1. 创建异步任务执行脚本tasks.py

    import time
    from celery import Celery
    
    # 无密码:redis://host:port/db
    # 有密码:redis://password@host:port/db
    broker = 'redis://:123456@127.0.0.1:6379/0'
    backend = 'redis://:123456@127.0.0.1:6379/1'
    
    app = Celery('test', broker=broker, backend=backend)
    
    
    @app.task
    def send_email(name):
        print(f'sending email to {name}...')
        time.sleep(5)
        print('finished')
        return 'success'
    

    Celery的参数说明:

    • 第一个参数为当前模块的名称,只有在__main__模块中定义任务时才会生产名称
    • 第二个参数为中间人(broker)的链接url,消息队列,用来发送和接受消息
    • 第三个参数为backend的链接url,用于保存任务结果和状态

    创建一个名称为send_email的任务,用于模拟向某人发送邮件。

  2. 创建执行任务文件run.py

    from tasks import send_email
    
    result1 = send_email.delay('Tony')  # 通过delay()方法来执行任务
    print(result1.id)
    result2 = send_email.delay('Jany')
    print(result2.id)
    
  3. 在控制台启动celery执行以下命令

    celery worker -A tasks -l info
    

然后运行run.py执行任务,在控制台就可以看到以下日志记录

[2020-12-10 19:32:16,875: INFO/MainProcess] Received task: tasks.send_email[02f372cf-2a3a-48e3-ad1f-16f74b42153d]
[2020-12-10 19:32:16,876: WARNING/MainProcess] sending email to Tony...
[2020-12-10 19:32:16,877: INFO/MainProcess] Received task: tasks.send_email[a8e4ad04-465a-43d7-8896-fb0c5c0974f5]
[2020-12-10 19:32:16,878: WARNING/MainProcess] sending email to Jany...

注意:如果是在win10下使用celery4.x的话,执行run.py后会报错

解决方法:安装eventlet库

pip3 install eventlet

再次启动celery执行任务:

celery worker -A tasks -l info -P eventlet

运行run.py就可以正常获取结果了

在redis数据库忠可以看到对应id的结果信息,如id为a8e4ad04-465a-43d7-8896-fb0c5c0974f5的信息如下:

{
  "status": "SUCCESS",
   "result":  "success",
   "traceback":  null,
   "children":  [],
   "date_done":  "2020-12-10T11:32:21.864885",
   "task_id":  "a8e4ad04-465a-43d7-8896-fb0c5c0974f5"
}

其中result为任务的返回值

多任务结构

新建项目celery_project,目录结构如下:

celery_project/
	celery_tasks/
		__init__.py
		celery.py
		task1.py
		task2.py
	get_result.py
	run.py

celery.py

from celery import Celery

CELERY_CONFIG = {
    'main': 'celery',
    'broker': 'redis://:123456@127.0.0.1:6379/0',
    'backend': 'redis://:123456@127.0.0.1:6379/1',
    'include': ['celery_tasks.task1', 'celery_tasks.task2']  # 包含的任务文件列表
}

app = Celery(**CELERY_CONFIG)
app.conf.timezone = 'Asia/Shanghai'  # 时区
app.conf.enable_utc = False  # 是否使用UTC(国际协调时间)

task1.py(模拟发送邮件)

import time
from celery_tasks.celery import app


@app.task
def send_email(name):
    print(f'sending email to {name}...')
    time.sleep(5)
    return 'email sent successfully'

task2.py(模拟发送短信)

import time
from celery_tasks.celery import app


@app.task
def send_msg(name):
    print(f'sending message to {name}...')
    time.sleep(5)
    return 'message sent successfully'

在控制台启动Celery

celery worker -A celery_tasks -l info -P eventlet

run.py(异步执行任务)

from celery_tasks.task1 import send_email
from celery_tasks.task2 import send_msg

result1 = send_email.delay('Lisa')
result2 = send_msg.delay('Ben')
print(result1.id)
print(result2.id)

运行run.py,结果如下

c14aee7c-27d4-4785-aeb3-0b34e6f3c87a
bad62286-700c-47f1-ada2-61526c0afde0

get_result.py(获取任务结果)

from celery.result import AsyncResult
from celery_tasks.celery import app

async_result = AsyncResult(id='c14aee7c-27d4-4785-aeb3-0b34e6f3c87a', app=app)
if async_result.successful():
    result = async_result.get()
    print(result)
elif async_result.failed():
    print('failed')

AsyncResult中的id值为上面执行任务获取的任务id,根据任务id就可以查看任务的完成状态和完成结果。

执行结果如下:

email sent successfully

定时任务

  1. 在上一个项目celery_project下新建一个延时任务执行文件cron_task.p

    from datetime import datetime, timezone, timedelta
    from celery_tasks.task1 import send_email
    
    # 默认使用utc时间
    utc_ctime = datetime.now(tz=timezone.utc)  # 当前的utc时间
    delay_time = timedelta(seconds=10)  # 设置10秒延迟
    task_time = utc_ctime + delay_time
    
    # 使用apply_async设置定时任务
    result = send_email.apply_async(args=['egon'], eta=task_time)
    print(result.id)
    

    控制台启动celery后,运行cron_task.py,10s后就可以看见控制台打印相关的任务执行信息。

  2. 上面只是简单演示了延时10s后执行任务的流程,如果需要每过10s执行一次send_email任务,则需要对celery.py的app.conf.beat_schedule进行配置,修改后如下:

    from datetime import timedelta
    from celery import Celery
    
    CELERY_CONFIG = {
        'main': 'celery',
        'broker': 'redis://:123456@127.0.0.1:6379/0',
        'backend': 'redis://:123456@127.0.0.1:6379/1',
        'include': ['celery_tasks.task1', 'celery_tasks.task2']  # 包含的任务文件列表
    }
    
    app = Celery(**CELERY_CONFIG)
    app.conf.timezone = 'Asia/Shanghai'  # 时区
    app.conf.enable_utc = False  # 是否使用UTC(国际协调时间)
    
    app.conf.beat_schedule = {
        'task-per-10s': {
            'task': 'celery_tasks.task1.send_email',  # 任务函数
            'schedule': timedelta(seconds=10),  # 10s执行一次
            # 'schedule': crontab(minute="*/1"),  # 也可以使用crontab的方式
            'args': ('Mike',)  # 传参
        }
    }
    
  3. 在项目的根目录下开启两个cmd窗口

    第一个窗口启动celery,用于接收并执行任务

    celery worker -A celery_tasks -l info -P eventlet
    

    第二个窗口用于发送定时任务

    celery beat -A celery_tasks -l info
    

    启动后可以看到第二个窗口每10s中发送一个任务

    [2020-12-11 16:58:59,748: INFO/MainProcess] beat: Starting...
    [2020-12-11 16:59:00,020: INFO/MainProcess] Scheduler: Sending due task task-per-10s (celery_tasks.task1.send_email)
    [2020-12-11 16:59:10,007: INFO/MainProcess] Scheduler: Sending due task task-per-10s (celery_tasks.task1.send_email)
    [2020-12-11 16:59:20,008: INFO/MainProcess] Scheduler: Sending due task task-per-10s (celery_tasks.task1.send_email)
    

    第一个窗口接收任务并执行

    [2020-12-11 16:59:00,291: INFO/MainProcess] Received task: celery_tasks.task1.sen
    d_email[31cbccb8-4fc4-4f44-8ee0-0e0a196cb8d7]
    [2020-12-11 16:59:00,292: WARNING/MainProcess] sending email to Mike...
    [2020-12-11 16:59:05,303: INFO/MainProcess] Task celery_tasks.task1.send_email[31
    cbccb8-4fc4-4f44-8ee0-0e0a196cb8d7] succeeded in 5.01600000000326s: 'email sent s
    uccessfully'
    [2020-12-11 16:59:10,011: INFO/MainProcess] Received task: celery_tasks.task1.sen
    d_email[06e03d73-611b-4750-83bb-9722e5d78660]
    [2020-12-11 16:59:10,012: WARNING/MainProcess] sending email to Mike...
    [2020-12-11 16:59:15,016: INFO/MainProcess] Task celery_tasks.task1.send_email[06
    e03d73-611b-4750-83bb-9722e5d78660] succeeded in 5.0s: 'email sent successfully'
    

以上就是关于异步任务队列框架Celery的简单介绍和使用,如果想了解更多关于Celery的功能和应用,可以去Celery官方文档查看。

posted @ 2020-12-11 17:34  蓝莓薄荷  阅读(708)  评论(1编辑  收藏  举报