翻译官网的文章已经翻译了几天了,这份官方文档写的总体算是很简洁易懂。它让我们很快的入门并了解了RabbitMQ的运作原理和使用方式。本篇最后介绍一下Exchange的另外两种类别,即direct和topic。对官网文档的翻译工作也将告一段落,接下来会探讨异步消息队列在spring项目中的集成。但现在还是先把RabbitMQ的另外两种使用方式来介绍完。

  Direct类型的Exchange

  一.基础知识点

  1.direct类别的Exchange.

  记得上一章节我们使用的是fanout类别的Exchange,它的特点是消费者不加区分地订阅所有消息,即所有的消费者都能订阅并收到广播到这个Exchange的消息。这一节我们希望稍作改动,让不同消费者能够订阅不同的消息。要实现这个功能,需要使用Direct的Exchange.

  其运作原理如图所示。

  

  首先,将Exchange设为direct类型,可用这句话来实现:channel.exchangeDeclare(EXCHANGE_NAME, "direct")。

  然后,我们发现,queue和Producer之间,除了多出Exchange,还多出了另外一条线索,也就是所谓的routeKey。其实它就是对Exchange的进一步分类,这样做了以后,当我们广播消息的时候,消息就进行了两层分类,第一层是Exchange(图中的X),第二层就是RouteKey(图中的Orange,green等)。我们使用如下语句来发布消息,channel.basicPublish(EXCHANGE_NAME, orange, null, message.getBytes());其中第二个参数就是RouteKey.

  再然后,消费者如果想要接收指定的消息,只需要在符合一定条件的queue的集合中去寻找即可。比如说,我想接收颜色为orange的消息,则去Q1中寻找,我想接收颜色为black或者green的消息,就去Q2中寻找。当然Q1和Q2需要进行绑定才能将特定的消息放进来。使用如下语句即可以将Queue与Exchange和RouteKey绑定。channel.queueBind(queueName, EXCHANGE_NAME, orange);其中第三个参数就是RouteKey。

  二.Demo

  话不多说,看下面的demo,这个demo希望对不同的日志信息做不同的处理,相信读者很快就会理解其中原理。

  首先是生产者类。

  

package com.xdx.learn;

import java.io.IOException;
import java.util.concurrent.TimeoutException; import net.sf.json.JSONObject; import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory; public class EmitLogDirect {
private final static String EXCHANGE_NAME="direct_logs";//交换机名称为log public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.1.195");//服务器ip
factory.setPort(5672);//端口
factory.setUsername("xdx");//登录名
factory.setPassword("xxxxx");//密码
Connection connection=factory.newConnection();//建立连接
Channel channel=connection.createChannel();//建立频道
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);//在频道里声明一个交换机,类型定位direct
System.out.println(channel+"发布20条info日志消息");
for(int i=0;i<20;i++){
String message="info日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "info", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为info
System.out.println(message);
}
System.out.println(channel+"发布10条error日志消息");
for(int i=0;i<10;i++){
String message="error日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "error", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为error
System.out.println(message);
}
System.out.println(channel+"发布5条debug日志消息");
for(int i=0;i<5;i++){
String message="debug日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "debug", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为error
System.out.println(message);
}
channel.close();
connection.close();
}
}

  再看消费者类。

package com.xdx.learn;

