RabbitMQ学习(三):Publish/Subscribe

说明

在第二篇教程中,我们了解了RabbitMQ的消息派发方式,消息持久化及消息确认相关内容。本文将继续翻译官方的第三篇教程Publish/Subscribe, 通过本篇教程我们将继续了解RabbitMQ一个功能强大的组件-----交换机(Exchanges),通过其中的一种交换机实现RabbitMQ对消息的发布订阅模式。

正文

发布/订阅(Publish/Subscribe)

在前一篇教程中,我们创建了一个任务队列。这个任务队列背后的设想是一个任务只会派发给一个消费者,每个消费者得到的都是不同的消息。在本篇教程中,我们学习的内容将是完全不同于任务队列的,我们希望将一个消息同时发送给多个消费者。这种模式就是著名的“发布-订阅”模式。

为了解释这种模式,我们将会创建一个简单的日志系统。这个系统将会包含两个模块–第一个模块程序将发送日志消息,另一个将把日志消息输出到屏幕上。

在我们消息系统中运行的每一个接收程序的副本都会接收到消息。这样我们就可以运行一个消费者将日志消息写到磁盘,同时可以运行另一个消费者将日志输出到屏幕上。

实际上,被发送的日志消息将会以广播的形式发送到所有的消费者。

交换机(Exchanges)

在之前的教程中我们都是从一个队列中进行发送和接收消息。现在我们将介绍RabbitMQ中完整的消息传递模式。

让我们快速地回顾下之前学习的内容:

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

RabbitMQ消息传递模型的核心思想是生产者从不直接向队列发送消息,实际上,生产者甚至不知道消息是否会被传递到队列。

相反地,生产者仅仅发送消息到一个交换机。交换机是一个很简单的东西,一方面它接收来自生产者的消息,另一方面它将消息发送到队列。交换机必须清楚地知道怎么处理接收到的消息,是将消息发送到一个指定的队列,还是发送到多个队列,又或者是将它丢弃。交换机的类型定义了这些规则。
在这里插入图片描述
RabbitMQ有四种交换机类型,分别是:direct,topic,headers和fanout。在本节教程中,我们将关注最后一种类型:fanout。让我们创建一个名为logs的fanout类型的交换机:

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

fanout交换机十分简单,或许你已经从名字猜出它的作用。它将接收到的消息以广播的形式发送到它已知的所有的队列。这正是我们日志系统所需要的。

Listing exchanges
在服务器上运行rabbitmqctl list_exchanges命令可以得到服务器上的交换机列表。在这个列表中,你可以看到名为以amq.*开头的交换机和默认的(无名的)的交换机。它们被默认创建,但此时你可能不需要使用它们。

Nameless exchanges
在之前的教程中我们对交换机一无所知,但是仍然可以发送消息到队列,这时因为我们使用了默认的以空字符串("")命名的交换机
回想下我们之前发送消息的代码:

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

第一个参数的就是交换机的名称。空的字符串表示默认的或无名的交换机。如果名称存在,消息将被使用指定名称的routingKey路由到队列。

现在我们可以使用已命名的交换机发送消息:

channel.basicPublish("logs", "", null, message.getBytes("UTF-8"));

临时队列(Temporary queues)

你可能记得之前我们使用的指定名称的队列(hello和task_queue)。可以对队列命名对我们来说是十分重要的,因为我们需要将多个消息者指向同一个队列。所以当你想要在生产者和消息者使用同一个队列,给队列一个名称就十分重要了。

但是在我们的日志系统中情况就不一样了。我们希望监听所有的日志消息,而不是一部分,而且我们只对当前最新的消息感兴趣。为了实现这些我们需要做两件事情:

首先,当无论何时我们连接到RabbitMQ时,我们需要一个新的,空的队列。为了做到这样,我们可以使用一个随机的名字创建一个队列,或者更好让服务器为我们随机选择一个队列名称。

其次,一旦我们的消费者断开连接,队列应该被自动删除。

在java客户端,当我们使用无参数的queueDeclare()方法声明创建队列时,服务会使用一个随机生成的名称创建一个未持久化的,排他的,自动删除的队列。

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

guide on queues中你可以了解更多关于exclusive参数和其他队列参数更多的内容

绑定(Bindings)

在这里插入图片描述
我们已经创建了一个fanout类型的交换机和一个队列。现在我们需要告诉交换机将消息发送到我们的队列中,交换机和队列间的这种关系我们称之为“绑定”。

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

Listing bindings
在服务器行运行rabbitmqctl list_bindings 命令可以得到队列和交换机的绑定关系。

Putting it all together

在这里插入图片描述
发送日志消息的生产者程序和我们之前教程中的生产者看起来并没有太多的不同,最重要的变化是我们将消息发送到我们自己创建的logs交换机,而不是默认的交换机。当发送时消息需要携带一个routingKey,但是fanout类型交换机将忽略它的值。

发送日志消息的生产者程序EmitLog.java:

public class EmitLog {

    private static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.exchangeDeclare(EXCHANGE_NAME, "fanout");

            String message = "hello world";
            channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
            System.out.println(" [x] Sent '" + message + "'");
        }

    }
}

正如你所看到的,在建立连接后我们声明了一个交换机,这步是必须的,因为RabbitMQ禁止发送一个消息到不存在的交换机。

如果交换机还没有绑定任何队列,那消息将会被丢弃。但是这里对我们来说没有任何问题,如果没有消费者监听队列,那我们就可以安全地丢弃它。

接收日志消息的消费者程序ReceiveLogs.java:

public class RecevieLogs {

    public static final String EXCHANGE_NAME = "logs";

    public static void main(String[] args) 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 message.");

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

源码地址:https://github.com/Edenwds/rabbitmq_study/tree/master/pubSub

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值