异步消息队列Celery
Celery是异步消息队列, 可以在很多场景下进行灵活的应用.消息中包含了执行任务所需的的参数,用于启动任务执行, suoy所以消息队列也可以称作
在web应用开发中, 用户触发的某些事件需要较长事件才能完成. 可以将任务交给celery去执行, 待任务完成后再将结果返回给用户. 用户同步请求触发的其它任务, 如发送邮件,请求云服务等也可以交由celery来完成.
celery的另一个重要应用场景则是各种计划任务.
celery由5个主要组件组成:
-
producer: 任务发布者, 通过调用API向celery发布任务的程序, 如web后端的控制器.
-
celery beat: 任务调度, 根据配置文件发布定时任务
-
worker: 实际执行任务的程序
-
broker: 消息代理, 接受任务消息,存入队列再按顺序分发给worker执行.
-
result backend: 存储结果的服务器, 一般为各种数据库服务
整体结构如图所示:
broker是celery的关键组件, 目前的可靠选择有RabbitMQ和Redis, 出于稳定性等原因我们选择官方推荐的RabbitMQ作为broker.顺便安装librabbitmq作为RabbitMQ的python客户端.
消息的发送接受过程需要对序列进行序列化和反序列化, 从celery3.2开始官方出于安全性原因拒绝使用python内置的pickle作为序列化方案, 目前celery支持的序列化方案包括:
-
json: 跨语言的序列化方案
-
yaml: 拥有更多数据类型, 但python客户端性能不如json
-
msgpack: 二进制序列化方案, 比json更小更快
若对可读性有要求可以采用json方案, 若追求更高的性能则可以选用msgpack.
result backend用于存储异步任务的结果和状态, 目前可用的有Redis、RabbitMQ、MongoDB、Django ORM、SQLAlchemy等.
可以使用boundless方式安装依赖:
pip install "celery[librabbitmq,redis,msgpack]"
第一个异步任务
创建tasks.py
文件, 并写入:
from celery import Celery
app = Celery('tasks', broker='redis://127.0.0.1:6379/0', backend='redis://127.0.0.1:6379/1')
@app.task
def add(x, y):
return x + y
这样我们创建了celery实例, Celery()的第一个参数为当前module的名称(py文件名或包名).
在终端执行命令以启动服务器:
celery -A tasks worker -l info
-A tasks
参数指定app为模块tasks,-l info
参数指定log级别为info.
当看到这条log时说明celery已就绪:
[2016-09-11 18:04:43,758: WARNING/MainProcess] celery@finley-pc ready.
在python中导入任务并执行
>>> from tasks import add
>>> result = add.delay(1,2)
>>> result.result
3
>>> result.status
'SUCCESS'
>>> result.successful()
True
使用一个py文件作为module非常不便, 在更复杂的任务中可以采用python包作为module.
建立python包demo,建立下列文件:
app.py:
from celery import Celery
app = Celery('demo', include=['demo.tasks'])
app.config_from_object('demo.config')
app.start()
config.py
BROKER_URL = 'redis://127.0.0.1:6379/0'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/1'
CELERY_TASK_SERIALIZER = 'msgpack'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_RESULT_EXPIRES = 60 * 60 * 24
CELERY_ACCEPT_CONTENT = ['json', 'msgpack']
tasks.py
from demo.app import app
@app.task
def add(x, y):
return x + y
在终端中启动:
celery -A demo.app worker -l info
若将-A
参数设为demo
则会默认尝试启动demo.celery
.因为该module与celery重名可能在导入时出现错误, 所以我们没有采用这种做法.
celery还支持绑定,日志,重试等特性:
from celery.utils.log import get_task_logger
logger = get_task_logger(__name__)
@app.task(bind=True)
def div(self, x, y):
logger.info('doing div')
try:
result = x / y
except ZeroDivisionError as e:
raise self.retry(exc=e, countdown=5, max_retries=3)
return result
bind=true
将app对象作为self参数传给task函数.
前文的示例需要producer主动检查任务的状态,存在诸多不便. 我们可以在task函数中主动通知producer:
from celery import Celery
from demo.app import app
from urllib.request import urlopen
@app.task
def add(x, y):
result = x + y
url = 'http://producerhost/callback/add?result=%d' % result
urlopen(url)
return result
上述示例中我们使用GET请求将结果发送给了producer的回调API, 当然很多情况下可以直接调用回调函数.
Celery易于与Web框架集成, 作者常采用的交互逻辑是:
-
提供提交任务, 查询任务结果两个API, 由客户端决定何时查询结果
-
采用websocket等技术, 服务器主动向客户端发送结果
当然也可以采用异步IO模式, 这需要一些扩展包的协助:
安装tornado-celery: pip install torando-celery
编写handler:
import tcelery
tcelery.setup_nonblocking_producer()
from demo.tasks import add
calss Users(RequestHandler):
@asynchronous
def get():
add.apply_async(args=[1,2], callback=self.on_success)
def on_success(self, response):
users = response.result
self.write(users)
self.finish()
其它的Web框架也有自己的扩展包:
-
Django: django-celery
-
Tornado: tornado-celery
-
web2py: web2py-celery
-
Pylons: celery-pylons
-
Pyramid: pyramid_celery
计划任务
celery的计划任务有schedule和crontab两种方式.
在config.py中添加配置:
CELERYBEAT_SCHEDULE = {
'add': {
'task': 'demo.tasks.add',
'schedule': timedelta(seconds=10),
'args': (16, 16)
}
}
启动beat:
celery beat -A demo.app
然后启动worker:
celery -A demo.app worker -l info
或者与celery app一同启动:
celery -B -A demo.app worker -l info
'schedule'可以接受datetime, timedelta或crontab对象:
from celery.schedules import crontab
{
'schedule': crontab(hour=7, minute=30, day_of_week=1),
pass
}
webhook
上文中我们使用本地python函数作为worker, webhook机制允许使用远程的Web服务作为worker.
在使用webhook作为worker时, broker将消息封装为http请求发送给worker, 并按照协议解析返回值.
使用webhook需要在CELERY_IMPORTS
参数中包含celery.task.http
, 或者在启动参数中指定-I celery.task.http
.
broker使用GET或POST方法发送请求, 参数由调用时的关键字参数指定. worker返回json格式的响应:
{'status': 'success', 'retval': ...}
在失败时返回响应:
{'status': 'failure', 'reason': ...}
我们用django作为worker:
from django.http import HttpResponse
import json
def add(request):
x = int(request.GET['x'])
y = int(request.GET['y'])
result = x + y
response = {'status': 'success', 'retval': result}
return HttpResponse(json.dumps(response), mimetype='application/json')
配置django为http://cloudservice/webhook/add
提供Web服务.
从本地添加任务:
>>>from celery.task.http import URL
>>>result = URL('http://cloudservice/webhook/add').get_async(x=10, y=10)
>>>result.get()
20
URL是HttpDispatchTask的快捷方法(shortcut):
>>> from celery.task.http import HttpDispatchTask
>>> res = HttpDispatchTask.delay(
... url='http://cloudservice/webhook/add',
... method='GET', x=10, y=10)
>>> res.get()
20
更多关于celery的内容请参阅: