1、路由
之前创建过了一个简单的日志系统。可以将日志信息广播至许多接收者。在本节中,会将日志系统增加一个特性:仅订阅日志消息的一个子集。例如,仅仅将关键的错误日志消息写入日志文件(保存在磁盘),同时还能够将所有的日志消息打印至控制台。
2、绑定
在之前的例子中,已经使用过绑定了,调用代码如下:
1 | channel.queueBind(queueName, EXCHANGE_NAME, "" ); |
绑定即交换机和队列之间的一个关系。可以简单理解为:该队列关注此交换机中的消息。绑定方法可以传入一个routingKey参数,为了避免和basic_publish参数混淆,将它叫做绑定关键字(binding key)。下面是如何使用关键字来进行绑定:
1 | channel.queueBind(queueName, EXCHANGE_NAME, "black" ); |
绑定关键字的意义视交换机类型而定。先前使用的广播交换机(fanout)将会忽略该关键字。
3、Direct交换机
之前的日志系统将所有的消息广播至所有的消费者客户端。我们希望在此基础上根据日志的级别来过滤消息。例如,我们可能仅需要将关键的错误日志写入磁盘中,从而不会在warning或info日志消息上浪费磁盘空间。
广播交换机不能提供复杂的特性,它仅能实现简单的广播机制。下面使用direct交换机来代替广播(fanout)交换机,direct交换机的路由算法相对简单:只有队列的绑定关键字和消息的路由关键字完全匹配时,消息才能够发送至队列。如下图的路由机制:

可以看出在此路由机制下,有两个队列绑定在同一个direct类型的交换机上。第一个使用orange关键字进行绑定,第二个队列有两个绑定关键字,black和green。在此机制下,使用路由关键字orange发布的消息将会被发布至队列Q1,使用路由关键字black或green将会至Q2,其它所有的消息将会被丢弃。
4、多重绑定(Multiple bindings)

多个队列使用相同的绑定关键字是非常合法的,在上图所示的例子中,在例子中,可以为Q1增加一个绑定关键字black绑定至交换机X。在这种情况下,direct交换机就像fanout交换机一样,将会广播消息至所有匹配的队列中。使用路由关键字black的消息将会传送至Q1和Q2。
5、产生日志(Emitting logs)
在日志系统中使用这种模型,使用direct交换机代替fanout交换机来发送消息。使用日志级别做为路由关键字,接收程序可以选择它想要接收的级别的日志。
和以前一样,首先需要先创建交换机:
1 | channel.exchangeDeclare(EXCHANGE_NAME, "direct" ); |
发送消息:
1 | channel.basicPublish(EXCHANGE_NAME, severity, null , message.getBytes()); |
在上面的代码中,可以假定变量severity可取值为:info、warning、error。
6、订阅消息
订阅消息很简单,仅需要将队列关注的级别绑定至相应的交换机即可:
1 | String queueName = channel.queueDeclare().getQueue(); |
2 | channel.queueBind(queueName, EXCHANGE_NAME, severity); |
7、代码整合

7.1、生产者
接收控制台的输入,每行输入以空格分隔,第一位表示日志级别。详细代码如下:
1 | package com.zenfery.example.rabbitmq; |
3 | import java.io.IOException; |
4 | import java.util.Scanner; |
6 | import com.rabbitmq.client.Channel; |
7 | import com.rabbitmq.client.Connection; |
8 | import com.rabbitmq.client.ConnectionFactory; |
9 | import com.rabbitmq.client.ConsumerCancelledException; |
10 | import com.rabbitmq.client.ShutdownSignalException; |
13 | public class EmitLogDirect { |
15 | private static final String EXCHANGE_NAME = "direct_logs" ; |
17 | public static void main(String[] args) throws IOException |
18 | , ShutdownSignalException, ConsumerCancelledException |
19 | , InterruptedException { |
20 | ConnectionFactory factory = new ConnectionFactory(); |
21 | factory.setHost( "localhost" ); |
22 | factory.setPort( 5672 ); |
24 | Connection connection = factory.newConnection(); |
25 | Channel channel = connection.createChannel(); |
27 | channel.exchangeDeclare(EXCHANGE_NAME, "direct" ); |
30 | Scanner scanner = new Scanner(System.in); |
31 | while (scanner.hasNextLine()){ |
32 | String line = scanner.nextLine(); |
33 | String severity = line.split( " " )[ 0 ]; |
34 | String message = line; |
36 | channel.basicPublish(EXCHANGE_NAME, severity, null , message.getBytes()); |
37 | System.out.println( " >>>发送:[" +severity+ "] " +message+ "" ); |
7.2、消费者
启动的消费者根据传入的日志级别参数列表,来决定监听哪些日志级别的日志,其余的将会被忽略:
1 | package com.zenfery.example.rabbitmq; |
3 | import java.io.IOException; |
5 | import com.rabbitmq.client.Channel; |
6 | import com.rabbitmq.client.Connection; |
7 | import com.rabbitmq.client.ConnectionFactory; |
8 | import com.rabbitmq.client.ConsumerCancelledException; |
9 | import com.rabbitmq.client.QueueingConsumer; |
10 | import com.rabbitmq.client.ShutdownSignalException; |
13 | public class ReceiveLogsDirect { |
15 | private static final String EXCHANGE_NAME = "direct_logs" ; |
17 | public static void main(String[] args) throws IOException |
18 | , ShutdownSignalException, ConsumerCancelledException |
19 | , InterruptedException { |
20 | ConnectionFactory factory = new ConnectionFactory(); |
21 | factory.setHost( "localhost" ); |
22 | factory.setPort( 5672 ); |
24 | Connection connection = factory.newConnection(); |
25 | Channel channel = connection.createChannel(); |
27 | channel.exchangeDeclare(EXCHANGE_NAME, "direct" ); |
30 | String queueName = channel.queueDeclare().getQueue(); |
31 | for (String severity: args){ |
32 | channel.queueBind(queueName, EXCHANGE_NAME, severity); |
36 | QueueingConsumer consumer = new QueueingConsumer(channel); |
37 | channel.basicConsume(queueName, true , consumer); |
40 | QueueingConsumer.Delivery delivery = consumer.nextDelivery(); |
41 | String message = new String(delivery.getBody()); |
42 | System.out.println( " >>>接收消息:" + message); |
7.3、执行演示
传入参数“error”,启动第一个消费者。传入参数“error info warning”启动第二个消费者。
启动第一个消费者,依次输入:
第一个消费者输出:
第二个消费者输出:
3 | >>>接收消息:warning logs789. |