在上一章中,我们完善了我们的日志系统,用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)的更多相关文章

  1. RabbitMQ入门:主题路由器(Topic Exchange)

    上一篇博文中,我们使用direct exchange 代替了fanout exchange,这次我们来看下topic exchange. 一.Topic Exchange介绍 topic exchan ...

  2. PHP 下基于 php-amqp 扩展的 RabbitMQ 简单用例 (二) -- Topic Exchange 和 Fanout Exchange

    Topic Exchange 此模式下交换机,在推送消息时, 会根据消息的主题词和队列的主题词决定将消息推送到哪个队列. 交换机只会为 Queue 分发符合其指定的主题的消息. 向交换机发送消息时,消 ...

  3. rabbitmq 交换机模式 -主题模式 topic

    建立一个交换机 tpc 并且绑定了各自的路由到 Q1 Q2 <?php require_once "./vendor/autoload.php"; use PhpAmqpLi ...

  4. RabbitMQ入门(5)——主题(Topic)

    前面我们介绍了通过使用direct exchage,改善了fanout exchange只能进行虚拟广播的方式.尽管如此,直接交换也有自身的局限,它不能基于多个条件路由. 在我们的日志系统中,也许我们 ...

  5. RabbitMQ系列教程之五:主题(Topic)(转载)

    RabbitMQ系列教程之五:主题(Topic) (本实例都是使用的Net的客户端,使用C#编写),说明,中文方括号[]表示名词. 在上一个教程中,我们改进了我们的日志记录系统. 没有使用只能够进行虚 ...

  6. 7.RabbitMQ系列之topic主题交换器

    topic主题交换器它根据在队列绑定的路由键和路由模式通配符匹配将消息路由到队列. 生产者在消息头中添加路由键并将其发送到主题交换器. 收到消息后,exchange尝试将路由键与绑定到它的所有队列的绑 ...

  7. RabbitMQ (五)主题(Topic) -摘自网络

    虽然使用direct类型改良了我们的系统,但是仍然存在一些局限性:它不能够基于多重条件进行路由选择. 在我们的日志系统中,我们有可能希望不仅根据日志的级别而且想根据日志的来源进行订阅.这个概念类似un ...

  8. RabbitMQ (五)主题(Topic)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37706355 上一篇博客中,我们进步改良了我们的日志系统.我们使用direct类 ...

  9. RabbitMQ简单应用の主题模式(topic)

    Topic exchange(主题转发器) 发送给主题转发器的消息不能是任意设置的选择键,必须是用小数点隔开的一系列的标识符.这些标识符可以是随意,但是通常跟消息的某些特性相关联.一些合法的路由选择键 ...

随机推荐

  1. Ubuntu 16.04安装Memcached(单机)

    Ubuntu 16.04安装Memcached,不过不仅限与Ubuntu,可以用CentOS等去安装,只不过测试时使用的是Ubuntu机器.Windows下不建议使用,本机调试可以使用,线上环境除了W ...

  2. 详解ORACLE数据库的分区表

    此文从以下几个方面来整理关于分区表的概念及操作:    1.表空间及分区表的概念    2.表分区的具体作用    3.表分区的优缺点    4.表分区的几种类型及操作方法    5.对表分区的维护性 ...

  3. NetworkManager的坑(如何让network manager不去管理网络端口)

    在CentOS上,有时你需要停止并禁用 NetworkManager.但这样做了之后,其实NetworkManager还在影响着你的端口. 比如你有端口配置如下: [root@compute02 ~] ...

  4. SAS编程基础 - 菜鸟入门常用操作

    1. SAS9.4添加和取消注释的快捷键? Ctrl+/:添加注释 Ctrl+Shift+/:取消注释 2. 如何强制终止程序运行? 看到那个圆圈里带叹号的图标了吗?没错,就是它 - 中断! 3. 如 ...

  5. 【CV论文阅读】An elegant solution for subspace learning

    Pre: It is MY first time to see quite elegant a solution to seek a subspace for a group of local fea ...

  6. 工作总结 string数组 排序 string数组 比较

    用到   工具类 Array 创建.处理.搜索数组并对数组进行排序 Enumerable  提供一组用于查询实现 System.Collections.Generic.IEnumerable<T ...

  7. [NOI2015Day1]解题报告

    今天一起做NOI的题. 我仅仅想说SunshinAK了好神啊. T3数据好坑啊,打表竟然被编译环境卡掉了... T1:程序自己主动分析 (http://www.lydsy.com/JudgeOnlin ...

  8. VC++ 模拟&quot;CLICK事件&quot;关闭指定窗体

    今天改动一个工具时遇到一个有意思的问题,打开某个窗体时弹出一些不相关的窗体.须要用户自己去手动点击后才干继续.保证不了自己主动处理,如今解说决方案记录一下,例如以下 主要使用windows提供的Fin ...

  9. MySQL通过函数获取字符串汉字拼音首字母大写字符串

    DELIMITER $$ DROP FUNCTION IF EXISTS `Fun_GetPY`$$ CREATE FUNCTION `HIS`.`Fun_GetPY` (in_string VARC ...

  10. [计算机]如何在win7下查看并更改文件的默认后缀名

    如何在win7下查看默认文件的后缀名并更改呢? 例如有一个文件本来是exe,想变更为txt.但是无法看到后缀名,就无法更改. 双击桌面上的计算机图标,或者任意盘符界面,单击如下图左侧“组织”右侧的下拉 ...