1. 发布/订阅
在RabbitMQ(三)中创建了一个工作队列。工作队列背后的假设是,每个任务恰好交付给一个工人。在这一部分中,我们将做一些完全不同的事情-我们将消息传达给多个消费者。这种模式称为“发布/订阅”。
2.代码示例
2.1 发送者
/**
* 消息发送者
*/
public class Sender {
private final static String EXCHANGE_NAME = "testExchange";//交换机名称
public static void main(String[] args) throws Exception {
//1.获取连接
Connection connection = ConnectionUtil.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.声名交换机
//参数1,交换机名称
//参数2,交换机类型,默认 fanout(扇区)
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//4.发送消息
String msg = "发送发布订阅消息";
channel.basicPublish(EXCHANGE_NAME,"",null,msg.getBytes());
//5.关闭连接
channel.close();
connection.close();
}
}
2.2 消费者1号
/**
* 消息消费者
*/
public class Recv1 {
private final static String EXCHANGE_NAME = "testExchange";//交换机名称
private final static String QUEUE1 = "testQueue1";//临时队列名称
public static void main(String[] args) throws Exception {
//1.获取连接
Connection connection = ConnectionUtil.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.1 声名队列
//参数1:队列的名称
//参数2:是否持久化队列,我们的队列模式是在内存中的,如果RabbitMQ重启会丢失,如果设置为true,则会保存在erlang自带的数据库中,重启后恢复
//参数3:是否排外,有两个作用,第一个当我们的连接关闭后是否自动删除队列;第二,是否私有当前队列,如果私有,其它通道不可访问
//参数4:是否自动删除
//参数5:一些其它参数
channel.queueDeclare(QUEUE1,false,false,false,null);
//3.2 绑定队列到交换机
channel.queueBind(QUEUE1,EXCHANGE_NAME,"");
//4.公平派遣:通知服务器,在消费确认前,不接收新发送的消息
channel.basicQos(1);
//5.定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//打印收到的消息
System.out.println("消费者1收到内容:"+new String(body));
//7.消费者消息确认:参数2,false 为手动确认收到消息。
//这里可以在异常捕获的时候,将它设置为true
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//6.注册消费者,参数2,false代表我们收到消息后需要手动告诉服务器
channel.basicConsume(QUEUE1,false,consumer);
}
}
2.3 消费者2号
/**
* 消息消费者
*/
public class Recv2 {
private final static String EXCHANGE_NAME = "testExchange";//交换机名称
private final static String QUEUE2 = "testQueue2";//临时队列名称
public static void main(String[] args) throws Exception {
//1.获取连接
Connection connection = ConnectionUtil.getConnection();
//2.创建通道
Channel channel = connection.createChannel();
//3.1 声名队列
//参数1:队列的名称
//参数2:是否持久化队列,我们的队列模式是在内存中的,如果RabbitMQ重启会丢失,如果设置为true,则会保存在erlang自带的数据库中,重启后恢复
//参数3:是否排外,有两个作用,第一个当我们的连接关闭后是否自动删除队列;第二,是否私有当前队列,如果私有,其它通道不可访问
//参数4:是否自动删除
//参数5:一些其它参数
channel.queueDeclare(QUEUE2,false,false,false,null);
//3.2 绑定队列到交换机
channel.queueBind(QUEUE2,EXCHANGE_NAME,"");
//4.公平派遣:通知服务器,在消费确认前,不接收新发送的消息
channel.basicQos(1);
//5.定义消费者
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//打印收到的消息
System.out.println("消费者2收到内容:"+new String(body));
//7.消费者消息确认:参数2,false 为手动确认收到消息。
//这里可以在异常捕获的时候,将它设置为true
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
//6.注册消费者,参数2,false代表我们收到消息后需要手动告诉服务器
channel.basicConsume(QUEUE2,false,consumer);
}
}
3.交换类型
channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
RabbitMQ消息传递模型中的核心思想是生产者从不将任何消息直接发送到队列。实际上,生产者经常甚至根本不知道是否将消息传递到任何队列。相反,生产者只能将消息发送到交换机。交流是一件非常简单的事情。一方面,它接收来自生产者的消息,另一方面,将它们推入队列。交换机必须确切知道如何处理收到的消息。是否应将其附加到特定队列?是否应该将其附加到许多队列中?还是应该丢弃它。规则由交换类型定义 。
3.1 交换类型种类
- direct -(直接)
- topic -(主题)
- headers-(页眉)
- fanout -(扇出)
3.2 fanout:扇出交换非常简单。正如您可能从名称中猜测的那样,它只是将接收到的所有消息广播到它知道的所有队列中。
4. 临时队列
上述代码中的QUEUE1,QUEUE2
当您想在生产者和消费者之间共享队列时,给队列命名很重要。但这不是我们的记录器的情况。我们希望监听到所有消息,而不仅仅是其中的一部分。我们也只对当前正在发送的消息感兴趣,而对旧消息不感兴趣。为了解决这个问题,我们需要两件事。
首先,无论何时连接到Rabbit,我们都需要一个全新的空队列。为此,我们可以创建一个具有随机名称的队列,或者甚至更好-让服务器为我们选择一个随机队列名称。其次,一旦我们断开了使用者的连接,队列将被自动删除。
在Java客户端中,当我们不向queueDeclare()提供任何参数时,我们将 使用生成的名称创建一个非持久的,排他的,自动删除的队列:
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName,EXCHANGE_NAME,"");