RabbitMQ教程扇出交换器和发布订阅

前言:上一篇我们实现了工作队列,工作队列我们的处理是一个队列中的消息被一个消费者处理, 这一篇里我们将发送一条消息,这条消息被多个消费者处理,这种模式也称为发布/订阅模式(publish / subscribe). 为说明这种模式,我们将会模拟一个简单的日志系统,它由两个程序组成,第一个程序发出日志消息,另一个程序接收并打印,在这个模拟系统中接收程序的每个副本都会收到发送的全部消息。从本质上来说, 发送的日志将会被广播给所有的消费者

RabitMQ文章目录

1-RabbitMQ Server简介和安装教程

2-RabbitMQ概念 AMQP模型

3-RabbitMQ教程第一步Hello World

4-RabbitMQ教程工作队列Work queues

5-RabbitMQ教程扇出交换器和发布订阅

6-RabbitMQ教程直连交换器和路由Routing

7-RabbitMQ教程主题交换器Topics

8-RabbitMQ教程远程过程调用RPC

9-RabbitMQ教程发布确认PublishConfirm

图例

P(生产者): 生产者属于数据的发送方 ,发送的消息被放入队列里

C(消费者): 消费者属于数据的接收方,发现队列里的消息,将消息从队列中读取出来做业务操作

Queue(队列): RabbitMQ的作用是存储消息,队列的特性是先进先出

几个概念

交换器(Exchanges)

前两个测试中,我们发送或者接收队列里的消息,现在是时候介绍RabbitMQ中的完整的消息模型了

  • 生产者是一个发送消息的应用程序

  • 队列是一个存储消息的缓存

  • 消费者是一个接收并使用消息的应用程序

RabbitMQ消息模型的核心思想是生产者不会直接将消息发送给队列,实际上生产者根本不知道消息会被发送到队列中,生产者只是向交换器投递消息。交换器从生产者哪里接收到消息,然后它将消息推送到队列中,如下图:

RabbitMQ教程扇出交换器和发布订阅_第1张图片

扇出交换器

RabbitMq有四种交换器 direct,topic,headers,fanout 当前我们这哪是关注最后一个fanout交换器

(如果想要了解其他交换器信息可以看另一篇文章  https://blog.csdn.net/Beijing_L/article/details/119181812)

扇出交换器声明交换器方式如下, 扇出交换器很简单,从名称上就能猜到它的作用:扇出交换器将会向它知道的所有队列广播消息

channel.exchangeDeclare("logs", "fanout");

可以使用使用命令可以在命令行查看现在你正在使用的交换器,其中amq.* 交换器是默认创建的交换器

sudo rabbitmqctl list_exchanges

 Window执行命令效果如下

C:\Windows\system32>rabbitmqctl list_exchanges
Listing exchanges for vhost / ...
name    type
        direct
amq.match       headers
amq.headers     headers
amq.rabbitmq.trace      topic
amq.topic       topic
amq.fanout      fanout
amq.direct      direct

默认交换器 

前面几篇文章中,虽然我们不知道交换器但是生产者仍然可以将消息发送到队列里。这就是我们使用了默认交换器的原因,声明时候我们使用了空字符串“”,声明中第一个空字符串表示交换器的名称,空字符串表示默认交换器,他将会使用队列名称当做路由KEY

channel.basicPublish("", "hello", null, message.getBytes());

临时队列

在前面的示例中, 我们使用了指定名称的队列, 然后将队列指向了一个消费者, 实现消费者处理任务, 当队列指向多个消费者的时候,一个任务只能被一个消费者处理。 但是本章想实现的和之前的不同,消费者要接收所有的日志,并不是其中一部分日志。要解决这个问题需要做两件事

  • 任何时候我们连接RabbitMq的时候,我们需要刷新出一个新的空队列,我们可以创建一个随机名字的队列。 更好的做法是让系统帮我们生成一个随机队列

  • 一但断开消费者的链接,队列将会被删除

在Java 客户端,我们可以使用queueDeclare()创建一个非持久的,专用的, 可自动删除的队列,队列名字是随机的,例如:amq.gen-7g5OWjcUitZZhS2uXBloXA

String queueName = channel.queueDeclare().getQueue();

前面我们使用exchangeDeclare 创建了扇出交换器,并使用queueDeclare() 创建了随机队列

channel.queueBind(queueName, "logs", "");

最后我们需要告诉扇出交换器将消息发送给随机队列,交换器和队列的关系我们称之为绑定( a binding),命令行中可以通过 rabbitmqctl list_bindings 查看

RabbitMQ教程扇出交换器和发布订阅_第2张图片

参考代码

实现 看起来和之前的教程很类似,最不同的地方是生产者发送消息将消息发送给有名字的扇出交换器而不是空字符串的默认交换器,扇出交换器会忽略路由KEY

RabbitMQ教程扇出交换器和发布订阅_第3张图片

消费者

public class ReciverLog {
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        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("Reciver1 waiting! queueName=" + queueName);

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {

            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };

        //设置自动确认=是
        boolean autoAck = true;
        channel.basicConsume(queueName, autoAck, deliverCallback, consumerTag -> {
        });
    }
}

生产者

public class Producer {

    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        //fanout表示扇出交换器,所有的消费者得到同样的队列信息
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

        //分发信息
        for (int i = 0; i < 5; i++) {
            String message = i + ". message is Hello world";
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
            System.out.println(" [x] Sent '" + message + "'");
        }


        channel.close();
        connection.close();
    }
}

运行结果

启动两个个消费者生成,其中amq.gen-EKOo39ulbszbmmzkKfBdGA和amq.gen-7g5OWjcUitZZhS2uXBloXA 是生成的临时队列

# 消费者1
Reciver1 waiting! queueName=amq.gen-EKOo39ulbszbmmzkKfBdGA

#消费者2:
Reciver2 waiting! queueName=amq.gen-7g5OWjcUitZZhS2uXBloXA

启动生产者模拟发送5条消息

 [x] Sent '0. message is Hello world'
 [x] Sent '1. message is Hello world'
 [x] Sent '2. message is Hello world'
 [x] Sent '3. message is Hello world'
 [x] Sent '4. message is Hello world'

再看消费者信息,两个消费者都接收到了全部的消息

# 消费者1
Reciver1 waiting! queueName=amq.gen-EKOo39ulbszbmmzkKfBdGA
 [x] Received '0. message is Hello world'
 [x] Received '1. message is Hello world'
 [x] Received '2. message is Hello world'
 [x] Received '3. message is Hello world'
 [x] Received '4. message is Hello world'

#消费者2:
Reciver2 waiting! queueName=amq.gen-7g5OWjcUitZZhS2uXBloXA
Reciver2 : '0. message is Hello world'
Reciver2 : '1. message is Hello world'
Reciver2 : '2. message is Hello world'
Reciver2 : '3. message is Hello world'
Reciver2 : '4. message is Hello world'

 文章总结

对比默认交换器和扇出交换器


//默认交换器生产者
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());

//扇出交换器生产者
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());

//-------------------------------------------------------------

//默认交换器消费者
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, autoAck, deliverCallback, consumerTag -> {});

//扇出交换器消费者
Channel channel = connection.createChannel();
channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
channel.queueBind(channel.queueDeclare().getQueue(), EXCHANGE_NAME, "");
channel.basicConsume(queueName, autoAck, deliverCallback, consumerTag -> { });


上一篇:RabbitMQ教程工作队列Work queues

你可能感兴趣的:(RabbitMQ,rabbitmq,java,队列)