RabbitMQ指南之五:主题交换器(Topic Exchange)
在上一章中,我们完善了我们的日志系统,用direct交换器替换了fanout交换器,使得我们可以有选择性地接收消息。尽管如此,仍然还有限制:不能基于多个标准进行路由。在我们的日志系统中,我们可能不仅希望根据日志等级订阅日志,还希望根据日志来源订阅日志。这个概念来自于unix工具syslog,它不仅可以根据日志等级(info/warn/crit...)来路由日志,同时还可以根据设备(auth/cron/kern...)来路由日志。这将更加灵活,我们可能希望只监听来自'cron'的error级别日志,同时又要接收来自'kern'的所有级别的日志。我们的日志系统如果要实现这个功能,就需要使用到另外一种交换器:主题交换器(Topic Exchange)。
1、主题交换器(Topic Exchange)
发送到主题交换器的消息不能有任意的routing key,必须是由点号分开的一串单词,这些单词可以是任意的,但通常是与消息相关的一些特征。比如以下是几个有效的routing key: "stock.usd.nyse", "nyse.vmw", "quick.orange.rabbit",routing key的单词可以有很多,最大限制是255 bytes。
binding key必须与routing key模式一样。Topic交换器的逻辑与direct交换器有点相似:使用特定路由键发送的消息将被发送到所有使用匹配绑定键绑定的队列,然而,绑定键有两个特殊的情况,如下:
- * 表示匹配任意一个单词
- # 表示匹配任意一个或多个单词
下图很好地表示这这两个通配符的用法:

在这个例子中,我们将发送所有跟动物有关的消息,这些消息将会发送到由三个单词,两个点号组成的routing key,第一个单词了表示的是速度,第二个单词表示颜色,第三个单词表示种类:
"<speed>.<colour>.<species>"。
我们创建三个绑定关系:队列Q1绑定到绑定键*.orange.* ,队列Q2绑定到*.*.rabbit和lazy.#。
总结下来就是:
- 队列Q1对橘黄色(orange)颜色的所有动物感兴趣;
- 队列Q2对所有的兔子(rabbit)和所有慢吞吞(lazy)的动物感兴趣。
一个路由为 "quick.orange.rabbit"的消息,将会被转发到这两个队列,路由为"lazy.orange.elephant"的消息也被转发给这两个队列,路由为 "quick.orange.fox"的消息将只被转发到Q1队列,路由为 "lazy.brown.fox"的消息将只被转发到Q2队列。"lazy.pink.rabbit" 只被转发到Q2队列一次(虽然它匹配绑定键*.*.rabbit和lazy.#),路由为 "quick.brown.fox"的消息与任何一个绑定键都不匹配,因此将会被丢弃。
如果我们发送的消息的的路由是由一个单词“orangle"或4个单词”quick.orangle.male.rabbit“将会怎样?会因为与任何一个绑定键不匹配而被丢弃。
另一方面,路由为 "lazy.orange.male.rabbit"的消息,因为匹配"lazy.#"绑定键,因而会被转发到Q2队列。
Topic交换器非常强大,可以像其他类型的交换器一样工作:
当一个队列的绑定键是"#"是,它将会接收所有的消息,而不再考虑所接收消息的路由键,就像是fanout交换器一样;
当一个队列的绑定键没有用到”#“和”*“时,它又像direct交换一样工作。
2、完整的代码
下面是在我们日志系统中采用Topic交换器的完整代码,我们要发送的日志消息的路由由两个单词组成:"<facility>.<severity>"。
EmitLogTopic.java
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class EmitLogTopic { private final static String EXCHANGE_NAME = "topic_logs"; public static void main(String[] args) throws Exception { ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost); try(Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) { channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC); String message = "A critical kernel error";
String routingKey = "kern.critical"; channel.basicPublish(EXCHANGE_NAME,routingKey,null,message.getBytes("utf-8")); System.out.println(" [x] Sent '" + routingKey + "':'" + message + "'");
}
}
}
ReceiveLogsTopic.java
import com.rabbitmq.client.*;
public class ReceiveLogsTopic {
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
String queueName = channel.queueDeclare().getQueue();
if (args.length < 1) {
System.err.println("Usage: ReceiveLogsTopic [binding_key]...");
System.exit(1);
}
for (String bindingKey : args) {
channel.queueBind(queueName, EXCHANGE_NAME, bindingKey);
}
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
System.out.println(" [x] Received '" +
delivery.getEnvelope().getRoutingKey() + "':'" + message + "'");
};
channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { });
}
}
启动4个接收者,分别传入绑定键:#、kern.*、*.critical、kern.* *.critical。
启动生产者:发送一条路由为“kern.critical”的消息,消息内容为:“A critical kernel error”,分别查看接收情况:


可以看到,所有绑定键的队列都正常接收到了消息。
3、SpringBoot实现
工程如下图:

一、生产者
application.properties
#RabbitMq
spring.rabbitmq.host=localhost
rabbitmq.exchange.topic=topic_logs2
rabbitmq.exchange.topic.routing.key=kern.critical
EmitLogTopic.java
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; @Component
public class EmitLogTopic { @Value("${rabbitmq.exchange.topic}")
private String exchangeName; @Value("${rabbitmq.exchange.topic.routing.key}")
private String routingKey; @Autowired
private AmqpTemplate template; public void sendMessage(Object message) {
System.out.println("发送消息:" + message);
template.convertAndSend(exchangeName,routingKey,message);
}
}
EmitLogTopicRunner.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component; @Component
public class EmitLogTopicRunner implements ApplicationRunner { @Autowired
private EmitLogTopic emitLogTopic; @Override
public void run(ApplicationArguments args) throws Exception {
emitLogTopic.sendMessage("A critical kernel error");
}
}
二、消费者
application.properties
#RabbitMq
spring.rabbitmq.host=localhost
rabbitmq.exchange.topic=topic_logs2
rabbitmq.topic.queue=topic_queue
rabbitmq.exchange.topic.binding.key=kern.critical server.port=8081
ReceiveLogsTopic.java
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Component; @Component
@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "${rabbitmq.topic.queue}",autoDelete = "true"),
exchange = @Exchange(value = "${rabbitmq.exchange.topic}",type = ExchangeTypes.TOPIC),
key = {"#","kern.*","*.critical"}
)
)
public class ReceiveLogsTopic { @RabbitHandler
public void reeive(Object message) {
System.out.println("接收到消息:" + message);
}
}
启动查看控制台输出:
生者产输出:

