A topic exchange in RabbitMQ doesn’t just route messages; it filters them based on a pattern that’s more powerful than simple wildcards, allowing for complex routing logic that’s often misunderstood.
Let’s see it in action. Imagine you have a system that publishes events about different types of user activity. You want to route these events to different consumers based on the granularity of the activity.
First, we’ll create a topic exchange named user_activity_exchange.
rabbitmqadmin declare exchange name=user_activity_exchange type=topic
Now, let’s say we have three consumers:
- A consumer interested in all user activity.
- A consumer interested in all login-related activity.
- A consumer interested in all activity related to the
adminuser.
We’ll bind queues to this exchange with specific routing keys:
-
For the "all activity" consumer, we bind
all_activity_queuetouser_activity_exchangewith the routing key#. The#(hash) is a special wildcard in RabbitMQ topic exchanges that matches zero or more words.rabbitmqadmin declare queue name=all_activity_queue rabbitmqadmin declare binding source=user_activity_exchange destination=all_activity_queue routing_key=# -
For the "all login activity" consumer, we bind
login_activity_queuetouser_activity_exchangewith the routing keyuser.login.*. The*(asterisk) wildcard matches exactly one word in the routing key. So, this will matchuser.login.successanduser.login.failed, but notuser.login.attempt.rabbitmqadmin declare queue name=login_activity_queue rabbitmqadmin declare binding source=user_activity_exchange destination=login_activity_queue routing_key=user.login.* -
For the "admin user activity" consumer, we bind
admin_user_activity_queuetouser_activity_exchangewith the routing keyuser.*.admin. This will matchuser.login.adminanduser.logout.admin, but notuser.admin.loginoruser.activity.admin.rabbitmqadmin declare queue name=admin_user_activity_queue rabbitmqadmin declare binding source=user_activity_exchange destination=admin_user_activity_queue routing_key=user.*.admin
Now, let’s publish some messages:
-
Publish a message with routing key
user.login.success:rabbitmqadmin publish exchange=user_activity_exchange routing_key=user.login.success payload="Admin logged in successfully"This message will be routed to
all_activity_queue(because#matchesuser.login.success) andlogin_activity_queue(becauseuser.login.*matchesuser.login.success). It will not go toadmin_user_activity_queuebecauseuser.*.admindoes not matchuser.login.success. -
Publish a message with routing key
user.logout.admin:rabbitmqadmin publish exchange=user_activity_exchange routing_key=user.logout.admin payload="Admin logged out"This message will be routed to
all_activity_queue(because#matchesuser.logout.admin) andadmin_user_activity_queue(becauseuser.*.adminmatchesuser.logout.admin). It will not go tologin_activity_queuebecauseuser.login.*does not matchuser.logout.admin. -
Publish a message with routing key
user.profile.update:rabbitmqadmin publish exchange=user_activity_exchange routing_key=user.profile.update payload="User profile updated"This message will only be routed to
all_activity_queuebecause only#matchesuser.profile.update.
The mental model here is that a topic exchange treats routing keys as dot-separated words. The * wildcard matches a single word, and the # wildcard matches zero or more words. When a message is published, RabbitMQ compares the message’s routing key against the binding keys for all queues attached to the topic exchange. A queue receives a message if its binding key matches the message’s routing key according to these wildcard rules.
Crucially, the routing key doesn’t have to be a direct match. The power of topic exchanges lies in their ability to match patterns. A single message published with a specific routing key can be delivered to multiple queues if their respective binding keys match that routing key. This is how you achieve flexible fan-out and filtering based on message content characteristics.
The most surprising thing is how the # wildcard operates: it can match nothing. If you bind a queue with a routing key of just #, it will receive every message published to that exchange, regardless of what the routing key is. This is a common way to create a "catch-all" or logging queue.
Understanding the precise behavior of * and # is key to designing robust message routing systems.
The next concept to explore is how to handle message durability and dead-lettering within this topic-based routing setup.