如何订阅RabbitMQ通知消息?

时间:2014-01-25 18:43:41

标签: qt rabbitmq amqp

我正在开发一个Qt5服务器应用程序,我正在使用QAMQP库。 我想做的是以下几点:

  • 另一台服务器应该在发送有关用户的信息时发送消息 应该改变
  • 我的服务器分布在多台计算机上,每台计算机有多个进程需要通知这些更新

问题是,我不确定我应该构建的架构。我只知道无论什么时候某些用户发生变化,服务器都需要向RabbitMQ代理发送一条消息,所有对该特定用户的更新感兴趣的进程都应该收到消息。但是,我应该为每个进程创建一个队列,并将其与每个用户的单独交换绑定吗?或者可以在每个进程中为每个用户创建一个单独的队列,并以某种方式将其绑定到某个交换。我想到扇出交换,每个进程有一个队列,我只是不确定队列交换关系,即使我已经花了一些时间试图解决这个问题。

更新,以便澄清事情并写下进展

我有一个分布式应用程序,需要通知产品更改。这些变化经常发生,并由另一个平台跟踪。我想在我的应用程序中获取这些更新。 为了实现这一点,我的每个应用程序实例都创建了自己的队列。然后,只要某个实例对特定产品的更新感兴趣,它就会为该产品创建一个交换并将其绑定到队列,如下所示:

Exchange type : 'direct'
Exchange name : 'product_update'
Routing key   : 'PRODUCT_CODE'

其中PRODUCT_CODE是表示产品代码的字符串。在跟踪更改的平台中,我只是使用相应的交换发布消息。

当我需要取消订阅产品更新时,问题就出现了。我正在使用QAMQP库,并且在QAMQP :: Exchange的析构函数中有一个无条件的remove()调用。 当调用该函数时,我在RabbitMQ日志中出现错误,如下所示:

=ERROR REPORT==== 28-Jan-2014::08:41:35 ===
connection <0.937.0>, channel 7 - soft error:
{amqp_error,precondition_failed,
            "exchange 'product_update' in vhost 'test-app' in use",
            'exchange.delete'}

我不确定如何正确取消订阅。我从RabbitMQ Web界面知道我只有一个交换('product_update'),它具有绑定到具有不同路由键的多个队列。 我可以看到QAMQP中对remove()的调用试图删除交换,但由于它被我的其他进程使用,它仍在使用中并且无法删除,我相信它是可以的。 但是我该怎么做才能删除我创建的交换对象?我应该先从队列中取消绑定吗?我相信我应该能够在不调用remove()的情况下删除该对象,但我可能会弄错或者我可能做错了。

另外,如果我想要实现的目标有更好的模式,请提供建议。

以下是每个请求的一些示例代码。

ProductUpdater::ProductUpdater(QObject* parent) : QObject(parent)
{
    mClient = new QAMQP::Client(this);
    mClient->setAutoReconnect(true);
    mClient->open(mConnStr);
    connect(mClient, SIGNAL(connected()), this, SLOT(amqp_connected()));
}

void ProductUpdater::amqp_connected()
{
    mQueue = mClient->createQueue();

    connect(mQueue, SIGNAL(declared()),   this, SLOT(amqp_queue_declared()));
    connect(mQueue, SIGNAL(messageReceived(QAMQP::Queue*)),
               this, SLOT(message_received(QAMQP::Queue*)));

    mQueue->setNoAck(false);
    mQueue->declare(QString(), QAMQP::Queue::QueueOptions(QAMQP::Queue::AutoDelete));
}

void ProductUpdater::amqp_queue_declared()
{
    mQueue->consume();
}

void ProductUpdater::amqp_exchange_declared()
{
    QAMQP::Exchange* exchange = qobject_cast<QAMQP::Exchange*>(sender());
    if (mKeys.contains(exchange))
        mQueue->bind(exchange, mKeys.value(exchange));
}

void ProductUpdater::message_received(QAMQP::Queue* queue)
{
    while (queue->hasMessage())
    {
        const QAMQP::MessagePtr message = queue->getMessage();
        processMessage(message);

        if (!queue->noAck())
            queue->ack(message);
    }
}

bool ProductUpdater::subscribe(const QString& productId)
{
    if (!mClient)
        return false;

    foreach (const QString& id, mSubscriptions) {
        if (id == productId)
            return true; // already subscribed
    }

    QAMQP::Exchange* exchange = mClient->createExchange("product_update");
    mSubscriptions.insert(productId, exchange);
    connect(exchange, SIGNAL(declared()), this, SLOT(amqp_exchange_declared()));
    exchange->declare(QStringLiteral("direct"));

    return true;
}

void ProductUpdater::unsubscribe(const QString& productId)
{
    if (!mSubscriptions.contains(productId))
        return;

    QAMQP::Exchange* exchange = mSubscriptions.take(productId);

    if (exchange) {
        // This may even be unnecessary...?
        mQueue->unbind(exchange, productId);

        // This will produce an error in the RabbitMQ log
        // But if exchange isn't destroyed, we have a memory leak
        // if we do exchange->deleteLater(); it'll also produce an error...
        // exchange->remove();
    }
}

1 个答案:

答案 0 :(得分:1)

艾米,

我认为您的疑问与RabbitMQ可用的消息分发方式(或模式)和交换类型有关。所以,我将尝试通过一个简短的解释来覆盖所有这些,你可以决定哪个最适合你的场景(RabbitMQ教程以另一种方式解释)。

工作队列

One queue, multiple consumers - They race for the messages

使用默认交换绑定密钥,您可以直接在队列中发布消息。一旦消息到达队列,消费者“竞争”以获取消息,这意味着消息不被传递给多个消费者。如果有多个消费者在监听单个队列,则消息将以round-robin方式传递。

当您有工作要做并希望轻松扩展多个服务器/进程时,请使用此方法。

发布/订阅

One queue per consumer, one message is replicated for all consumers listening

在此模型中,一条发送的消息可能会触及许多消费者在队列中收听。对于此方案,您必须无选择地所有消费者发送消息,您可以使用扇出交换。这些交流是“愚蠢的”,就像他们的名字暗示一样:就像一个粉丝。有一件事进入并且没有任何智能地复制到绑定到交换的所有队列。您也可以使用直接交换,但前提是您需要对消息进行任何过滤或路由。

当您遇到类似事件的情况时使用此方案,您可能需要多个服务器,进程和使用者来处理该事件,< strong>每个人都在做一个不同性质的任务来处理事件。如果您不需要任何过滤器/路由,请在此方案中使用扇出交换。

路由/主题

enter image description here

发布/订阅模型的特殊情况,您可以使用过滤器在交换机上“侦听”队列,这些过滤器可能具有模式匹配(主题)或不具有模式匹配(仅路由)。

如果您需要模式匹配,请使用主题交换类型。如果不这样做,请使用直接。 当队列“侦听”交换时,使用绑定。在此绑定中,您可以指定绑定密钥

要将邮件传递到正确的队列,交换机会检查邮件的路由键。如果它与绑定键匹配,则将消息转发到该队列。匹配策略取决于您使用主题或直接交换,如前所述。

TL; DR:

对于您的方案,如果每个流程使用用户更改事件执行不同的操作,请使用带有 扇出 类型的单个交换。每个类的处理程序声明绑定到该交换的相同队列名称。这与上面的发布/订阅模型有关。您可以将工作分配给同一类的消费者,并监听相同的队列名称,即使它们不在同一个进程中。

但是,如果对事件感兴趣的所有消费者在处理时执行相同的任务,请使用工作队列模型。

希望这有帮助,