系统拆分解耦利器之消息队列---RabbitMQ-路由

来源:互联网 发布:家庭网络拓扑 主路由 编辑:程序博客网 时间:2024/05/17 20:27

[一曲广陵不如晨钟暮鼓]

本文,我们来介绍RabbitMQ中的路由功能。在正式开始之前,我们假设RabbitMQ服务已经启动,运行端口为5672,如果各位看官有更改过默认配置,那么就需要修改为对应端口,保持一致即可。

准备工作:

操作系统:window 7 x64 

其他软件:eclipse mars,jdk7,maven 3

--------------------------------------------------------------------------------------------------------------------------------------------------------

路由(Routing)

在前文的教程中,我们构建了一个非常简单的日志系统。将日志消息发送到所有的客户端当中。

现在,我们需要对上面的日志系统加入一些新的特性,使得某个客户端能够接收到部分消息。举个例子:上文的第一个客户端能只将错误的消息写入到磁盘,同时第二个客户端能将所有的日志消息打印在控制台上。

绑定(Bindings)

在上文的例子当中,我们使用下面的语句完成绑定:

channel.queueBind(queueName, EXCHANGE_NAME, "");
由此,在exchange与queue之间建立了绑定关系。可以简单的理解为:队列有兴趣获取该exchange转发来的消息。

同时,也可是使用额外的“routingKey”参数。为了避免与basic_publish参数产生冲突,我们使用“binding key”作为新的标示。使用方式如下:

channel.queueBind(queueName, EXCHANGE_NAME, "black");
bindkey key的含义依赖于exchange的类型。如果是前面我们使用的fanout类型,那么是没有实际意义的,从而直接被忽略掉。

特别备注:为了便于理解博主习惯上将exchange的类型,称之为“点对点”,“组播”,“广播”。这种命名是错误的,但有助于学习和理解exchange的类型。如上文使用的fanout,就可以理解为广播的意思。在此,各位看官可以按照习惯来学习理解即可,不做特别要求。

接下来,我们按照官方文档的顺序来继续学习。

直接转发(Direct exchange)(点对点)

前文教程搭建的日志系统试讲所有的消息广播给所有的接收者。现在,我们需要按照日志的不同级别来将消息分发给不同客户端。正如上面举得例子:其中一个客户端只负责将错误的消息写入到磁盘,至于warning,info级别的日志就不需要浪费磁盘空间了。

之前日志系统使用的fanout模式只进行无脑的转发,是不能够提供足够的灵活性的。

所以,我们在此处替换fanout,而是使用直接转发(点对点)模式。这种模式的路由算法给予exchange与队列之间一一对应的关系。消息进入到exchange之后,只会被转发到与其拥有完全对应的routing key的队列当中。

为了说明这个概念,请观察下面这幅示意图:


在这种配置下,我们可以看到名称为“X”的exchange的类型为direct,并且拥有两个队列与之绑定。第一个队列Q1其routing key为orange。第二个队列Q2拥有2个绑定关系,routing key分别为black,green。

因此,如果消息带有的routing key是orange,那么其将会被投递到Q1当中。如果消息带有的routing key是black,green的任何一个,都会被投递到Q2当中。除此之外的任何消息都会被直接丢弃。

多重绑定(Multiple bindings)


在RabbitMQ中,使用相同的binding key来绑定多个队列是完全合法的。在刚刚的例子当中,可以继续使用black再增加一个与之对应的队列。在这种情况下,“点对点”的模式就非常的类似于“广播”的形式了。正如图所示的,消息将被投递到Q1,Q2两个队列当中。

发送日志(Emitting logs)

我们将会继续使用上文构建日志系统来介绍相关的内容。但是,我们会使用点对点的(direct)来替换掉之前使用的广播(fanout)类型的exchange。同时,队列的类型按照日志的级别进行划分。接收方将按照自身需要接收的级别选择性的进行消息接受。具体做法如下:

首先,我们需要创建一个exchange,如下:

channel.exchangeDeclare(EXCHANGE_NAME, "direct");
接着,使用这个exchange进行消息发送,如下:

channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
为了简单起见,我们假设日志的级别只有“info”,“waring”,“error”三种。

订阅(Subscribing)

接受消息的消费者客户端和前文的非常类似,不同之处在于:我们需要为每一种想要接受的消息级别创建一个绑定关系。如下:

String queueName = channel.queueDeclare().getQueue();for(String severity : argv){      channel.queueBind(queueName, EXCHANGE_NAME, severity);}
综合上面所有的内容,来看看完整的工程吧,结构图如下:


1.修改pom文件,具体内容请看前文,在此不再赘述。

2.创建EmitLogDirect文件,具体内容如下:

package com.csdn.ingo.rabbitmq_1;import java.io.IOException;import java.util.Date;import java.util.Random;import java.util.UUID;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;public class EmitLogDirect {// 队列名称private final static String EXCHANGE_NAME = "ex_log_direct";private final static String[] SEVERITIES = {"info","warning","error"};public static void main(String[] args) throws IOException {// 创建连接和频道ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");Connection connection = factory.newConnection();Channel channel = connection.createChannel();// 声明队列channel.exchangeDeclare(EXCHANGE_NAME, "direct");for(int i=0;i<6;i++){String severity = getSeverity();String message = severity+"_log"+UUID.randomUUID().toString();channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());System.out.println("[x] Sent:"+message);}// 关闭频道和资源channel.close();connection.close();}private static String getSeverity() {Random random = new Random();int ranval = random.nextInt(3);return SEVERITIES[ranval];}}
3.创建ReceiveLogsDirect文件,具体内容如下:

package com.csdn.ingo.rabbitmq_1;import java.io.IOException;import java.util.Random;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import com.rabbitmq.client.ConnectionFactory;import com.rabbitmq.client.ConsumerCancelledException;import com.rabbitmq.client.QueueingConsumer;import com.rabbitmq.client.ShutdownSignalException;public class ReceiveLogsDirect {private final static String EXCHANGE_NAME = "ex_log_direct";private final static String[] SEVERITIES = { "info", "warning", "error" };public static void main(String[] args)throws IOException, ShutdownSignalException, ConsumerCancelledException, InterruptedException {ConnectionFactory factory = new ConnectionFactory();factory.setHost("localhost");Connection conn = factory.newConnection();Channel channel = conn.createChannel();channel.exchangeDeclare(EXCHANGE_NAME, "direct");String queueName = channel.queueDeclare().getQueue();String severity = getSeverity();channel.queueBind(queueName, EXCHANGE_NAME, severity);System.out.println("[*] waiting for messages. To exit press CTRL+C");QueueingConsumer consumer = new QueueingConsumer(channel);channel.basicConsume(queueName, true, consumer);while (true) {QueueingConsumer.Delivery delivery = consumer.nextDelivery();String message = new String(delivery.getBody());System.out.println("[x] Received:" + message);}}private static String getSeverity() {Random random = new Random();int ranval = random.nextInt(3);return SEVERITIES[ranval];}}
4.测试方法:首先运行接收方程序,在运行发送者程序,观察控制台输出即可。

备注:由于使用Random来模拟创建3个队列,因此,需要多次运行程序来观察不同队列的消息发送与接收情况。各位看官也可以启动3个接收方进行一一对应,在此不做要求。

-------------------------------------------------------------------------------------------------------------------------------------------------------

至此,系统拆分解耦利器之消息队列---RabbitMQ-路由 结束


参考资料:

官方文档:http://www.rabbitmq.com/tutorials/tutorial-four-java.html

1 0