For the last part of this short series, let’s take a glance at some conceptual differences between RabbitMQ and MSMQ from developer’s point of view. We can’t cover much in a single blog post, so let’s just mention few things to get you started.
Declaring queues and exchanges
Queues and exchanges in RabbitMQ are often temporary and not as long living as in MSMQ. What do I mean by that? In MSMQ, queue’s setup is usually multi-step process – you’re not only creating a queue, but also configuring its properties and access rights. Because of that it’s typical to do it from management console and not from code. Once set up, queue often remains in MSMQ “forever”, i.e. until we manually delete it.
With RabbitMQ, creating new queues can be simpler. I’ll just talk about queues here, but same things apply for exchanges as well. You just declare a queue, and it’s auto-created if doesn’t already exists. You don’t have to manually check whether a queue exists before the declaration. This alone doesn’t represent a significant saving over MSMQ way. Here’s where real difference lies – if you’re using policies to configure queue parameters, queue will automatically pick up these settings as it’s created. Also, user permissions are managed centrally too, with pattern-based matching. So, depending on how policies and user settings are set up, your newly created queue could be immediately configured as it should be as soon as it’s created. Except for bindings, unless we use default AMQP exchange.
RabbitMQ itself has support for temporary queues. We can declare a queue not to be durable, in which case it will exist only until the broker is restarted. Even more, we can set it to auto delete mode, where it gets automatically deleted as soon as it doesn’t have any more consumers. Or even use exclusive mode, where it can be used only from the same connection where it was created.
RabbitMQ .Net client (and probably others as well) supports automatic recovery. In that mode, if there’s a network problem and connection is dropped, client will reconnect, but also declare again all queues, exchanges, binding, etc. which were declared previously.
With MSMQ, you always communicated through local MSMQ service, even if destination was on another machine. MSMQ stored pending messages in outgoing queues and took care of failures and retries. With RabbitMQ it’s possible to communicate with broker over the network, so there’s a higher probability that something will go wrong and we have to take care of potential issues ourselves.
RabbitMQ has ack/nack mechanism which is used to communicate that message is dealt with correctly (ack – acknowledgment), or that there was some problem during operation (nack – negative acknowledgment). This is available both for sending and receiving messages. Also, some client libraries have built-in retry or reconnect mechanism. If not, we have to do it manually.
If you read Part 2 of this series where we discussed Exchanges, you know that we can’t send to a queue directly, we always send to exchange and then it passes messages around according to its parameters and bindings. There is a shortcut to this rule, by using AMQP default exchange which allows us to put destination name in routing key. Messages will go directly to that queue without us having to configure any bindings.
What happens if we send a message to an exchange and it turns out it’s unroutable? Either exchange doesn’t have any bindings, or its bindings have filters (e.g. by routing key, header property, etc) and our message doesn’t match any of them. That message will be lost. In some cases we’re ok with that. Maybe message contains some temporary information and if nobody needs that info currently, it’s not a loss if message is deleted. However, if message shouldn’t be lost, and we want to know about these cases, we can set up mandatory flag and we’ll get an error if it won’t reach any destination queue. Then we can deal with that situation.
Since “send” operation could involve multiple destination queues, even on multiple machines, RabbitMQ can give us confirmation on whether message reached all destination queues or not. We issue confirm.select operation and RabbitMQ gives as ack or nack. If response is nack, we can either abandon publishing that message or try sending it again. Since ack requires that message reaches all destinations, it might be too slow to wait for it after each message. RabbitMQ gives us delivery tag for each message we send, and since it’s autoincrementing number we can use that to ask for confirmation for multiple messages. Something like “please confirm that all messages up to this delivery tag are correctly published”.
Consuming messages from a queue
RabbitMQ doesn’t have Peek operation like MSMQ. When you take out a message from a queue using BasicGet operation, it will immediately be invisible to all other receivers. However, it doesn’t mean it’s completely removed from a queue. Message is permanently removed only after we send Ack. It’s advisable to send Ack at the end of entire processing, e.g. after database and other operations are completed.
If we Nack that message manually, or if Nack happens automatically because of a timeout, message will be requeued – it will be visible and available again in a queue. We can also tell RabbitMQ not to requeue and just delete it instead. There’s similar operation called reject (also with requeue or no requeue) but it works for a single message only. While ack/nack can be issued for multiple messages at once, thus increasing performance.
Receiving by subscription (Push)
BasicGet is a manual (pull) way of consuming messages. We can also subscribe to a queue and RabbitMQ client library will trigger our code when a message arrives. This is not only API difference, but RabbitMQ treats subscriptions differently. For instance, “auto delete” queue will be deleted when there are no more consumers. However, it works only for subscribed consumers, not for BasicGet mode.
Where to go next
There’s a lot more to RabbitMQ than we could cover in this short series. Fortunately, it has excellent documentation:
Although RabbitMQ management console is quite good, it offers only very basic view of messages, without any additional operations. That’s where QueueExplorer helps (yep, our own product) – the best way to work with RabbitMQ during development, testing, or production usage.
Links to all 6 parts of this series.