import java.io.IOException;
import java.util.concurrent.TimeoutException; import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope; public class ReceiveLogsDirect {
private final static String EXCHANGE_NAME = "direct_logs";
public static void main(String[] args) throws IOException, TimeoutException {
// 下面的配置与生产者相对应
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.195");// 服务器ip
factory.setPort(5672);// 端口
factory.setUsername("xdx");// 登录名
factory.setPassword("xxxxx");// 密码
Connection connection = factory.newConnection();// 连接
final Channel channel = connection.createChannel();// 频道
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
final String queueName = channel.queueDeclare().getQueue();// 生成一个独立的,非持久的,自动删除的queue
channel.queueBind(queueName, EXCHANGE_NAME, "info");// 绑定queue和exchange,还有routekey。这样队列中就有通过EXCHANGE_NAME发布的消息。
channel.queueBind(queueName, EXCHANGE_NAME, "debug");
System.out.println(" messages from channel:"+ channel+",queue:"+ queueName
+ ". To exit press CTRL+C");
// defaultConsumer实现了Consumer,我们将使用它来缓存生产者发送过来储存在队列中的消息。当我们可以接收消息的时候,从中获取。
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("channel:"+channel+",queue:"+queueName+",consumer:"+this.getConsumerTag()+" Received '" + message + "'");
// channel.basicAck(envelope.getDeliveryTag(),false);
try {
Thread.sleep(300);
} catch (Exception e) {
}
}
};
channel.basicConsume(queueName, true, consumer);//自动回复,消息发出后队列自动消除
} }

  首先我们先运行消费者类,这样它就可以守株待兔,订阅消息了。然后再运行生产者类。

  生产者类的控制台打印出如下信息:

  

  消费者类的控制台打印出如下信息。

  

  三.总结

  1.在生产者类中,我们将消息分为三种类别,info,error,debug,并分别广播进了以这三个类别作为routeKey的Exchange中。这是第一步,对消息做分类。

  2.在消费者类中,我们想要查看info和debug这两种类型的消息,所以我们将queue与这两种类别的Exchange进行了绑定,通过channel.queueBind(queueName, EXCHANGE_NAME, "info");channel.queueBind(queueName, EXCHANGE_NAME, "debug");这两条代码。这样,这个queue中就存满了这两种类型的信息,消费者就可以使用它们了。至于error类别的日志消息,因为没有消费者订阅,所以它们会被丢弃。

  应该来说demo是非常简单也非常恰当的解释了Direct类型的Exchange的运作机制。

  Topic类型的Exchange

  topic类型的Exchange其实是在Direct类型上进行了扩展,如果direct类型的Exchange只能从一个维度来对Exchange进行区分,那么topic类型的Exchange以一种更为灵活的方式对Exchange进行了多维度的区分。关键还是在RouteKey上。

  一.基础知识点

  来看topic类型的Exchange的示意图。

  

  可以看到,现在的RouteKey不在是一个单一的一个字段来描述,而是可以有多个字段来共同描述。每个字段代表一个维度。例如上述的例子,我们描述一种动物,第一个字段代表速度,第二个字段代表颜色,第三个字段代表的是物种。字段之间用点号隔开,即<speed>.<colour>.<species>。并且,它还为我们提供了两个很好用的通配符。

  *:代表一个字段

  #:代表0个或者多个字段。

  所以*.orange.*代表的是所以橙色的动物,*.*.rabbit代表的是所以兔子,而lazy.#代表的是所有反应缓慢的动物。那么:quick.orange.rabbit匹配Q1和Q2,lazy.orange.elephant也是,quick.orange.fox匹配Q1.lazy.brown.fox匹配Q2.

  值得注意的是,如果我将一个routeKey写为#,则这时候Exchange相当于fanout类型,而如果routeKey中即没有*又没有#,则相当于Direct类型了。

  二.Demo

  话不多说,还是来看一个demo吧。在这个demo中,我们将消息分为两个维度<project><severity>。

  生产者类

  

package com.xdx.learn;

