topic类型交换机的routingKey
向topic类型的交换机投递信息时设置的routingKey不能由单个标识符组成,必须由”.”分隔的若干个标识符组成(总长度不能大于255个字节)。下面第一个示例是非法的,第二个是合法的:
非法:
//声明一个topic类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String routingKey="apple";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
合法:
//声明一个topic类型的交换机
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String routingKey="small.apple";
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
routingKey的匹配规则
topic类型的交换机向队列投递信息和direct类型的交换机在逻辑上是相似的,只会将消息投递给绑定的routingKey和发布消息设置的routingKey相匹配的队列。与direct类型的交换机不同的是,topic类型的交换机在队列绑定routingKey时可以使用模式匹配:
* 匹配一个标识符
# 匹配0个或多个标识符
例如:
我们假设routingKey由3部分组成:<speed>.<colour>.<species>(分别为速度,颜色和物种)。队列Q1绑定的routingKey为”*.orange.*”,队列Q2绑定的routingKey为”*.*.rabbit” 和”lazy.#”。这样,队列Q1可以接收所有颜色为橙色(orange)的物种的消息,Q2则可以接收所有物种为兔子(rabbit)以及速度慢(lazy)的物种的消息。
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String routingKey=...;
channel.basicPublish(EXCHANGE_NAME, routingKey, null, message.getBytes());
下面举例说明投递消息设置的routingKey为不同值时,队列Q1和Q2的接收消息的情况:
quick.orange.rabbit
消息会被转发到两个队列lazy.orange.elephant
消息也被转发到两个队列quick.orange.fox
只会被转发到Q1lazy.brown.fox
被转发到Q2lazy.pink.rabbit
虽然与Q2的绑定两个routingKey匹配,但是只会被转发到Q2一次quick.brown.fox
不匹配,被丢弃orange
一个标识符的routingKey违反了约定,不能与任何绑定键匹配,被丢弃quick.orange.male.rabbit
四个标识符违反了约定,被丢弃lazy.orange.male.rabbit
虽然是四个标识符,但可以与lazy.#匹配,从而转发至Q2
PS: 上面的例子来自rabbitmq的官方教程
可以看到,topic类型的交换机是非常强大和灵活的,我们可以通过topic类型的交换机来实现其他类型的交换机。例如,如果队列绑定的routingKey使用“#”可以实现fanout类型的交换机,routingKey不包含”#”和”.”则相当于direct类型的交换机。
示例
假设我们的系统有两个模块:web模块和admin模块。两个模块都有info、warning、error三种级别的日志。EmitLog随机发送web和admin模块三种级别的日志,PrintLog将两个模块的info和warning日志打到控制台,SaveLog将两个模块的error日志保存到文件。
EmitLog.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Random;
public class EmitLog {
private static final String EXCHANGE_NAME = "topic_logs";
private static final String[] LOG_MODULE_LEVEL = {"web.info", "web.warning", "web.error","admin.info", "admin.warning", "admin.error"};
private static final Random random = new Random();
public static void main(String[] args) {
Connection connection = null;
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String logLevel = getLogLevel();
String log = "this is a log with the level:" + logLevel;
channel.basicPublish(EXCHANGE_NAME, logLevel, null, log.getBytes());
System.out.println("send the log:" + log);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
try {
connection.close();
} catch (IOException e) {
// ignore this
}
}
}
}
/**
* 随机获取日志级别
*/
private static String getLogLevel() {
return LOG_MODULE_LEVEL[random.nextInt(3)];
}
}
PrintLog.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class PrintLog {
private static final String EXCHANGE_NAME = "topic_logs";
private static final String[] ROUTING_KEYS = {"*.info", "*.warning"};
public static void main(String[] args) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
for (String routingKey : ROUTING_KEYS) {
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
}
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
System.out.println("waiting for logs....");
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String log = new String(delivery.getBody());
String routingKey = delivery.getEnvelope().getRoutingKey();
//打印日志
System.out.println("receive the log:" + log);
System.out.println("routingKey is:" + routingKey);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SaveLog.java
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
public class SaveLog {
private static final String EXCHANGE_NAME = "topic_logs";
private static final String[] ROUTING_KEYS = {"*.error"};
public static void main(String[] args) {
try {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
for (String routingKey : ROUTING_KEYS) {
channel.queueBind(queueName, EXCHANGE_NAME, routingKey);
}
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
System.out.println("waiting for logs....");
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String log = new String(delivery.getBody());
String routingKey = delivery.getEnvelope().getRoutingKey();
//保存日志到文件(模拟)
System.out.println("save the log:" + log);
System.out.println("routingKey is:" + routingKey);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
参考资料:rabbitmq官方教程5