RabbitMQ:具有主题交换的持久消息


68

我对RabbitMQ非常陌生。

我已经建立了一个“主题”交流。消费者可以在发布者之后开始。我希望消费者能够接收在启动之前发送的,尚未使用的消息。

交换使用以下参数设置:

exchange_type => 'topic'
durable => 1
auto_delete => 0
passive => 0

消息使用以下参数发布:

delivery_mode => 2

消费者使用get()从交易所检索消息。

不幸的是,任何客户端启动之前发布的任何消息都将丢失。我使用了不同的组合。

我想我的问题是交易所不保存消息。也许我需要在发布者和使用者之间排队。但这似乎不适用于通过密钥路由消息的“主题”交换。

我应该如何进行?我使用Perl绑定Net :: RabbitMQ(无关紧要)和RabbitMQ 2.2.0。


您可以检查出RabbitMQ的话题交换此视频- youtu.be/0dXwJa7veI8?t=1496
Jeethesh Kotian

Answers:


77

如果在发布消息时没有可用的连接使用者来处理消息,则需要一个持久队列来存储消息。

交换不存储消息,但队列可以存储。令人困惑的部分是,交流可以被标记为“耐用”,但所有的真正的意思是,交易所本身仍然会在那里,如果你重新启动你的经纪人,但它意味着发送到该交换的任何消息都自动持久。

鉴于此,这里有两个选择:

  1. 在启动发布者自行创建队列之前,请执行管理步骤。您可以使用Web UI或命令行工具来执行此操作。确保将其创建为持久队列,以便即使没有活动的使用者也可以存储路由到该队列的所有消息。
  2. 假设您的使用者被编码为始终在启动时声明(并自动创建)他们的交换和队列(并且他们声明它们是持久的),那么在启动任何发布者之前,至少运行所有使用者一次。这样可以确保正确创建所有队列。然后,您可以关闭使用者,直到真正需要它们为止,因为队列将永久存储路由到他们的任何将来的消息。

我会去#1。可能没有太多要执行的步骤,您可以始终编写所需步骤的脚本,以便可以重复执行。另外,如果您所有的消费者都将从同一个队列(而不是每个队列都有一个)中拉出,那实际上是最小的管理开销。

队列是需要适当管理和控制的东西。否则,您最终可能会导致流氓消费者声明持久队列,将它们使用几分钟,但再也不会使用。不久之后,您将拥有一个永久增长的队列,没有任何减小其大小的队列,以及即将发生的经纪人启示。


好的,因此解决方案是在发布者脚本中声明固定的客户端队列。当然,这需要我预先知道会有多少消费者。
朱利安

4
假设每个使用者都需要自己的队列,这是事实。但是,您需要回答的主要问题是:“这些消费者是否需要您曾经出现过的所有历史消息?”。如果他们不关心旧消息,则可以在启动时声明自己的队列,并从那时起接收所有消息,但是没有旧消息了。
布莱恩·凯利

4
应用程序“声明”队列,然后MQ代理(如果它们尚不存在)创建它们。尽管对于侦听器应用程序声明队列而不是发送器应用程序有意义,但是您会遇到所看到的问题。在运行应用程序之前,声明队列,声明交换,创建虚拟主机等可能是最好的解决方案。
迈克尔·狄龙

19

正如Brian所提到的,交换机不存储消息,并且主要负责将消息路由到另一个交换机或队列。如果交换未绑定到队列,则发送到该交换的所有消息都将“丢失”。

您无需在发布者脚本中声明固定的客户端队列,因为这可能不可扩展。队列可以由您的发布者动态创建,并使用交换到交换绑定在内部进行路由。

RabbitMQ支持交换到交换绑定,这将允许拓扑灵活性,去耦和其他好处。您可以在RabbitMQ Exchange到Exchange绑定[AMPQ]上阅读更多内容。

RabbitMQ交换到交换绑定

拓扑示例

如果没有使用队列的使用者,则示例Python代码将创建具有持久性的交换到交换绑定。

#!/usr/bin/env python
import pika
import sys
 
 
connection = pika.BlockingConnection(pika.ConnectionParameters(
host='localhost'))
channel = connection.channel()
 
 
#Declares the entry exchange to be used by all producers to send messages. Could be external producers as well
channel.exchange_declare(exchange='data_gateway',
exchange_type='fanout',
durable=True,
auto_delete=False)
 
#Declares the processing exchange to be used.Routes messages to various queues. For internal use only
channel.exchange_declare(exchange='data_distributor',
exchange_type='topic',
durable=True,
auto_delete=False)
 
#Binds the external/producer facing exchange to the internal exchange
channel.exchange_bind(destination='data_distributor',source='data_gateway')
 
##Create Durable Queues binded to the data_distributor exchange
channel.queue_declare(queue='trade_db',durable=True)
channel.queue_declare(queue='trade_stream_service',durable=True)
channel.queue_declare(queue='ticker_db',durable=True)
channel.queue_declare(queue='ticker_stream_service',durable=True)
channel.queue_declare(queue='orderbook_db',durable=True)
channel.queue_declare(queue='orderbook_stream_service',durable=True)
 
#Bind queues to exchanges and correct routing key. Allows for messages to be saved when no consumer is present
channel.queue_bind(queue='orderbook_db',exchange='data_distributor',routing_key='*.*.orderbook')
channel.queue_bind(queue='orderbook_stream_service',exchange='data_distributor',routing_key='*.*.orderbook')
channel.queue_bind(queue='ticker_db',exchange='data_distributor',routing_key='*.*.ticker')
channel.queue_bind(queue='ticker_stream_service',exchange='data_distributor',routing_key='*.*.ticker')
channel.queue_bind(queue='trade_db',exchange='data_distributor',routing_key='*.*.trade')
channel.queue_bind(queue='trade_stream_service',exchange='data_distributor',routing_key='*.*.trade')

2
“吃完所有邮件”队列丢失了,
据我看

解释一下?它肯定回答了OP的问题并起作用。对您的评论更具建设性
Skillachie '16

2
这实际上是可行的@KurtPattyn和@flyer,因为您随时可以创建新的使用者,Eat All Messages从而可以从那里“恢复”未处理的消息,并将其路由到正确的位置
Kostanos

1
@Kostanos所说的,只是补充说:恢复使用方一定不要消耗消息(没有自动确认,一旦看到所有消息,就关闭与该队列的连接)。这样,您可以将Rabbitmq用作事件存储-不确定它们是否打算这样做。
mbx

这“闻”。如mbx所写,这将rabbitmq配置为一种事件存储,恕我直言,这不是应该使用的方式。而是考虑将Kafka用于您的用例。Brian Kelly的回答完美地说明了这一点。
伯恩德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.