import java.io.IOException;
import java.util.concurrent.TimeoutException; import net.sf.json.JSONObject; 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";//交换机名称为log public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory=new ConnectionFactory();
factory.setHost("192.168.1.195");//服务器ip
factory.setPort(5672);//端口
factory.setUsername("xdx");//登录名
factory.setPassword("xxxx");//密码
Connection connection=factory.newConnection();//建立连接
Channel channel=connection.createChannel();//建立频道
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);//在频道里声明一个交换机,类型定位topic
System.out.println(channel+"发布3条project1.info日志消息");
for(int i=0;i<3;i++){
String message="project1.info日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "project1.info", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为info
System.out.println(message);
}
System.out.println(channel+"发布3条project1.error日志消息");
for(int i=0;i<3;i++){
String message="project1.error日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "project1.error", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为error
System.out.println(message);
}
System.out.println(channel+"发布3条project1.debug日志消息");
for(int i=0;i<3;i++){
String message="project1.debug日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "project1.debug", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为error
System.out.println(message);
}
System.out.println(channel+"发布3条project2.info日志消息");
for(int i=0;i<3;i++){
String message="project2.info日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "project2.info", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为info
System.out.println(message);
}
System.out.println(channel+"发布3条project2.error日志消息");
for(int i=0;i<3;i++){
String message="project2.error日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "project2.error", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为error
System.out.println(message);
}
System.out.println(channel+"发布3条project2.debug日志消息");
for(int i=0;i<3;i++){
String message="project2.debug日志消息"+i;
channel.basicPublish(EXCHANGE_NAME, "project2.debug", null, message.getBytes());//发布消息,发布到EXCHANGE_NAME,并且routeKey标为error
System.out.println(message);
}
channel.close();
connection.close();
}
}

  消费者类

  

package com.xdx.learn;

import java.io.IOException;
import java.util.concurrent.TimeoutException; import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope; public class ReceiveLogsTopic {
private final static String EXCHANGE_NAME = "topic_logs";
public static void main(String[] args) throws IOException, TimeoutException {
// 下面的配置与生产者相对应
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.1.195");// 服务器ip
factory.setPort(5672);// 端口
factory.setUsername("xdx");// 登录名
factory.setPassword("xxxx");// 密码
Connection connection = factory.newConnection();// 连接
final Channel channel = connection.createChannel();// 频道
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
final String queueName = channel.queueDeclare().getQueue();// 生成一个独立的,非持久的,自动删除的queue
channel.queueBind(queueName, EXCHANGE_NAME, "*.info");// 绑定queue和exchange,还有routekey。这样队列中就有通过EXCHANGE_NAME发布的消息。
channel.queueBind(queueName, EXCHANGE_NAME, "project1.*");
System.out.println(" messages from channel:"+ channel+",queue:"+ queueName
+ ". To exit press CTRL+C");
// defaultConsumer实现了Consumer,我们将使用它来缓存生产者发送过来储存在队列中的消息。当我们可以接收消息的时候,从中获取。
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("channel:"+channel+",queue:"+queueName+",consumer:"+this.getConsumerTag()+" Received '" + message + "'");
// channel.basicAck(envelope.getDeliveryTag(),false);
try {
Thread.sleep(300);
} catch (Exception e) {
}
}
};
channel.basicConsume(queueName, true, consumer);//自动回复,消息发出后队列自动消除
} }

  运行消费者类,再运行生产者类,分别打印出。

  生产者类:

  

  生产者类:

  

  大家可以根据代码和结果来验证我们的假设,这里就不做总结了。

