RabbitMQ非官方教程(四)发布和订阅

上一节我们创建了一个工作队列,并且假设每个任务都恰好交付给一个消费者。在本章节中,我们将消息传达给多个消费者,这种模式称为“发布/订阅”。

为了说明这种模式,我们将构建一个简单的日志记录系统。它由两个程序组成:第一个程序将发出日志消息,第二个程序将接收并打印它们。在我们的日志系统中,接收器程序的每个运行副本都将获得消息。这样我们将能够运行一个接收器并将日志定向到磁盘。同时我们将能够运行另一个接收器并在屏幕上查看日志。实际上已发布的日志消息将被广播到所有接收者。

交换器

前面教程中,我们都是向队列发送消息,然后从队列接收消息。现在是添加一个新概念——交换器(交换机)。RabbitMQ消息传递模型中的核心思想是:生产者不能直接把生产的消息发送给队列。实际应用中生产者根本不知道是否将消息传递到那个队列中,生产者只能将消息发送到交换器中。交流是一件非常简单的事情,一方面它接收来自生产者的消息;另一方面将它们推入队列。

交换器必须确切知道如何处理收到的消息,是否应将其附加到特定队列?是否应该将其附加到许多队列中?还是应该丢弃它。这些操作都是由交换类型定义决定 。

交换器的类型, 内置的有四种, 分别是:

  • fanout
  • direct
  • topic
  • headers

这是本节的demo代码地址:https://gitee.com/mjTree/javaDevelop/tree/master/testDemo

 

先不着急写Demo代码,看一下这么定义的。首先是fanout类型的交换器,在代码中定义交换器:

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

fanout类型的交换比较简单,它只是将接收到的所有消息广播到它知道的所有队列中,而这正是我们记录器所需要的。

/*
这是NewTask.java中消息的定义,第一个参数是交换的名称。
我们填写的是空字符串,它表示默认或无名称交换
*/
channel.basicPublish("", TASK_QUEUE_NAME,
                    MessageProperties.PERSISTENT_TEXT_PLAIN,
                    message.getBytes("UTF-8"));

// 现在我们把它修改为,这样生产者就不会直接把消息交给队列
channel.basicPublish( "logs", "", 
                    MessageProperties.PERSISTENT_TEXT_PLAIN, 
                    message.getBytes("UTF-8"));

当我们要在生产者和消费者之间共享队列时,给队列命名很重要。而且在交换器把消息按上面四种规则发布时也需要队列名的规整性。但这不是我们的记录器的情况,我们希望听到所有日志消息,而不是一部分。我们只对当前正在发送的消息感兴趣,对旧消息不感兴趣。为了解决这个问题,我们需要两件事。

首先,无论何时连接到Rabbit,我们都需要一个全新的空队列。因此我们需要创建一个具有随机名称的队列,或者甚至更好的让服务器为我们选择一个随机队列名称。其次一旦我们断开了使用者的连接时队列将被自动删除。在Java客户端中,当我们不向queueDeclare()提供任何参数时,我们会使用生成的名字来创建一个非持久的、排他的、自动删除的队列:

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

非持久化前面教程已经说到了,排他性是指仅由一个连接使用并且该连接关闭时队列将被删除。

排他队列

排他队列只能通过其声明连接来使用(消耗,清除,删除等)。尝试使用来自其他连接的互斥队列将导致通道级异常 RESOURCE_LOCKED,并显示一条错误消息,提示 无法获得对锁定队列的互斥访问。 

排他队列的声明连接已关闭或消失时(例如,由于基础TCP连接丢失),它们将被删除。因此,它们仅适用于客户端特定的瞬态。

通常,以服务器名称命名排他队列。

我们上面生成的queueName是一个随机队列名称,可能看起来像amq.gen-vfAboU8KeitFZq5UYpnZKQ。现在我们已经创建了一个叫logs的交换机和一个叫queueName的消息队列。此时我们需要告诉交换机将消息发送到我们的队列,而交换和队列之间的关系称为绑定。

通过下面代码,我们把交换机logs和消息队列queueName绑定在一起。在我们发送消息的时候,如果第一个参数提供的交换机名称是logs,我们只会把消息转发到queueName队列中,其他队列收不到。这就是fanout类型交换机的特性。

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

举个例子:

// 这里我们定义了一个交换机和三个消息队列
exchangeDeclare(exchangeName="First", type="fanout")
queueDeclare(queue= "A")
queueDeclare(queue= "B")
queueDeclare(queue= "C")

// 开始绑定
queueBind(exchange="First", queue="A")
queueBind(exchange="First", queue="B")

// 循环发送10次消息
basicPublish(exchange="First", body="Hello MJ")

结果:A和B队列各有10个消息,C队列没有

接下来写代码,新建Third包,建立EmitLog.java和ReceiveLogs.java。

package com.mytest.rabbitMQ.Third;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class EmitLog {
    // 定义交换器名称
    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            // 创建fanout类型的交换机
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
            String message = argv.length < 1 ? "info:Hello World!":
                    String.join(" ", argv);

            channel.basicPublish(EXCHANGE_NAME, "",
                    null,
                    message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}
package com.mytest.rabbitMQ.Third;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;

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

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("127.0.0.1");

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

        // 创建fanout类型的交换机
        channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
        // 创建一个非持久的、排他的、自动删除的队列
        String queueName = channel.queueDeclare().getQueue();
        System.out.println("自动生成的队列名称:" + queueName);
        //channel.queueDeclare(TASK_QUEUE_NAME, durable, false, false, null);   // 之前教程创建的持久化队列
        // 交换机和队列绑定
        channel.queueBind(queueName, EXCHANGE_NAME, "");

        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        channel.basicConsume(queueName, true, deliverCallback, consumerTag ->{});
    }
}

这里我们先执行EmitLog,然后去网页查看是否生成该队列,是生成一个名字怪怪的队列。然后我们执行ReceiveLogs得到下面结果,通过交换机把消息发送到怪怪的队列中,然后消费者把队列中消息拿出来之后队列自动删除。刷新一下网页之后怪怪的队列自动被删除不存在了。

后面教程再介绍其他三中交换机的匹配规则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值