在之前的学习的工作队列中,每条消息只发送给一个消费者。而发布/订阅模式则不同,是将消息发送给多个消费者。
交换机
在前面的学习过程中,我们向队列发送消息和从队列接收消息。现在是时候在Rabbit中引入完整的消息传递模型了。
前面学习介绍的内容:
- 生产者是发送消息的用户的应用程序。
- 队列是一个缓冲区,用于存储消息。
- 消费者是接收消息的用户的应用程序。
RabbitMQ消息传递模型中的核心思想是生产者从不将任何消息直接发送到队列。实际上,生产者经常甚至根本不知道是否将消息传递到任何队列。
相反,生产者只能将消息发送到交换机。交换机接收来自生产者的消息,另一方面,将它们推入队列。交换机必须确切知道如何处理收到的消息。是否应将其附加到特定队列?是否应该将其附加到许多队列中?还是应该丢弃它。规则由交换类型定义 。
交换器的规则有:
- direct(直连)
- fanout(分发)
- headers(首部)
- topic(主题)
创建一个【fanout】类型的名为logs的交换机
channel.exchangeDeclare("logs","fanout");
fanout类型的交换机非常简单,是将消息发送给所有与该交换机绑定的队列中。
现在可以将消息发布到logs交换机中
channel.basicPublish("logs","",message.getBytes() );
第一个参数就是交换器的名称。如果输入“”空字符串,表示使用默认的匿名交换器。
第二个参数是【routingKey】路由键
匿名交换器规则:
发送到routingKey名称对应的队列
临时队列
记得前面使用的队列指定的名称吗?
如果要在生产者和消费者之间创建一个新的队列,又不想使用原来的队列,临时队列就是为这个场景而生的:
- 首先,每当我们连接到RabbitMQ,我们需要一个新的空队列,我们可以用一个随机名称来创建,或者说让服务器选择一个随机队列名称给我们。
- 一旦我们断开消费者,队列应该立即被删除。
在Java客户端,提供queuedeclare()为我们创建一个非持久化、独立、自动删除的队列名称。
String queueName = channel.queueDeclare().getQueue();
此时,queueName包含一个随机队列名称。例如,它可能看起来像amq.gen-JzTY20BRgKO-HjmUJj0wLg。
绑定
现在,已经创建出交换机和队列,但是他们之间并没有任何关系,可以将队列与交换机绑定起来,他们之间的关系就叫做绑定。
channel.queueBind(queueName,“ logs”,“”);
从现在开始,logs交换机将消息添加到我们的队列中。
如果要查看绑定列表,可以执行【rabbitmqctl list_bindings】命令
最终代码
产生日志消息的生产程序与前面学习的看起来没有太大不同。最重要的变化是,我们现在希望将消息发布到日志交换机,而不是无名的消息交换器。发送时我们需要提供一个routingKey,但是对于fanout交换,它的值将被忽略。这是SendLog.java程序的代码 :
package com.xsw.publisherAndSubscribe;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class SendLog {
private final static String EXCHANGE_NAME="logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.101");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明一个交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//发送消息到交换机
for (int i=0;i<5;i++) {
String message = "hello log"+i;
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("send message:"+message);
}
channel.close();
connection.close();
}
}
上面的代码中,在建立连接后,我们声明了一个交换机。如果当前没有队列被绑定到交换机,消息将被丢弃,因为没有消费者监听,这条消息将被丢弃。
接收日志消息的RecvLog1.java和RecvLog2.java
package com.xsw.publisherAndSubscribe;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RecvLog1 {
private final static String EXCHANGE_NAME="logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.101");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//生成随机队列名
String queueName = channel.queueDeclare().getQueue();
System.out.println("队列:"+queueName);
System.out.println("RecvLog1 waiting message...");
//将队列绑定到交换机
channel.queueBind(queueName,EXCHANGE_NAME,"");
DeliverCallback deliverCallback = (consumerTag,deliver)->{
String message = new String(deliver.getBody(),"UTF-8");
System.out.println("receivered message:"+message);
};
channel.basicConsume(queueName,true,deliverCallback,consumerTag->{});
}
}
package com.xsw.publisherAndSubscribe;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RecvLog2 {
private final static String EXCHANGE_NAME="logs";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.56.101");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//生成随机队列名
String queueName = channel.queueDeclare().getQueue();
System.out.println("队列:"+queueName);
System.out.println("RecvLog2 waiting messge...");
//将队列绑定到交换机
channel.queueBind(queueName,EXCHANGE_NAME,"");
DeliverCallback deliverCallback = (consumerTag,deliver)->{
String message = new String(deliver.getBody(),"UTF-8");
System.out.println("receivered message:"+message);
};
channel.basicConsume(queueName,true,deliverCallback,consumerTag->{});
}
}
运行
先运行接收日志消息的RecvLog1和Recvlog2,可以看到:
然后在运行SendLog.java
然后观察RecvLog1和RecvLog2的日志:
结果的解释很简单:logs交换器中的数据进入到两个绑定的队列中。这正是我们的意图。