Exchanges are a new concept for MSMQ users. In RabbitMQ you can’t send to a queue directly, only through an exchange. Exchange is some sort of router – it determines which messages go where. In this process messages could be multiplied, i.e. single source message could end up in multiple destination queues. This routing is configured using bindings. Binding is a way to tell to exchange in more details how messages are distributed to destination queues. Since bindings can be reconfigured at a runtime, this brings us great flexibility in how messages are processed.
The simplest case is if an exchange is configured to just transfer all incoming messages to a single destination queue. That gives us more or less same functionality we had in MSMQ. However, exchanges can handle more complex scenarios which were not natively available in MSMQ. RabbitMQ can filter messages based on some properties, and then route them to different destination queues. For example, if our message contains “payment method” value, we can configure exchange to send credit card payments to one queue, and wire transfers to another one.
Let’s see what bindings are and how they work with different kind of exchanges RabbitMQ supports.
Bindings are the way to describe to RabbitMQ how to connect exchanges and queues. Each exchange has a list of bindings (zero or more). Single binding connects exchange to a single destination – if you want to connect 3 queues, you’ll have to create 3 bindings. Apart from a queue, destination can also be another exchange. If it’s another exchange, it’s possible that some message will go through multiple exchanges until it reaches final queue.
There are 4 types of exchanges in RabbitMQ, and they differ on how routing is performed, and whether it depends on message content or not. Message routing (i.e. determining destination) can depend on following factors:
- type of exchange
- content of the message – its routing key or header properties
If single message is routed to multiple destination queues, each of these queues will receive its own copy of message. So if it’s routed to 3 queues, one message coming into exchange will become 3 messages going out of it, one in each of these queues.
There are 4 different types of exchanges: Fanout, Direct, Topic, and Headers. You can’t change that after exchange is created.
Fanout – all messages go to all destinations
This kind of exchange works the same for all messages that come in – they are all routed to all destinations specified in bindings. Content of message makes no difference. If there’s only one binding then it’s same functionality like MSMQ queue. However if there are multiple bindings, copy of message will go to each destination.
Direct – exact match of routing key
Direct exchanges take into account message’s routing key when determining where it will go. Routing key is a field in a message, which you can set to an arbitrary value when message is sent. Since routing key is under your control, you can set it to what makes sense for your application.
What happens when a message arrives to direct exchange? Exchange will check its bindings. Each binding in this case has additional “routing key” parameter. If that parameter matches routing key from a message, that binding will be used and message will be sent to destination. If RK is not matched, that binding will be ignored.
Therefore direct exchanges allow you to filter messages by routing key, and to send them to different destinations based on that. For example if you put payment method into routing key , you can configure that “CreditCard” messages go to one queue, and “WireTransfer” to another.
Let’s see how it works, using QueueExplorer. We created exchange called “Billing” and created two bindings in it, each for specific routing key:
Now we’ll drag&drop two invoices from Invoices queue to Billing exchange. We can hold Ctrl so that original messages are not removed from source queue. Note that one message has “CreditCard” and another one “WireTransfer” routing key.
If we now check these two destination queues, which were set as destinations for our exchange, we’ll see that one message went to one queue and one to another, based on routing key:
And here’s “WireTransferKey”, and message which ended up there because of its “WireTransfer” routing key:
Topic – pattern match of routing key
Topic exchanges are similar to direct, but they use patterns instead of exact matching. Pattern is a list of words separated by dots – aaa.bbb.ccc. Similar to direct exchanges, pattern is not specified on exchange level, but for each of binding separately.
If you just specify simple pattern, it will work same as a direct exchange – exact match is required. However, pattern can contain * which replaces exactly one word, or # to replace zero or more words.
For example, if binding pattern is aaa.*, it will match messages with routing keys aaa.bbb, aaa.ccc, but not aaa.bbb.ccc. However, if pattern is aaa.#, it will also match aaa.bbb.ccc.
Pattern matching is essentially a way to use multiple variables in your messages, and then match one or more of them independently. For example, let’s say that sender emits log messages, and that each log entry has a value for “subsystem” (e.g. ordering/billing/shipping) and severity (debug/info/error). You can put both these values to routing key, so one message would have “ordering.debug”, and another “billing.info”. With patterns, you can match by subsystem, by severity, or by both values if you want to. You can redirect all “error” messages regardless of subsystem to one queue (*.error). Or all “billing” messages to another one (billing.*). Or use “shipping.info” to filter only “info” messages from “shipping” subsystem.
Headers – exact match of headers
For this kind of exchange, routing key is not consulted at all. Instead, it tries to match values in message headers. Since headers are key-value pairs, both key and value must match key-value specified in a binding.
Since message can have multiple header values, you can choose whether they ALL should be exactly as those specified in a binding, or whether it’s enough that any single of them is matched. This can be specified by adding special “x-match” argument to binding, with value “any” or “all”. “all” is the default value if x-match is not specified.
There are some exchanges that are automatically created by RabbitMQ. You can use these “system exchanges” if you don’t want to create your own. There’s a separate exchange for each exchange type we discussed previously. Their names start with “amq.”, so there are amq.fanout, amq.direct, amq.topic, and amq.headers. There are few more for other purposes. They can’t be deleted, but you configure their bindings.
There’s also one special exchange without a name, which is displayed as “(AMQ default)” in management tools. It’s a “direct” type of exchange. Specific for this exchange is that it has automatic built-in bindings for each queue. When you add a queue, it’s also automatically added to a binding list, with routing key same as the name of the queue. Therefore this exchange is a shortcut for sending messages directly to any queue you want – just send it there and set routing key to a name of destination queue. This is closest you can get to MSMQ queue.
In part 3 we’ll talk more about queues.
Links to all 6 parts of this series.