3.发布/订阅

发布/订阅

Publish/Subscribe(using the java client)

在之前的章节中,我们创建了一个工作队列。工作队列背后的假设是每个任务只交付给一个工作者。在这一部分中,将会做一些完全不同的事情——向多个消费者传递一条消息。这种模式称为“发布/订阅”。
为了说明该模式,将构建一个简单的日志系统。它将由两个程序组成——第一个程序将发出日志消息,第二个程序将接收并打印它们。
在构建的日志系统中,每个运行的接收程序的副本都将获得消息。通过这种方式,能够运行一个接收者并将日志定向到磁盘。与此同时,可以运行另一个接收者,并在屏幕上看到日志。
实际上,发布的日志消息将被广播到所有的接收者。

Exchanges(交换机)

在之前的章节中,我们向队列发送和接收消息。现在是时候介绍rabbitMQ中完整的消息模型了。
回顾一下在之前的章节中介绍的内容:

  • 生产者是一个发送消息的用户应用程序。
  • 队列是存储消息的缓冲区。
  • 消费者是接收消息的用户应用程序。

RabbitMQ消息传递模型的核心思想是生产者从不直接向队列发送任何消息。实际上,通常生产者甚至根本不知道消息是否会被发送到任何队列。
相反,生产者只能向交换机发送消息。交换机是很简单的东西。一方面它从生产者那里接收消息,另一方面它将消息推送到队列中。交换机必须确切地知道如何处理它接收到的消息。它应该被附加到一个特定的队列吗?是否应该将其添加到多个队列中?或者它应该被丢弃。这些规则由交换类型定义。
交换机
有一些可用的交换类型:directtopicheaderfanout。现在先来关注最后一个,fanout。首先创建一个这种类型的交换机,并将其称为logs

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

fanout交换机非常简单。它只是将它接收到的所有消息广播到它所知道的所有队列。这正是当前的日志系统所需要的。

交换机列表

要列出服务器上的交换机,可以使用非常有用的rabbitmqctl命令:

rabbitmqctl list_exchanges

在这里插入图片描述

在这个列表中会有一些amq.*的交换机和默认的(未命名的)交换机。这些都是默认情况下创建的,但是目前不太可能需要使用它们。

未命名的交换机

在前面的章节中,我们对交换机一无所知,但仍然能够将消息发送到队列。这是可能的,因为使用的是默认交换机,由空字符串("")标识。

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

第一个参数是交换机的名称。空字符串表示默认的或未命名的交换机:如果routingKey存在,消息将被路由到由其指定的名称的队列。
现在,可以将其发布到已命名的交换机:

channel.basicPublish("logs", "", null, message.getBytes());
临时队列

之前使用的队列都具有特定的名称(例如hellotask_queue)。能够命名一个队列对我们来说非常重要——需要将工作者指向同一个队列。当希望在生产者和消费者之间共享队列时,为队列指定一个名称是很重要的。
但日志系统不是这样的。我们希望了解所有日志消息,而不仅仅是其中的一个子集。并且还只对当前流动的消息感兴趣而不是旧消息。要解决这个问题,需要做两件事。
首先,当连接到rabbitMQ时,需要一个新的并且为空的队列。要做到这一点,可以创建一个随机名称的队列,或者更好的是——让服务器选择一个随机的队列名称。
其次,一旦和消费者断开连接,队列就会被自动删除。
在java客户端中,当不向queueDeclare()传参时,就会创建一个非持久的、独占的、自动删除的队列,并使用一个生成的名称。

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

此时,queueName包含一个随机队列名。例如amq.gen-JzTY20BRgKO-HjmUJj0wLg(可以在guide on queues中了解有关exclusive标志和其他的队列属性的更多信息)。

绑定

在这里插入图片描述

我们已经创建了fanout交换机以及队列。现在,需要告诉交换机向队列发送消息。交换机和队列之间的这种关系称为绑定

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

从现在开始,logs交换机将向队列添加消息。

绑定列表

可以使用以下方法列出现有的绑定:

rabbitmqctl list_bindings

在这里插入图片描述

整合代码

在这里插入图片描述

生产者程序发出日志消息,与前面章节中的没有太大的不同。最重要的变化是,我们现在希望将消息发布到logs交换机,而不是未命名的。需要在发送时提供一个routingKey,但是它的值对于fanout交换机会被忽略。下面是EmitLog.java程序的代码:

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

    public static void main(String[] argv) throws Exception {
        var factory = new ConnectionFactory();
        factory.setHost("192.168.1.254");
        factory.setUsername("admin");
        factory.setPassword("admin123");

        try (var connection = factory.newConnection();
             var channel = connection.createChannel()) {
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");
            String message = argv.length < 1 ? "info: Hello World!" : String.join(" ", argv);
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes(StandardCharsets.UTF_8));

            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

正如所看到的那样,在建立连接之后声明了交换机。这一步是必要的,因为发布到一个不存在的交换机是被禁止的。
如果还没有队列绑定到交换机上,消息就会丢失,但这对我们来说是可以的。此时如果还没有消费者在监听,可以安全地丢弃该消息。
ReceiveLogs.java的代码:

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

    public static void main(String[] argv) throws Exception {
        var factory = new ConnectionFactory();
        factory.setHost("192.168.1.254");
        factory.setUsername("admin");
        factory.setPassword("admin123");

        var connection = factory.newConnection();
        var 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");

        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), StandardCharsets.UTF_8);
            System.out.println(" [x] Received '" + message + "'");
        };

        channel.basicConsume(queueName, true, deliverCallback, consumerTag -> {});
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值