celery路由和交换的相关知识

0. 实际问题

假设我们拥有2个celery的task对象my_taskAmy_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_taskAmy_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'})),
)

5.参考文献

  1. Using pika to create headers exchanges with RabbitMQ in python
  2. RabbitMQ Exchanges, routing keys and bindings
  3. celery 、rabbitmq的exchange三种方式的实现
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 序言第1章 并行和分布式计算介绍第2章 异步编程第3章 Python的并行计算第4章 Celery分布式应用第5章...
    SeanCheney阅读 12,286评论 3 35
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • 来源 RabbitMQ是用Erlang实现的一个高并发高可靠AMQP消息队列服务器。支持消息的持久化、事务、拥塞控...
    jiangmo阅读 10,255评论 2 34
  • 1.定义: Celery是一个异步的任务队列(也叫做分布式任务队列) 2.工作结构 Celery分为3个部...
    四号公园_2016阅读 28,603评论 5 60
  • 在学习Celery之前,我先简单的去了解了一下什么是生产者消费者模式。 生产者消费者模式 在实际的软件开发过程中,...
    c2db9ba35639阅读 3,451评论 0 8