使用Celery在RabbitMQ中将消息发布到Exchange

时间:2018-02-06 00:04:56

标签: python django rabbitmq celery django-celery

我编写了一个Django-Project,它通过my_task.delay()提示不同的任务异步。问题是随着项目变得越来越大,正确地路由任务真的很困难 - 我开始编写的任务只是为了组合不同的任务,这使得代码在一段时间后变得混乱。

当我阅读一些RabbitMQ文档时,我遇到了一个解决方案,它可以更好地构建我的项目,它依赖于 Exchanges Exchange 可以将消息发布到 Cosumer 可以使用它的多个队列,简而言之:

Publish/Subscribe in RabbitMQ Documentation

RabbitMQ-Documentation描述了Pika的解决方案,它是RabbitMQ比Celery更低级别的客户端。

Celery-Documentation在其Documentation中描述了这种情况,但没有描述如何创建 Producer ,该 Producer 生成消息 Exchange ,将其分发到各种队列,如上图所示。 它仅描述了如何向队列和send tasks发送消息 - 但我想要由交易所进行此操作。

我发现芹菜依赖于它的Kombu具有通过 Producer 向Exchange发送邮件的功能,但我找不到任何文档如何在celery-django中使用它。

我如何能够在芹菜中实现所描述的程序?

PS:StackOverflow上已经有similar question建议使用像Chain和Group of Celery这样的原语,但这与我理解的Exchange范式相矛盾。

1 个答案:

答案 0 :(得分:0)

要获得良好的任务路由,您应该创建更多队列。 Celery使用一次交换,并直接绑定到队列。通过设置几个队列,您可以拆分工作。然后,您可以启动更多仅从某些队列消耗的工作程序,以更快地处理工作量最大的队列。

看看哨兵如何解决它:https://github.com/getsentry/sentry/blob/master/src/sentry/conf/server.py#L467

如果您真的想使用多个交换,则可以在设置文件中以及在task_queues上设置更多交换,以定义在哪个路由上使用哪个交换。继续将芹菜直接交易给您,可以在需要时切换到其他经纪人。

first_exchange = kombu.Exchange(name="first", type="direct")
second_exchange = kombu.Exchange(name="second", type="direct")
task_queues = [
    kombu.Queue(
        name="queue1",
        exchange=first_exchange,
        routing_key="queue1",
    ),
    kombu.Queue(
        name="queue2",
        exchange=second_exchange,
        routing_key="queue2",
    )]

当我尝试解决将芹菜任务中的消息发布到芹菜未使用的另一个交易所时,我多次遇到这个问题。我以为我可以分享我的发现的结果,以防其他人在这里遇到同样的问题。

这使用的是Celery 4.3,而不是django中不再需要的django-celery。

我有一个django应用程序,除了使用celery之外,还通过RabbitMQ向其他较小的集成应用程序和客户发送“常规” AMQP消息。

因此,在芹菜任务中,我想发布到一个与我用于芹菜任务的交换不同的交换,而消息不是任务。

我要使其工作的最初方法是在每个任务中创建一个新连接。但是我认为这不是可伸缩的,因为如果我的应用程序正在处理大量并发任务,我将获得很多连接。在需要新连接的任何地方导入amqp-connection字符串的django设置也很烦人。

相反,我开始研究是否可以以某种方式从celery获取当前连接并将其重新使用以发布到RabbitMQ。最好像我在非Django的消费者和生产者中一样使用连接池。

事实证明,连接池和产品池很容易获得。

在celery.py中我的外部消息的初始设置:

app = Celery("my_proj")
setting_object = import_module(settings.CELERY_CONF_MODULE)
app.config_from_object(setting_object)
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

with app.pool.acquire(block=True) as conn:
    exchange = kombu.Exchange(
        name=settings.AMQP_PUBLISH_TO, type="topic", durable=True, channel=conn
    )
    exchange.declare()

    queue = kombu.Queue(
        name="my_queue",
        exchange=exchange,
        routing_key="my_queue.#",
        channel=conn,
    )
    queue.declare()

在我的芹菜任务中,我使用current_app,因为它是在工人上运行的。

@task
def my_task(attrs):
    # do something
    with current_app.producer_pool.acquire(block=True) as producer:
        producer.publish(
            body,
            routing_key="my_queue.test",
            exchange=settings.AMQP_PUBLISH_TO,
            retry=True,
       )

这对我来说真的很好。但是您真的不能成为我们CELERY_ALWAYS_EAGER。我有问题,因为那时并没有真正使用连接。所以我的测试需要写得更好一些,但是没关系。