在上一章中,我们完善了我们的日志系统,用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. [bzoj4520][Cqoi2016]K远点对_KD-Tree_堆

    K远点对 bzoj-4520 Cqoi-2016 题目大意:已知平面内 N 个点的坐标,求欧氏距离下的第 K 远点对. 注释:$1\le n\le 10^5$,$1\le k\le 100$,$k\l ...

  2. [bzoj2733][HNOI2012]永无乡_权值线段树_线段树合并

    永无乡 bzoj-2733 HNOI-2012 题目大意:题目链接. 注释:略. 想法: 它的查询操作非常友善,就是一个联通块内的$k$小值. 故此我们可以考虑每个联通块建一棵权值线段树. 这样的话每 ...

  3. [bzoj3712][PA2014]Fiolki_倍增LCA

    Fiolki bzoj-3712 PA-2014 题目大意:题目链接. 注释:略. 想法: 神题! 我们建树:对于一次倾倒操作,我们弄一个新的大瓶子作为两个合并瓶子的父亲节点,与两个瓶子相连. 对于一 ...

  4. 洛谷—— P2543 [AHOI2004]奇怪的字符串

    P2543 [AHOI2004]奇怪的字符串 题目描述 输入输出格式 输入格式: 输入文件中包含两个字符串X和Y.当中两字符串非0即1.序列长度均小于9999. 输出格式: X和Y的最长公共子序列长度 ...

  5. io计算

    http://www.cnblogs.com/yalong_xiang/archive/2011/11/15/2249530.html ぬ儱←OWEN★   windows下如何查看磁盘IO性能 复制 ...

  6. Oracle SqlPlus导出查询结果

    Oracle SqlPlus导出查询结果 在sqlplus下导出查询的结果保存到本地sql文件中,可以采用如下方式:1.连接数据库: sqlplus xmq/xmqpwd@192.168.1.57:1 ...

  7. Linux学习系列之MySQL备份

    MySQL排除表备份 #!/bin/bash #created by 90root #date: 20160809 date_y=$(date +%Y) date_m=$(date +%m) time ...

  8. JAVA 流程控制之选择语句

    在程序设计时,有三种基本技术可以改变程序的流程控制: 调用方法: 选择: 循环. 在这里,我们主要来讲讲选择语句. JAVA中的选择语句与C语言中的基本相同,包括: if 语句: if/else 语句 ...

  9. 划分问题(dp)

    给出一个整数集合  , 将这个集合分成2个 和相等的集合. 思路就是  总和的一半作为一个目标容量,在集合中寻找能否够有元素的和恰好为这个目标容量,也就转化为一个类似01背包问题. 这篇文章讲的比較具 ...

  10. c# IP从192.168.1.1转成int类型

    找了一些资料,总结如下: 方法1 .net提供的方法转换IP地址 //字符串转换为数字 System.Net.IPAddress ipaddress = System.Net.IPAddress.Pa ...