• 【RabbitMQ】Publish/Subscribe


    Publish/Subscribe

    在上一节我们创建了一个work queue。背后的设想为每个任务被分发给明确的消费者。这节内容我们将做一些完全不同的事情 -- 我们将发送一条消息给多个消费者。这种模式被称为“发布/订阅”。

    为了描述这种模式,我们来构建一个简单的日志系统。它包含两个程序 -- 第一个将会发送日志消息,第二个接收并打印。在我们的日志系统中,所有的正在运行的接收程序都会收到消息。这样我们可以运行一个接收程序,将日志定向到磁盘;同时可以运行另外的接收程序可以从屏幕上看到日志。

    本质上,发布的所有日志消息会被广播给所有的接受者。

    交换机

    在前几节内容中,我们都是从一个队列中发送和接收消息。现在是时候介绍RabbitMQ的完整消息模型了。

    快速回顾前面章节:

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

    RabbitMQ消息模型的核心思想是,生产者从来不会直接发送消息给一个队列。确切的说,大多数情况下,生产者根本不知道它的消息将会发送到哪个队列。

    事实是,生产者只能发送消息给一个交换机(exchange)。交换机是一个很简单的概念。一方面它接收生产者的消息,另一方面它推送消息到队列中。但是交换机必须明确知道自己要对接收到的消息进行何种处理:是添加到制定队列?还是添加到所有的队列?抑或是将之丢弃?这些规则由交换机的类型来定义。

    有许多可供选择的交换机类型:direct, topic, headers, fanout. 我们集中在fanout上讲解。创建一个fanout类型的交换机,称它为logs:

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

    这个交换机非常简单。它会广播所有接收到的消息给所有它的已知队列。这就是我们logger程序所需要的。

    Listing exchanges

    可以使用rabbitmqctl来列出你服务器上的所有交换机:

    $ sudo rabbitmqctl list_exchanges
    Listing exchanges ...
            direct
    amq.direct      direct
    amq.fanout      fanout
    amq.headers     headers
    amq.match       headers
    amq.rabbitmq.log        topic
    amq.rabbitmq.trace      topic
    amq.topic       topic
    logs    fanout
    ...done.

    这个列表中有一些amp.*的交换机和默认(未命名)的交换机。它们都是默认被创建的。

    Nameless exchange

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

    回想我们如何发布一条消息的:

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

    第一个参数就是交换机的名字。空字符串表示默认或未命名的交换机:消息根据路由Key指定的队列名称被路由到队列。

    现在我们可以发布到我们自己命名的交换机:

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

    临时队列

    你可能还记得我们之前使用的队列都是有一个指定的名称的(比如hello和task_queue)。给队列命名对我们来说至关重要 -- 我们需要将消费者指向相同的队列。当你想在生产者和消费者之间分享队列的时候,给队列一个名字非常重要。

    但我们的日志程序不是这样的。我们希望监听所有的日志消息,而不仅仅是其中的一部分。我们也仅仅对当前的流动消息感兴趣,而不是老的消息。解决这个问题需要下面两件事:

    第一,无论何时我们连接到RabbitMQ时,都需要一个新的空的队列。要做到这一点我们可以创建一个随机名称的队列,或者更好一点的方式 - 让服务器为我们选择一个随机队列名。

    第二,一旦我们断开生产者的连接,队列应该被自动删除。

    在Java客户端,当我们调用无参的queueDeclare()方法,我们将创建一个非持久化的,唯一的,自动删除的并且随机名称的队列。

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

    queueName是一个随机的队列名称,可能看起来像:amq.gen-JzTY20BRgKO-HjmUJj0wLg

    绑定

    现在我们已经创建了一个fanout交换机和一个队列。现在我们需要告诉交换机给我们的队列发送消息。这种在交换机和队列之间的关系叫做绑定(binding)

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

    现在开始,logs交换机将向我们的队列追加消息。

    Listing bingdings

    使用rabbitmqctl list_bindings列出所有存在的绑定

    Putting it all together

    生产者程序,发送日志消息,看起来和之前的程序没有什么太大的区别。最重要的改变在我们现在希望发布消息到logs交换机,而不是之前的没有名字的交换机。在发送的时候,我们需要提供一个路由Key(routingKey),但是它的值被fanout交换机忽略了。下面是EmitLog.java:

    import com.rabbitmq.client.ConnectionFactory;
    import com.rabbitmq.client.Connection;
    import com.rabbitmq.client.Channel;
    
    public class EmitLog {
    
      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 message = getMessage(argv);
    
        channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes("UTF-8"));
        System.out.println(" [x] Sent '" + message + "'");
    
        channel.close();
        connection.close();
      }
    
      private static String getMessage(String[] strings){
        if (strings.length < 1)
                return "info: Hello World!";
        return joinStrings(strings, " ");
      }
    
      private static String joinStrings(String[] strings, String delimiter) {
        int length = strings.length;
        if (length == 0) return "";
        StringBuilder words = new StringBuilder(strings[0]);
        for (int i = 1; i < length; i++) {
            words.append(delimiter).append(strings[i]);
        }
        return words.toString();
      }
    }

    如你所见,在创建了连接之后,我们声明了交换机。这一步是必须的,因为无法向一个不存在的交换机发布消息。

    如果没有队列绑定到交换机上,消息会丢失,但对我们来说这没有什么,如果没有消费者监听我们可以安全的丢弃消息。

    ReceiveLogs.java:

    import com.rabbitmq.client.*;
    
    import java.io.IOException;
    
    public class ReceiveLogs {
      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);
      }
    }
  • 相关阅读:
    (八)CXF之用spring添加拦截器
    (七)CXF之与spring整合发布web服务
    (六)CXF之自定义拦截器
    借鉴mini2440的usb-wifi工具集在Beagleboard上移植无线网卡
    ubuntu设置网络
    mysql优化学习
    java日期转化
    Navicat使用
    (转载)windows下mysql忘记密码
    java读取文件乱码
  • 原文地址:https://www.cnblogs.com/shiyu404/p/6252924.html
Copyright © 2020-2023  润新知