七、发布/订阅
前面的工作队列是假设每个任务都交付给一个工作者。在这部分,将是向多个消费者传递消息,此模式称为"发布/订阅"
。
为了说明这种模式,将构建一个简单的日志记录系统。它包含两个程序
- 发出日志消息
- 接收和打印消息,这里有两个消费
- 将日志定向到磁盘
- 在屏幕上看到日志
基本上,发布的日志消息将被广播给所有接收者。
Exchange
经过前面的教程内容,知道:
- producer:用户发送消息的应用程序
- queue:存储消息的缓冲器
- consumer:接收用户消息的应用程序
RabbitMQ中消息传递模型的核心思想是生产者永远不会将任何消息直接发送到队列。实际上,生产者通常甚至不知道消息是否会被传递到任何队列。
生产者只能向Exchange发送消息。Exchange,一方面,它接收来自生产者的消息,另一方面将它们推送到队列。Exchange必须确切知道如何处理收到的消息,可能推送到Exchange,可能推送到Queue,也有可能被丢弃。其规则由exchange类型定义 。
exchange类型有direct
、topic
、headers
和fanout
。这里将使用最后一个fanout
类型,创建名为logs
的Exchange。
channel.exchangeDeclare("logs", "fanout");
对于该类型,它将收到的所有消息广播到它知道的所有队列中。
临时Queue
对于前面的工作队列来说,命名队列至关重要-需要将consumer指向同一个队列。当在生产者和消费者之间共享队列时,命名队列很重要。
对于日志记录器,我们需要了解所有日志消息,而不仅仅是它们的一部分,也只对目前流动的消息感兴趣,而不是就消息。所以需要两个条件:
- 每当连接RabbitMQ时,都需要一个新的队列。为此,可以使用随机名称创建队列,或更好让RabbitMQ选择随机队列名称。
- 一旦断开消费者后,应该自动删除队列
所以,当没有想queueDeclare()提供参数时,将创建一个非持久、独占的自动删除队列:
String queueName = channel.queueDeclare().getQueue();
queueName生成一个随机队列名称,像amq.gen-JzTY20BRgKO-HjmUJj0wLg
。
Binding
创建了fanout
类型Exchange和Queue,需要告诉Exchange将消息发送到指定Queue中。Exchange和Queue之间需要绑定(Binding):
channel.queueBind(queueName, "logs", "");
绑定后,logs
Exchange将消息推送到Queue。
日志记录系统代码:
-
日志消息发送客户端:
/** * @author Hayson * @date 2018/11/26 17:17 * @description */ public class Send { final static String QUEUE = "helloWorld"; public static void main(String[] args) throws IOException, TimeoutException { send(); } public static void send() throws IOException, TimeoutException { //获取连接 Connection connection = ConnectionUtils.getConnection(); //通过连接创建信道 Channel channel = connection.createChannel(); //创建交换器 channel.exchangeDeclare("logs", BuiltinExchangeType.FANOUT); String message = "logs messages..."; //指定消息发送到交换器 channel.basicPublish("logs", "", null, message.getBytes("UTF-8")); System.out.println("发送消息:" + message); //关闭信道和连接 channel.close(); connection.close(); } }
这里只创建了交换器,没有创建队列绑定交换器,消息将会消失。对这里日志系统没有问题-如果没有消费者在看,可以丢弃消息。如果需要,消费者连接就可
-
日志消息接收客户端:
/** * @author Hayson * @date 2018/11/23 13:41 * @description rabbitmq消费者接收消息2 */ public class Receiver { final static String QUEUE = "helloWorld"; public static void main(String[] args) throws IOException, TimeoutException { recevier(); } public static void recevier() throws IOException, TimeoutException { Connection connection = ConnectionUtils.getConnection(); Channel channel = connection.createChannel(); //创建发送logs消息的交换器 channel.exchangeDeclare("logs", BuiltinExchangeType.FANOUT); //获取随机队列名 String queue = channel.queueDeclare().getQueue(); //绑定交换器和队列 channel.queueBind(queue, "logs", ""); //接收消息 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("接收到消息:" + message); } }; channel.basicConsume(queue, true, consumer); //关闭信道、连接 //channel.close(); //connection.close(); } }
首先运行多遍生成多个接收客户端监听队列,再运行日志发送客户端,可以看到多个消费客户端全部接收到同样日志消息。
消费客户端看完日志后,关闭后,队列将会自动删除。