可靠的aio-pika连接到多个RabbitMQ主机

时间:2018-07-06 12:30:56

标签: rabbitmq python-asyncio pika reconnect

在我们的设置中,我们有一个运行在三台主机上的中央RabbitMQ实例,每台主机都有自己的URL。

出于维护目的,这些主机中的任何一个都可能在几个小时内随时关闭。发生这种情况时,我们想连接到其他主机之一。

我们一直在使用aio_pika.connect_robust进行连接,但是它仅接受单个主机作为参数。

如果重新连接可以在后台无缝进行,那将是完美的。工作者可以从连接发送到一台主机的消息,对其进行处理,然后通过另一个连接进行确认。

解决这个问题的最佳方法是什么?

2 个答案:

答案 0 :(得分:2)

  

工作人员可以从连接发送到主机的消息,对其进行处理,然后通过另一个连接进行确认

这是不可能的,因为袜子与渠道有关。当第一个频道关闭时,RabbitMQ将重新加入该消息并将其重新传递给另一个使用者。

似乎aio-pika不支持从中选择连接的多个主机。我建议您自己捕获与连接有关的异常以选择另一台主机,或者在您的应用程序和RabbitMQ之间放置haproxy。

答案 1 :(得分:0)

所以最后我找到了一种方法。正如我在对卢克的回答的评论中所述,我无法修复断开的频道。因此,在尝试确认消息之前,我选择保存一个worker的输出。当连接断开时,此操作将失败,因此该消息将永远不会得到确认,并将再次发送给工作程序,从而启动新的协程,然后可以重用先前的结果。

我希望这段代码能使内容更清楚:

def on_foo_message(connection_loss_event):
    async def context_aware_on_message(message: aio_pika.IncomingMessage):

        try:
            # Obtain a message-specific lock, so only one
            # coroutine can work on a message

            # Check if the result was already calculated
            # If yes, just ack here and return

            # Work on the message

            # Save the result

            # Ack the message

        except pika.exceptions.ConnectionClosed:
            # This can happen while interacting with channel or message
            connection_loss_event.set()

    return context_aware_on_message


async def worker():
    rabbit_urls = get_rabbitmq_connection_uris()
    url_index = 0

    while True:

        try:
            # Connect to rabbit
            url_index = url_index % len(rabbit_urls)
            url = rabbit_urls[url_index]
            connection = await aio_pika.connect(url)
            channel = await connection.channel()
            logger.info(f'Connected to rabbit at {url}')

            # Configure queues
            await channel.set_qos(prefetch_count=MAX_MESSAGES_IN_PARALLEL)
            foo_queue = await channel.declare_queue('foo', durable=True)
            bar_queue = await channel.declare_queue('bar', durable=True)

            # Start listening to queues
            connection_loss_event = asyncio.Event()
            await foo_queue.consume(
                on_foo_message(connection_loss_event))
            logger.info(f'Now listening to queue "foo"')
            await bar_queue.consume(
                on_bar_message(connection_loss_event))
            logger.info(f'Now listening to queue "bar"')

            # Wait for connection loss
            await connection_loss_event.wait()
            raise ConnectionRefusedError()

        except ConnectionRefusedError:
            logger.info('No connection to rabbit, will try next url')
            url_index += 1
            await asyncio.sleep(2)


def main():
    loop = asyncio.get_event_loop()
    loop.create_task(worker())
    loop.run_forever()    

if __name__ == '__main__':
    main()