在上一章中,咱们构建了一个简单的日志系统,咱们能够把消息广播给不少的消费者。在本章中咱们将增长一个特性:咱们能够订阅这些信息中的一些信息。例如,咱们但愿只将error级别的错误存储到硬盘中,同时能够将全部级别(error、info、warning等)的日志都打印在控制台上。java
在上一章中,咱们已经建立了绑定关系,回顾一下代码:算法
1 channel.queueBind(queueName, EXCHANGE_NAME, "");
一个绑定是一个交换器与队列之间的关系。意思是指:这个队列对这个交换器的消息感兴趣。测试
该方法同时还有另外一个routing Key参数,为了不与basic_public参数产生中的路由键(routing key)混淆,咱们称之为绑定键(bingind key),下面展现了如何经过一个绑定key建立一个绑定:spa
1 channel.queueBind(queueName, EXCHANGE_NAME, "black");
注意,这个绑定键(这里是"black")的含义依赖于交换器的类型。好比在咱们的日志系统中,交换器类型为fanout,此时,绑定键没有任何意义,会被忽略掉。日志
在咱们以前的日志系统中,全部的消息被广播给全部的消费者,可是本章的须要是但愿有一个程序能够只接收error级别的日志并保存到磁盘中,而不用浪费空间去存储那些info、warning级别的日志。code
咱们正在用的广播模式的交换器并不够灵活,它只是不加思索地进行广播。所以,须要使用direct exchange来代替。直连交换器的路由算法很是简单:将消息推送到binding key与该消息的routing key相同的队列。blog
为了说明这点,请看下图:rabbitmq
在该图中,直连交换器X上绑定了两个队列。第一个队列绑定了绑定键orange,第二个队列有两个绑定键:black和green。在这种场景下,一个消息在布时指定了路由键为orange将会只被路由到队列Q1,路由键为black和green的消息都将被路由到队列Q2。其余的消息都将被丢失。队列
同一个绑定键能够绑定到不一样的队列上去,在上图中,咱们也能够增长一个交换器X与队列Q2的绑定键,在这种状况下,直连交换器将会和广播交换器有着相同的行为,将消息推送到全部匹配的队列。一个路由键为black的消息将会同时被推送到队列Q1和Q2。路由
首先咱们要一如既往地建立一个交换器:
1 channel.exchangeDeclare(EXCHANGE_NAME, "direct");
并准备发送消息:
1 channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes());
咱们须要确保在咱们日志系统中参数"severity"是“info”、“warning”和“error”中的一个。
建立接收消息与上一章基本相同,惟一不一样的是,须要在建立绑定关系时,指定severity的值:
1 String queueName = channel.queueDeclare().getQueue(); 2 3 for(String severity : argv){ 4 channel.queueBind(queueName, EXCHANGE_NAME, severity); 5 }
EmitLogDirect.java
1 import com.rabbitmq.client.Channel; 2 import com.rabbitmq.client.Connection; 3 import com.rabbitmq.client.ConnectionFactory; 4 5 public class EmitLogDirect { 6 7 private static final String EXCHANGE_NAME = "direct_logs"; 8 9 public static void main(String[] argv) throws Exception { 10 ConnectionFactory factory = new ConnectionFactory(); 11 factory.setHost("localhost"); 12 try (Connection connection = factory.newConnection(); 13 Channel channel = connection.createChannel()) { 14 channel.exchangeDeclare(EXCHANGE_NAME, "direct"); 15 16 String severity = getSeverity(argv); 17 String message = getMessage(argv); 18 19 channel.basicPublish(EXCHANGE_NAME, severity, null, message.getBytes("UTF-8")); 20 System.out.println(" [x] Sent '" + severity + "':'" + message + "'"); 21 } 22 } 23 //.. 24 }
ReceiveLogsDirect.java
1 import com.rabbitmq.client.*; 2 3 public class ReceiveLogsDirect { 4 5 private static final String EXCHANGE_NAME = "direct_logs"; 6 7 public static void main(String[] argv) throws Exception { 8 ConnectionFactory factory = new ConnectionFactory(); 9 factory.setHost("localhost"); 10 Connection connection = factory.newConnection(); 11 Channel channel = connection.createChannel(); 12 13 channel.exchangeDeclare(EXCHANGE_NAME, "direct"); 14 String queueName = channel.queueDeclare().getQueue(); 15 16 if (argv.length < 1) { 17 System.err.println("Usage: ReceiveLogsDirect [info] [warning] [error]"); 18 System.exit(1); 19 } 20 21 for (String severity : argv) { 22 channel.queueBind(queueName, EXCHANGE_NAME, severity); 23 } 24 System.out.println(" [*] Waiting for messages. To exit press CTRL+C"); 25 26 DeliverCallback deliverCallback = (consumerTag, delivery) -> { 27 String message = new String(delivery.getBody(), "UTF-8"); 28 System.out.println(" [x] Received '" + 29 delivery.getEnvelope().getRoutingKey() + "':'" + message + "'"); 30 }; 31 channel.basicConsume(queueName, true, deliverCallback, consumerTag -> { }); 32 } 33 }
为了测试方便,咱们能够把"info"、"error"、"warning"都绑定到一个队列上去,而后生产者分别往"info"、"error"、"warning"发送消息:
此时查看RabbitMq控制台:
到此,发布-订阅涉及到的相关知识点都讲解完了,下一章将讲解Topic(主题模式)。