分布式队列Celery — 应用基础

前言

分布式任务队列 Celery,Python 后端技能树必点。结合 RabbitMQ 系列,深入梳理 Celery 知识点。当然,这也会是一个系列的文章。

简介

Celery 是一个简单、灵活且可靠的分布式任务队列(Distributed Task Queue)。队列是一种常见的数据结构,具有 FIFO(First in First Out)的特性,阻塞队列能够有效的将应用程序解耦为生产者和消费者,从而实现异步调用。任务的主体是被 celery.task 装饰器包装过的函数,而在队列中传递的 “任务” 其实是执行这些函数所需要的全部参数。

首先需要明确的是,Celery 虽然称之为分布式任务队列,但其本身并不提供队列服务,它更像是队列的 Controller,而非队列本身。所以应用 Celery 还需要集成第三方的消息队列中间件(e.g. RabbitMQ or Redis)。

那 Celery 和 RabbitMQ 有什么区别呢?简单来说 RabbitMQ 专注于消息队列的管理,而 Celery 专注于实时的任务处理。

Celery 的应用场景

即时响应需求

网页的响应时间是用户体验的关键,Amazon 曾指出响应时间每提高 100ms,他们的收入便会增加 1%。对于一些需要长时间执行的任务,大多会采用异步调用的方式来释放用户操作。Celery 的异步调用特性,和前端使用 Ajax 异步加载类似,能够有效缩短响应时间。


周期性任务需求(Periodic Task)

对于心跳测试、日志归档、运维巡检这类指定时间周期执行的任务,可以应用 Celery 任务调度模块 Celery Beat,支持 crontab 定时模式,简单方便。


高并发及可扩展性需求

解耦应用程序最直接的好处就是可扩展性和并发性能的提高。Celery 支持以多线程、多进程、协程的方式来并发执行任务,同时支持自动动态扩展。

架构组成

Broker

消息代理,作为临时储存任务的中间媒介,为 Celery 提供了队列服务。生产者将任务发送到 Broker,消费者再从 Broker 获取任务。

Beat

任务调度器,负责调度并触发 Celery 定时周期任务。Beat 进程读取 CeleryConfig 中自定义的定时周期任务列表,将到期需要执行的定时任务发送到任务队列中。

Worker

任务执行单元,实际负责执行任务的服务进程,每一个 Worker 都有一个并发池(Prefork/Eventlet/Gevent/Thread)来支持多并发。Worker 会监听订阅的任务队列,当队列中有任务时,就会获取任务并执行。

Result Backend/Store

任务执行状态和结果存储,Celery 支持任务实时处理,也就是说 Celery 可以把任务执行的实时状态和最终结果回传生产者。这种回传也需要通过中间存储媒介。

Celery 应用基础

先通过一个简单的例子来认识 Celery,这里使用 RabbitMQ 充当 Broker,使用 Redis 充当 Result Backend。

Step1:安装 Celery & RabbitMQ & Redis

pip install celery
pip install redis
sudo apt-get install -yq rabbitmq-server

Step2:初始化 Celery Proj 项目

jmilkfan@aju-dev:/workspace$ tree proj/
proj/
├── app_factory.py       # Celery application 工厂模块
├── celeryconfig.py      # Celery 常规配置文件模块
├── celery.py            # Celery 启动模块
├── __init__.py
└── task                 # 任务包
   ├── api.py           # 任务 API 模块
   ├── tasks.py         # 任务实现模块
   └── __init__.py

Step3:配置 Celery Config

# filename: celeryconfig.py
# 直接使用 RabbitMQ 的缺省用户 guest 以及缺省虚拟主机 '/'
BROKER_URL = 'amqp://guest:guest@localhost:5672//'
# Redis 同样使用默认数据库 '0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
# 设定导入任务模块路径
CELERY_IMPORTS = ['proj.task.tasks']

Step4:实现 App Factory