消费者输出:

至此,RabbitMq的主题交换器讲解完了,在下一章中将会讲解RabbitMq的RPC。
RabbitMQ指南之五:主题交换器(Topic Exchange)的更多相关文章
- RabbitMQ入门:主题路由器(Topic Exchange)
上一篇博文中,我们使用direct exchange 代替了fanout exchange,这次我们来看下topic exchange. 一.Topic Exchange介绍 topic exchan ...
- PHP 下基于 php-amqp 扩展的 RabbitMQ 简单用例 (二) -- Topic Exchange 和 Fanout Exchange
Topic Exchange 此模式下交换机,在推送消息时, 会根据消息的主题词和队列的主题词决定将消息推送到哪个队列. 交换机只会为 Queue 分发符合其指定的主题的消息. 向交换机发送消息时,消 ...
- rabbitmq 交换机模式 -主题模式 topic
建立一个交换机 tpc 并且绑定了各自的路由到 Q1 Q2 <?php require_once "./vendor/autoload.php"; use PhpAmqpLi ...
- RabbitMQ入门(5)——主题(Topic)
前面我们介绍了通过使用direct exchage,改善了fanout exchange只能进行虚拟广播的方式.尽管如此,直接交换也有自身的局限,它不能基于多个条件路由. 在我们的日志系统中,也许我们 ...
- RabbitMQ系列教程之五:主题(Topic)(转载)
RabbitMQ系列教程之五:主题(Topic) (本实例都是使用的Net的客户端,使用C#编写),说明,中文方括号[]表示名词. 在上一个教程中,我们改进了我们的日志记录系统. 没有使用只能够进行虚 ...
- 7.RabbitMQ系列之topic主题交换器
topic主题交换器它根据在队列绑定的路由键和路由模式通配符匹配将消息路由到队列. 生产者在消息头中添加路由键并将其发送到主题交换器. 收到消息后,exchange尝试将路由键与绑定到它的所有队列的绑 ...
- RabbitMQ (五)主题(Topic) -摘自网络
虽然使用direct类型改良了我们的系统,但是仍然存在一些局限性:它不能够基于多重条件进行路由选择. 在我们的日志系统中,我们有可能希望不仅根据日志的级别而且想根据日志的来源进行订阅.这个概念类似un ...
- RabbitMQ (五)主题(Topic)
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37706355 上一篇博客中,我们进步改良了我们的日志系统.我们使用direct类 ...
- RabbitMQ简单应用の主题模式(topic)
Topic exchange(主题转发器) 发送给主题转发器的消息不能是任意设置的选择键,必须是用小数点隔开的一系列的标识符.这些标识符可以是随意,但是通常跟消息的某些特性相关联.一些合法的路由选择键 ...
随机推荐
- COGS2479(四维偏序)
题意:给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 分析:cdq分治 ...
- html5视频播放器 二 (功能实现及播放优化)
样式改写css,其中的一些按钮是在“阿里妈妈”上找的字体图标,就不向上传了. /* *CoolPlay视频播放器 * 2016年8月1日 * 627314658@qq.com * */ @font-f ...
- MyBatis 3在Insert之后返回主键
XML: <insert id="addUser" parameterType="User" useGeneratedKeys="true&qu ...
- Access restriction: The type 'JPEGImageWriter' is not API
报错: Access restriction: The type 'JPEGImageWriter' is not API due to restriction on required library ...
- NYOJ 题目42 一笔画问题(欧拉图)
一笔画问题 时间限制:3000 ms | 内存限制:65535 KB 难度:4 描写叙述 zyc从小就比較喜欢玩一些小游戏.当中就包含画一笔画.他想请你帮他写一个程序.推断一个图是否可以用一笔画下 ...
- 踩坑录- Spring Boot - CORS 跨域 - 浏览器同源策略
1.解决办法,创建一个过滤器,处理所有response响应头 import java.io.IOException; import javax.servlet.Filter; import javax ...
- Windows Server2008 R2 设置NAT 让Hyper-V连接Internet
1.添加虚拟网卡,设置为内部,并且固定IP地址192.168.1.1 255.255.255.0 此为内网网卡 2.添加服务器角色:DHCP服务器,DNS服务器,网络策略和访问服务 3."网 ...
- (一)Java 入门教程
Java 入门教程 Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言. Java可运行于多个平台,如Windows, Mac OS,及其他多种UNIX版本的系统 ...
- 协议解析Bug分析
协议解析Bug分析 源自邮件协议RPC(远程过程调用)处理的Request请求数据包的bug. 一.Bug描写叙述 腾讯收购的Foxmailclient能够作为outlookclient ...
- Java 反射 —— 运行时的类型信息
1. 反射机制的由来 RTTI 机制可以告知某个对象的确切类型,但有一个前提,该类型在编译时必须已知(编译器在编译时打开和检查 .class 文件以获取类型信息).似乎是个很宽松的限制,但假如你获取了 ...