发布/订阅模式
发布/订阅模式:交换机类型为 fanout
,也称为广播模式,即:一个生产者对应多个消费者。它会把所有发送到该交换机的消息路由到所有与该交换机绑定的队列中(无视 BindingKey)
发布/订阅模式的简要说明如下:
- 一个生产者,多个消费者
- 每个消费者都绑定一个自己的队列
- 生产者没有将消息直接发送给队列,而是发送给交换机 Exchange
- 每个队列都需要绑定到交换机上
- 生产者发送的消息,经过交换机到达队列,可实现一个消息被多个消费者消费
发布/订阅模式结构如下图:
X(Exchanges):交换机一方面:接收生产者发送的消息;另一方面:知道如何处理消息。例如:递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于 Exchange 的类型。
Exchange 类型有以下几种:
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定 routing key 的队列
- Topic:通配符,把消息交给符合 routing pattern(路由模式) 的队列
- Header:header模式与routing不同的地方在于,header模式取消routingkey,使用header中的 key/value(键值对)匹配队列
Exchange 类型详解请移步到之前的文章:【RabbitMQ】RabbitMQ 学习教程(一)认识 RabbitMQ
Exchange(交换机)只负责转发消息,不具备存储消息的能力。因此,如果没有任何队列与 Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
【注意】:路由键在 fanout 类型的交换机中不起作用
后台页面操作
先添加两个队列 fanout_queue1
、fanout_queue2
,
需要将队列与交换机进行绑定。可在队列详情中进行绑定交换机,也可在交换机那绑定队列。二者选其一即可。这里我们选择后者。但现在我们没有交换机啊!!
再添加一个交换机 fanout_exchange,它的类型为 fanout。(相较于之前的两个模式,这里需要新建一个交换机,并不是使用的默认的交换机)
然后,绑定队列与交换机(不需要添加绑定键)
交换机 fanout_exchange 绑定完两个队列后 fanout_queue1、fanout_queue2:
然后,在交换机 fanout_exchange 中去发送消息(不需要添加路由键)
发送消息完毕后,与交换机绑定的相关队列都会收到消息
代码实现
生产者
和前面两种模式不同:
- 声明 Exchange,不再声明 Queue
- 发送消息到 Exchange,不再发送到 Queue
public class Producer {
private static final String FANOUT_EXCHANGE_NAME = "code_fanout_exchange";
public static void main(String[] args) throws Exception{
// 1. 获取连接
Connection connection = RabbitMqUtil.getConnection("生产者");
// 2. 通过连接获取通道 Channel
Channel channel = connection.createChannel();
// 3. 通过通道声明交换机,以及交换机类型为 fanout
/**
* @param1:交换机名称
* @param2:交换机类型
*/
channel.exchangeDeclare(FANOUT_EXCHANGE_NAME, "fanout");
// 4. 消息内容
String message = "Hello RabbitMQ Fanout!!";
// 5. 发送消息到交换机
channel.basicPublish(FANOUT_EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("消息发送完成~~~发送的消息为:" + message);
// 6. 关闭信道、连接
RabbitMqUtil.close(connection, channel);
}
}
exchangeDeclare() 方法详解:声明交换机
exchangeDeclare() 方法有多个重载方法,这些重载方法都是由下面这个方法中缺省的某些参数构成:
DeclareOk exchangeDeclare(String exchange,
String type, boolean durable,
boolean autoDelete, boolean internal,
Map<String, Object> arguments) throws IOException;
这个方法的返回值是 Exchange.DeclareOk
,用来标识成功声明了一个交换机。
各个参数详细说明如下:
- exchange:交换机名称
- type:交换机的类型。常见的有:fanout、direct、topic
- durable:设置是否持久化。设置为 true,表示持久化。持久化可以将交换机存盘,在服务器重启的时候不会丢失相关信息
- autoDelete:设置是否自动删除。设置为 true,表示自动删除。自动删除的前提是至少有一个队列或者交换机与这个交换机绑定,之后所有与这个交换机绑定的队列或者交换机都与此解绑
- internal:设置是否内置。设置为 true,则表示是内置的交换机。客户端程序无法直接发送消息到这个交换机中,只能通过交换器路由到交换机这种方式
- arguments:其它一些结构化参数
消费者
和前面两种模式不同:
- 消费者需要将队列绑定到交换机
添加两个消费者,这两个消费者绑定不同的队列:fanout_queue1、fanout_queue2
public class Consumer {
private static final String FANOUT_QUEUE_NAME = "fanout_queue1";
private static final String FANOUT_EXCHANGE_NAME = "code_fanout_exchange";
public static void main(String[] args) throws Exception{
// 获取连接
Connection connection = RabbitMqUtil.getConnection("消费者");
// 获取通道
Channel channel = connection.createChannel();
// 绑定队列到交换机
channel.queueBind(FANOUT_QUEUE_NAME, FANOUT_EXCHANGE_NAME, "");
// 定义消费者
com.rabbitmq.client.Consumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// body 消息体
String msg = new String(body,"utf-8");
System.out.println("收到消息:" + msg);
}
};
// 监听队列
channel.basicConsume(FANOUT_QUEUE_NAME, true, consumer);
System.out.println("开始接收消息~~~");
System.in.read();
// 关闭信道、连接
RabbitMqUtil.close(connection, channel);
}
}
Consumer2 代码与 Consumer 代码一样,只不过需要将 FANOUT_QUEUE_NAME
稍作修改
queueBind() 方法详解:将交换机与队列进行绑定
Queue.BindOk queueBind(String queue,
String exchange, String bindingKey,
Map<String, Object> argument) throws IOException;
方法中的各参数含义如下:
- queue:队列名称
- exchange:交换机名称
- bindingKey:绑定键
- argument:定义绑定时的一些参数
发布/订阅模式与工作队列模式区别
问题:publish/subscribe 与 work queues 有什么区别?
不同:
- work queues 不用定义交换机,而 publish/subscribe 需要定义交换机
- publish/subscribe 的生产方是面向交换机发送消息,work queues 的生产方是面向队列发送消息(底层使用默认交换机)
- publish/subscribe 需要设置队列和交换机的绑定,work queues 不需要设置,实际上 work queues 会将队列绑定到默认的交换机
相同:
- 两者实现的发布/订阅的效果是一样的,多个消费端监听同一个队列不会重复消费消息