RabbitMQ 使用 | 第三篇:发布/订阅模式
大部分内容仅仅对官方教程进行了翻译,有些内容为了更简便进行了调整
在上两篇文章中,介绍了的RabbitMQ
的消息收发模型都是发送的消息只能订阅一次,意思就是说,发布了消息之后,每一条消息只有一个订阅者能接收到该消息。发布/订阅模式是指发送消息,到某个队列,所有订阅了该队列的接受者都能接收到全部的消息。(类似观察者模式)
简介:
RabbitMQ
中,所有生产者提交的消息都由Exchange
来接受,然后Exchange
按照特定的策略转发到Queue
进行存储
RabbitMQ
提供了四种Exchange
:fanout,direct,topic,header
本节用到的Exchange
是fanout
,所以针对这种进行解释
fanout
这种模式不需要指定队列名称,需要将Exchange
和queue
绑定,他们之间的关系是‘多对多’的关系
任何发送到fanout Exchange
的消息都会被转发到与该Exchange
绑定的queue
上面。
发布/订阅模式就是是基于fanout Exchange
实现的。
回顾一下之前使用的声明和发送消息代码:
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello world";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
在前面的教程中,我们对Exchange
一无所知,但是仍然能够发送消息,这是我们在使用默认的Exchange,我们用空字符串来标识 “”
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
第一个参数是Exchange
的参数,这里使用了空字符串,标识使用默认的Exchange
,消息会发送到指定的QUEUE_NAME
如果使用fanout Exchange
需要改为以下代码:
//声明一个名为logs的类型为fanout的Exchange
channel.exchangeDeclare("logs", "fanout");
String message = "Hello World."
//对这个Exchange发送消息
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
接收的时候:
//声明一个名为logs的类型为fanout的Exchange
channel.exchangeDeclare("logs", "fanout");
//创建并获取一个队列名称
String queueName = channel.queueDeclare().getQueue();
//绑定队列到Exchange
channel.queueBind(queueName,”logs“, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
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(" [x] Received '" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
以上就是使用fanout Exchange
的代码,全部代码贴出如下:
发送端:
public class Publisher {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv)
throws java.io.IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String[] messages = new String[]{"First message.", "Second message..",
"Third message...", "Fourth message....", "Fifth message....."};
for (String message : messages) {
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("[x] Sent '" + message + "'");
}
channel.close();
connection.close();
}
}
接收端:
public class Subscriber {
private static final String EXCHANGE_NAME = "logs";
public static void main(String[] argv) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, EXCHANGE_NAME, "");
System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
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(" [x] Received '" + message + "'");
}
};
channel.basicConsume(queueName, true, consumer);
}
}
此时启动顺序有点需要注意,要先启动接收端,再启动发送端,我们启动3个Subscriber
,然后运行Publisher
。这个时候你会发现在Publisher
控制台输出:
[x] Sent 'First message.'
[x] Sent 'Second message..'
[x] Sent 'Third message...'
[x] Sent 'Fourth message....'
[x] Sent 'Fifth message.....'
Process finished with exit code 0
然后在3个Subscriber
控制台都能看到输出:
[*] Waiting for messages. To exit press CTRL+C
[x] Received 'First message.'
[x] Received 'Second message..'
[x] Received 'Third message...'
[x] Received 'Fourth message....'
[x] Received 'Fifth message.....'
这证明了每个Subscriber
都能接受到完整的消息。