TL;DR
- Scenario: Need to route messages by “multi-dimensional tags” (e.g., source+level/region+type)
- Conclusion: Topic implements fine-grained distribution through routingKey (dot-separated words) and bindingKey (* /#) pattern matching. Unmatched messages are directly discarded
RabbitMQ Topic Mode
When using topic type exchange, queues bind to exchange via bindingKey, where bindingKey can contain wildcards for flexible matching. When routing messages, exchange performs pattern matching between message’s routingKey and queue’s bindingKey, achieving finer-grained message distribution.
Wildcard Rules
*(asterisk): Matches exactly one word#(hash): Matches zero or more words
Application Scenario Examples
Assuming the following requirements:
- Need to receive all error level logs from cron service
- Need to receive all level logs from kern service
- Need to receive critical level logs from all services
Corresponding binding relationships:
- Queue1 binding key:
cron.error - Queue2 binding key:
kern.* - Queue3 binding key:
*.critical
Message routingKey matching results:
cron.error→ Queue1kern.info→ Queue2auth.critical→ Queue3
RoutingKey Format Specification
-
Format Requirements:
- Must use dotted-word notation
- Each word separated by dot (.)
- Total string length must not exceed 255 bytes
-
Naming Examples:
- Finance domain:
stock.usd.nyse - Automotive industry:
nyse.vwm - General example:
quick.orange.rabbit
- Finance domain:
Binding Matching Rules Detail
Q1 binds to *.orange.*
- Matches:
quick.orange.fox,lazy.orange.elephant - Does not match:
orange.quick.fox(first word not wildcard),quick.orange(missing third word)
Q2 binds to *.*.rabbit and lazy.#
*.*.rabbit: Matchesquick.orange.rabbit,lazy.brown.rabbitlazy.#: Matcheslazy,lazy.orange,lazy.orange.male.rabbit
When No Match
If message’s routingKey doesn’t match any binding (e.g., quick.brown.fox), that message is directly discarded and won’t enter any queue.
Topic Exchange Behavior Analogy
- Similarity to Fanout Exchange: If bindingKey uses
#(e.g.,#orlazy.#), exchange broadcasts all messages to matching queues. - Similarity to Direct Exchange: If bindingKey doesn’t use
*or#at all (e.g.,orange.rabbit), exchange exactly matches routingKey.
Example Scenarios
| routingKey | Matching Queues |
|---|---|
quick.orange.rabbit | Q2 |
lazy.orange.elephant | Q1 and Q2 |
orange.rabbit | No match, discarded |
lazy.brown.rabbit | Q2 |
Java Code Examples
EmitLogTopic (Producer)
package icu.wzk;
public class EmitLogTopic {
private static final String EXCHANGE_NAME = "topic_logs";
private static final String[] SPEED = {"lazy", "quick", "normal"};
private static final String[] COLOR = {"black", "orange", "red", "yellow", "blue", "white", "pink"};
private static final String[] SPECIES = {"dog", "rabbit", "chicken", "horse", "bear", "cat"};
private static final Random RANDOM = new Random();
public static void main(String[] args) throws IOException, TimeoutException {
// 1) Configure connection
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setVirtualHost("/");
factory.setUsername("admin");
factory.setPassword("secret");
factory.setPort(5672);
// 2) Establish connection and Channel
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 3) Declare topic exchange
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
// 4) Loop to send 10 random messages
for (int i = 0; i < 10; i++) {
String speed = pick(SPEED);
String color = pick(COLOR);
String species = pick(SPECIES);
String message = speed + "-" + color + "-" + species;
String routingKey = speed + "." + color + "." + species;
channel.basicPublish(
EXCHANGE_NAME,
routingKey,
null,
message.getBytes(StandardCharsets.UTF_8)
);
System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
}
}
}
private static String pick(String[] arr) {
return arr[RANDOM.nextInt(arr.length)];
}
}
ReceiveLogsTopic (Consumer)
package icu.wzk;
public class ReceiveLogsTopic {
private static final String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
factory.setPort(5672);
factory.setVirtualHost("/");
factory.setUsername("admin");
factory.setPassword("secret");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = channel.queueDeclare().getQueue();
String bindingKey = "*.*.rabbit";
channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
DeliverCallback callback = (consumerTag, delivery) -> {
String body = new String(delivery.getBody(), StandardCharsets.UTF_8);
System.out.println(bindingKey + " matched message: " + body);
};
boolean autoAck = true;
channel.basicConsume(queueName, autoAck, callback, consumerTag -> {});
}
}
Error Quick Reference
| Symptom | Root Cause | Fix |
|---|---|---|
| Producer sends successfully, consumer can’t receive | routingKey and bindingKey have no match | Unify routingKey naming conventions |
| NOT_FOUND - no exchange | Exchange doesn’t exist or vhost inconsistent | First exchangeDeclare |
| PRECONDITION_FAILED | Same name exchange type different | Use new exchange name |
| Messages lost after consumer restart | autoAck=true, crash before processing | Set autoAck=false, basicAck after success |
| Wildcard in middle of word doesn’t work | Wildcard must be independent word | Change to dot-separated word structure |
| Consumer temporary queue disappears | exclusive/auto-delete | Explicitly declare fixed queue name + durable |
Notes
- Wildcards must appear as independent words, cannot be embedded within words
- Matching is case-sensitive
- Empty string cannot be used as routingKey
- Dots cannot appear at beginning or end