主题(topic)
在上一个教程中我们改善了我们的日志系统。我们使用direct
类型的exchange,可以选择性的接收日志消息,不是像fanout
类型的exchange那样,单纯的将消息广播到所有的消费者。
即使使用direct
类型的exchange改善了我们的系统,但是它还是有缺陷的 —— 它不能基于多条件进行路由。
在我们的日志系统中,我们可能不仅仅想根据严重性进行订阅,也希望能基于发送日志的来源进行订阅。这个概念来自于unix的工具syslog
,根据日志的严重性程度(info/warn/crit...)和设备(auth/cron/kern...)进行路由。
这样能给我们很大的灵活性 - 我们可能只想监听来自cron
的致命错误日志和来自kern
的所有日志。
为了实现我们的日志系统,我们需要学习更复杂的topic
类型的exchange。
主题交换机(topic exchange)
消息发送到topic
类型的exchange不可能是任意的routing_key - 它必须是一串单词,以.
号隔开。这些单词可以是任意的,但是通常它们指定一些特性跟消息关联起来。一些有效的routing key 例子:stock.usd.nyse
,nyse.vmw
,quick.orange.rabbit
。只要你开心,routing key 可以是很多个单词,最多255个字节。
binding key 必须也是相同的形式。topic
exchange背后的逻辑和direct
是类似的 —— 一个携带特定routing key的消息将被交付到所有绑定了binding key的相匹配的队列。然而 binding key 有两个重要的特殊情况:
*
可以匹配一个完整的单词。
#
可以匹配一个或多个单词。
下图可以很容易的解释:
在上图中,我们将发送关于描述动物的消息。这个消息的routing key包含三个单词(两个.
)。routing key中的第一个单词描述速度,第二个单词描述颜色,第三个描述物种:
。
我们创建了三个绑定:Q1绑定的binding key为*.orange.*
,Q2的就是*.*.rabbit
和lazy.#
.
这些绑定可以总结为:
- Q1对橙色的动物感兴趣
- Q2想知道兔子的一切和懒惰的动物的一切
下面是随着消息的routing key不同被交付到队列也不同的例子:
-
quick.orange.rabbit
将交付给所有的队列 -
lazy.orange.elephant
也将交付到两个队列 -
quick.orange.fox
将只交付给Q1 -
lazy.brown.fox
交付到Q2 -
lazy.pink.rabbit
匹配了Q2中的两个条件,但是只交付一次 -
quick.brown.fox
没有匹配的绑定,将被丢弃
如果我们打破我们的约定,发送一个routing key为一个词或者四个词的消息将会发生什么?例如:orange
,quick.orange.male,rabbit
。这些消息都不匹配,都将被丢弃。
但是也有例外,lazy.orange.male.rabbit
虽然有四个词,但是它和最后一个绑定匹配,将被交付到第二个队列。
Topic exchange
topic exchange 是非常强大的,可以实现其他类型的exchange。
当一个队列绑定了
#
的binding key - 它将接收所有的消息,不用管routing key - 类似于fanout
exchange。
当
*
和#
都没有用在绑定中,topic
exchange相当于direct
exchange。
信息汇总
我们将使用topic
exchange实现我们的日志系统。我们假设routing key 有两个词:
。
代码和上个教程差不多。官网设置了五种binding key,分别如下:
#
kern.*
*.critical
-
kern.*
和*.critical
kern.critical
我们就发送五条消息,消息分别对应下面的routing key:
- kern.critical
- kern.error
- cron.critical
- auth.warn
- cron.info.rolling
下面是代码实现和运行结果:
EmitLogTopic.java
package com.roachfu.tutorial.rabbitmq.website.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* topic exchange 生产者
*/
public class EmitLogTopic {
private static final String EXCHANGE_NAME = "topic.log";
private static final String[] routingKeys = {"kern.critical", "kern.error", "cron.critical", "auth.warn", "cron.info.rolling"};
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
for (String routingKey : routingKeys) {
channel.basicPublish(EXCHANGE_NAME, routingKey, null, routingKey.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + routingKey + "':'" + routingKey + "'");
}
}
}
ReceiveLogTopicAll.java
package com.roachfu.tutorial.rabbitmq.website.topic;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 接收所有消息:#
*/
public class ReceiveLogTopicAll {
private static final String EXCHANGE_NAME = "topic.log";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "topic");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "#");
System.out.println(" [*] Waiting for all the logs. . .");
Consumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(" [x] Received '" + envelope.getRoutingKey() + "':'" + message + "'");
}
};
channel.basicConsume(queueName, consumer);
}
}
以下消费者只贴出不同代码
ReceiveLogTopicFromFacility.java
channel.queueBind(queueName, EXCHANGE_NAME, "kern.*");
System.out.println(" [*] Waiting for all the logs from the facility 'kern'. . .");
ReceiveLogTopicAboutCritical.java
channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");
System.out.println(" [*] Waiting for all the logs about 'critical' logs. . .");
ReceiveLogTopicMultipleBinding.java
channel.queueBind(queueName, EXCHANGE_NAME, "*.critical");
System.out.println(" [*] Waiting for all the logs by multiple bindings. . .");
ReceiveLogTopicFullMatch.java
channel.queueBind(queueName, EXCHANGE_NAME, "kern.critical");
System.out.println(" [*] Waiting for all the logs from the facility 'kern'. . .");
运行结果
我们还是先开启消费者,然后再开启生产者。得到如下结果:
EmitLogTopic
ReceiveLogTopicAll
ReceiveLogTopicFromFacility
ReceiveLogTopicAboutCritical
ReceiveLogTopicMultipleBinding
RecevieLogTopicFullMatch