# filename: app_factory.py 
from __future__ import absolute_import
# 这里使用 __future__.absolute_import 是因为 proj 包中的 celery 模块与 celery 库重名了,所以需要指明 celery 从绝对路径中导入。
from celery import Celery
def make_app():
   app = Celery('proj')
   # 指定常规配置模块路径
   app.config_from_object('proj.celeryconfig')
   return app

Step5:实现 Celery Runner

# filename: celery.py
from proj.app_factory import make_app
# 该模块能够自动被 celery cmd 识别,并自动加载模块中的 app 对象,以此来启动 worker service。
app = make_app()

Step6:实现 Celery Tasks

# filename: tasks.py
from proj.celery import app
# 使用 celery.task 装饰器包装的函数就是任务的主体
@app.task
def add(x, y):
   return x + y

Step7:启动 Celery Worker

/workspace$ celery worker -A proj -l info

选项 -A/--app,指定 Celery 的项目,如果参数是一个 Package,那么 celery cmd 会自动搜索 celery.py 模块。当然了,你也可以使用 app 所在的 module_name 作为参数。celery cmd 最终要加载的实际上是 app 对象。

Step7:执行任务

# 执行任务的代码逻辑一般在 “生产者” 实现。
>>> from proj.task import tasks
>>> tasks.add
<@task: proj.task.tasks.add of proj at 0x7f9149e6e250>
>>> result = tasks.add.delay(1, 2)
>>> result.status
u'SUCCESS'
>>> result.failed()
False
>>> result.info
3
>>> result.result
3

NOTE:想要获取任务执行结果必须启用 Result Backend,否则会触发异常

>>> result.status
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "/usr/local/lib/python2.7/dist-packages/celery/result.py", line 436, in state
   return self._get_task_meta()['status']
 File "/usr/local/lib/python2.7/dist-packages/celery/result.py", line 375, in _get_task_meta
   return self._maybe_set_cache(self.backend.get_task_meta(self.id))
 File "/usr/local/lib/python2.7/dist-packages/celery/backends/base.py", line 352, in get_task_meta
   meta = self._get_task_meta_for(task_id)
AttributeError: 'DisabledBackend' object has no attribute ‘_get_task_meta_for'

Step8:自定义  Exchange & Queue
自定义 Exchange 和 Queue 需要依赖 kombu 库,kombu 是一个「Messaging library for Python」,Python 写的消息库。目标是为 AMQP 协议提供高层接口,让 Python 的消息传递尽可能变得简单,并且也提供一些常见的消息传递问题解决方案。

# filename: app_factory.py
from __future__ import absolute_import
from celery import Celery
from kombu import Queue, Exchange
def make_app():
   app = Celery('proj')
   app.config_from_object('proj.celeryconfig')
   # 除了 config_from_object,也支持 celery.conf 直接设置 Celery Config
   # 定义 Exchange
   default_exchange = Exchange('default', type='direct')
   web_exchange = Exchange('task', type='direct')
   # 定义默认 Exchange 和 Queue,所有没有指定队列的任务,都会放置在这里
   app.conf.task_default_queue = 'default'
   app.conf.task_default_exchange = 'default'
   app.conf.task_default_routing_key = 'default'
   # 定义 Queues
   app.conf.task_queues = (
       Queue('default', default_exchange, routing_key='default'),
       Queue('high_queue', web_exchange, routing_key='hign_task'),
       Queue('low_queue', web_exchange, routing_key='low_task'),
   )
   return app


Step9:指定任务队列

# task.apply_async 和 task.delay 一样用于触发任务,区别在于前者的功能参数更多。
>>> from proj.task import tasks
>>> result  = tasks.add.apply_async(args=(1, 2), queue='low_queue')
>>> result.status
u'SUCCESS'
>>> result.result
3

NOTE:apply_async & delay
使用 app.task 装饰器包装函数以后,得到的实际上是一个类对象,而非单纯的函数,所以我们能够通过函数来调用 delay/apply_async 方法。当调用这两个方法时,才实际触发了一个任务。而如果直接调用该函数的话,那么跟调用一个普通函数没有区别。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值