五.RabbitMQ之路由(Routing)和主题(topics)的更多相关文章

  1. RabbitMQ学习总结 第五篇:路由Routing

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  2. RabbitMQ之路由(Routing)【译】

    在上一节中,我们创建了一个简单的日志系统,可以广播消息到很多接收者. 这一节,我们将在上一节的基础上加一个功能--订阅部分消息.例如,我们只将严重错误信息写入到日志文件保存在磁盘上,同时我们能将所有的 ...

  3. RabbitMQ入门教程(七):主题交换机Topics

    原文:RabbitMQ入门教程(七):主题交换机Topics 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog. ...

  4. RabbitMQ官方中文入门教程(PHP版) 第四部分:路由(Routing)

    路由(Routing) 在前面的教程中,我们实现了一个简单的日志系统.可以把日志消息广播给多个接收者. 本篇教程中我们打算新增一个功能——使得它能够只订阅消息的一个字集.例如,我们只需要把严重的错误日 ...

  5. RabbitMQ 入门教程(PHP版) 第四部分:路由(Routing)

    路由(Routing) 在前面的第三部分教程中,我们实现了一个简单的日志系统.可以把日志消息广播给多个接收者. 本篇教程中我们打算新增一个功能——使得它能够只订阅消息的一个字集.例如,我们只需要把严重 ...

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

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

  7. asp.net MVC 5 路由 Routing

    ASP.NET MVC ,一个适用于WEB应用程序的经典模型 model-view-controller 模式.相对于web forms一个单一的整块,asp.net mvc是由连接在一起的各种代码层 ...

  8. AngularJS - 路由 routing 基础示例

    AngularJS 路由 routing 能够从页面的一个视图跳转到另外一个视图,对单页面应用来讲是至关重要的.当应用变得越来越复杂时,我们需要一个合理的方式来管理用户在使用过程中看到的界面.Angu ...

  9. (八)RabbitMQ消息队列-通过Topic主题模式分发消息

    原文:(八)RabbitMQ消息队列-通过Topic主题模式分发消息 前两章我们讲了RabbitMQ的direct模式和fanout模式,本章介绍topic主题模式的应用.如果对direct模式下通过 ...

随机推荐

  1. 清理win10过期补丁的命令

    作用是删除已经被新版本取代的旧系统文件 DISM.exe /Online /Cleanup-Image /StartComponentCleanup /ResetBase 注1: 执行后, 补丁就无法 ...

  2. Power BI连接SSAS(微软的分析服务)进行权限控制(本地部署)

    尬聊...... 在干活之前先尬聊一会儿 丸子我在10月下旬左右就开始弄power BI连接SSAS进行权限控制的问题,中间也是历经波折,看了网上很多资料,可是都是SSAS怎么进行权限控制,没有SSA ...

  3. 堆排序HeapSort

    堆排序,顾名思义,是采用数据结构堆来进行排序的一种排序算法. 研究没有规律的堆,没有任何意义.特殊的堆有最大堆(父节点值大于等于左右字节点值),最小堆(父节点值小于等于子节点值).一般采用最大堆来进行 ...

  4. tmux frequently asked questions

    tmux frequently asked questions How is tmux different from GNU screen?     tmux and GNU screen have ...

  5. mysql使用use db出现夯住问题

    表的数目在15585个,在使用use db的时候出现夯住 从show processlist中看到一堆表在做排序,想看看这些表的表结构.使用use db之后夯住,没有办法查看. 当时没有想到怎么办,鉴 ...

  6. mysql数据库表卡死解决方法

    ---恢复内容开始--- 问题引起原因: 由于在执行大量插入操作的时候意外终止程序之后, MySQl的线程并没有被终止,导致表不能打开和操作 -  解决思路就是找到等待的线程并kill -- 查看所有 ...

  7. Office2016 KMS激活

    Office标准版激活 一新买本子需要安装Office,闲来无事就安装了一款Office Standard 2016,网上许多激活秘钥均已过期,无法激活,无奈下选择KMS激活. KMS下载链接如下: ...

  8. Gitlab自动触发Jenkins构建打包

    一.目的 在部门的测试环境中,开发人员一旦向gitlab仓库提交成功代码,gitlab就会自动触发jenkins构建项目.当然在构建后还可以添加项目部署或者自动化测试的脚本.这里只针对测试环境. 二. ...

  9. springboot + redis缓存使用

    [参照资料] 1.spring boot 官网文档 2.https://www.cnblogs.com/gdpuzxs/p/7222309.html [项目结构] [pom.xml配置] <?x ...

  10. 友元函数 C++

    #include<iostream> #include<vector> using namespace std; class Text{ public: Text():a(){ ...