0. 实际问题
假设我们拥有2个celery的task对象my_taskA
和my_taskB
,具体的两个函数逻辑如下:
@celery_app.task()
def my_taskA(a, b, c):
print("doing my_taskA here...")
time.sleep(0.01)
return a + b + c
@celery_app.task()
def my_taskB(x, y):
print("doing my_taskB here...")
time.sleep(10)
return x - y
默认来说,这两个任务都会在celery的默认队列(queue)‘default’中运行,那么存在这么一个问题,
如果同时添加两类任务中,消费的任务对象my_taskA
和my_taskB
并不会很快消耗完,那么很可能出现
一个现象,那就是很快这个队列中的任务都是my_taskB
的任务了。
1. 如何处理?
一般来说,我们肯定想到的是,对于每个任务给它分配一个单独的broker,这样不久不会影响了,各自维护
各自的东西,事实上很多人也是这么做的。但这样,增加了维护的成本。试想一下,如果有20种任务,我们
难道要开辟20个broker去指定不同的任务吗?
但实际上,在阅读完celery的官方教程后,发现一个路由和交
换的概念。可以在一个broker上面开辟多个队列,每个队列绑定指定类型的任务,而对应的worker通过指定
的队列获取任务。这个概念就类系我们的双频路由器一样,2.4G的设备你们走常规的通道,5G的设备我有独享
的高速5G通道。完美!
2. 如何使用?
看到这里,接下来肯定是准备尝试了。但是说句实话,像我这样的白痴级别的任务,很难通过官方样例看懂,
所以我查询了一部分资料,然后实现了个demo。
先上目录结构:
untitled1
├── app.py
├── celeryconfig.py
└── send_task.py
做个简单说明:app.py
实现具体的worker逻辑,send_task
负责分发任务到队列中,
celeryconfig.py
是基础配置。
- app.py实现
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import time
from celery import Celery
from celery.signals import task_postrun
celery_app = Celery('test_work', broker='amqp://sangfor:test@localhost/producer-vhost')
celery_app.config_from_object('celeryconfig')
@celery_app.task()
def my_taskA(a, b, c):
print("doing my_taskA here...")
time.sleep(0.01)
return a + b + c
@celery_app.task()
def my_taskB(x, y):
print("doing my_taskB here...")
time.sleep(10)
return x - y
@task_postrun.connect
def task_postrun_handler(sender=None, task_id=None, task=None, args=None, kwargs=None, retval=None, state=None, **kwds):
"""任务完成的信号处理函数"""
print ''' Done!
sender: %s
task_id: %s
task: %s
retval: %s
state: %s
args:%s
kwargs:%s
kwds:%s''' % (sender, task_id, task, retval, state, args, kwargs, kwds,)
- send_task.py的实现
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
import time
from celery import Celery
celery_app = Celery('send_work', broker='amqp://sangfor:test@localhost/producer-vhost')
# celery_app.config_from_object('celeryconfig')
celery_app.conf.update(
CELERY_TASK_SERIALIZER='json',
CELERY_RESULT_SERIALIZER='json',
CELERY_ENABLE_UTC=True,
CELERY_ROUTES={
'app.my_taskA': {'queue': 'for_task_A'},
'app.my_taskB': {'queue': 'for_task_B'},
},
CELERY_QUEUES={
"for_task_A": {
"exchange": "for_task_A"
},
"for_task_B": {
"exchange": "for_task_B"
}
}
)
def run():
while True:
print 'Now send task A'
celery_app.send_task('app.my_taskA', (1, 2, 3,))
print 'Now send task B'
celery_app.send_task('app.my_taskB', (9, 8))
time.sleep(0.01)
if __name__ == '__main__':
run()
- celeryconfig.py的实现
#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
CELERY_IMPORTS = ['app']
CELERY_ROUTES = {
'app.my_taskA': {'queue': 'for_task_A'},
'app.my_taskB': {'queue': 'for_task_B'},
}
CELERY_QUEUES = {
"for_task_A": {
"exchange": "for_task_A"
},
"for_task_B": {
"exchange": "for_task_B"
}
}
3.如何执行
celery -A app worker -n workerA -Q for_task_A
celery -A app worker -n workerA -Q for_task_B
-
python send_task.py
注意,这里的-Q
是精髓,为了指定执行特定通道中的任务。这个指令干啥的,自己--help吧。
运行一下试试吧,其他的不解释了。
4.几点知识补充
1.Exchange
有几类type:
(1) direct(default)
:
direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing
key完全匹配的Queue中。
例子:
CELERY_QUEUES = (
Queue('for_adds',Exchange('for_adds',type='direct'), routing_key='adds'),
Queue('for_send_emails', Exchange('for_adds',type='direct'), routing_key='email'),
Queue('add', Exchange('for_adds',type='direct'), routing_key='add'),
)
CELERY_ROUTES = {
'celery_test.tasks.add': {'exchange':'for_adds','routing_key':'add'},
'celery_test.tasks.send_mail': {'exchange':'for_adds','routing_key':'email'},
'celery_test.tasks.adds': {'exchange':'for_adds','routing_key':'add'},
}
(2)topic
:
topic类型的Exchange在匹配规则上进行了扩展,它与direct类型的Exchage相似,也是将消息路由到
binding key与routing key相匹配的Queue中。可以看出和上面那个区别的地方,这里面不
是强匹配。它引入了两个通配符#
和*
前者匹配多个单词(可以为0),后者匹配一个单词。
例子:
CELERY_QUEUES = (
Queue('for_adds',Exchange('for_adds',type='topic'), routing_key='*.task.*'),
Queue('for_send_emails', Exchange('for_adds',type='topic'), routing_key='*.*.email'),
Queue('add', Exchange('for_adds',type='topic'), routing_key='*.add'),
)
CELERY_ROUTES = {
'celery_test.tasks.add': {'exchange':'for_adds','routing_key':'q.task.email'},
'celery_test.tasks.send_mail': {'exchange':'for_adds','routing_key':'a.task.e'},
'celery_test.tasks.adds': {'exchange':'for_adds','routing_key':'b.add'},
}
(3)fanout
:
fanout类型就是传说中的广播形式,它没有参数绑定,就是不需要指定上面的routing_key
之类的东西,
只要和该交换绑定的queue,统统发送出去。类似于通过交换口,就广播发出。
例子:
CELERY_QUEUES = (
Queue('for_adds',Exchange('for_adds',type='fanout')),
Queue('for_send_emails', Exchange('for_adds',type='fanout')),
Queue('add', Exchange('for_adds',type='fanout')),
)
CELERY_ROUTES = {
'celery_test.tasks.add': {'exchange':'for_adds'},
'celery_test.tasks.send_mail': {'exchange':'for_adds',},
'celery_test.tasks.adds': {'exchange':'for_adds',},
}
(4)headers
:
headers类型中,通过一个参数表(包含header和values(可选)),队列被绑定到交换
还有一个名为“x-match”的特殊参数确定两者之间的匹配算法,可以使用两个关键字进行表示。
其中“all”意味着一个AND(所有对必须匹配),“any”意味着OR(至少一对必须匹配)。
例子(google没找到合适的例子,自己看资料写了个,不一定对,有知道的欢迎指正):
CELERY_QUEUES = (
Queue('for_adds',Exchange('for_adds',type='header', arguments = {'ham': 'good', 'x-match':'any'})),
)