RabbitMQ教程 (三):Publish/Subscribe 发布订阅模式

Publish/Subscribe

(using the Java Client)

Prerequisites

  This tutorial assumes RabbitMQ is installed and running on localhost on standard port (5672). In case you use a different host, port or credentials, connections settings would require adjusting.

Where to get help

  If you’re having trouble going through this tutorial you can contact us through the mailing list.

  In the previous tutorial we created a work queue. The assumption behind a work queue is that each task is delivered to exactly one worker. In this part we’ll do something completely different – we’ll deliver a message to multiple consumers. This pattern is known as “publish/subscribe”.
  在上一个教程中,我们创建了一个工作队列。工作队列背后的假设是每个任务都交付给一个工作者。在这一部分,我们将做一些完全不同的事情 - 我们将向多个消费者传递信息。此模式称为“发布/订阅”。
  To illustrate the pattern, we’re going to build a simple logging system. It will consist of two programs – the first will emit log messages and the second will receive and print them.
  为了说明这种模式,我们将构建一个简单的日志记录系统。它将包含两个程序 - 第一个将发出日志消息,第二个将接收和打印它们。
  In our logging system every running copy of the receiver program will get the messages. That way we’ll be able to run one receiver and direct the logs to disk; and at the same time we’ll be able to run another receiver and see the logs on the screen.
  在我们的日志记录系统中,接收程序的每个运行副本都将获取消息。这样我们就可以运行一个接收器并将日志定向到磁盘; 同时我们将能够运行另一个接收器并在屏幕上看到日志。

  Essentially, published log messages are going to be broadcast to all the receivers.
  基本上,发布的日志消息将被广播给所有接收者

Exchanges 交换器

  In previous parts of the tutorial we sent and received messages to and from a queue. Now it’s time to introduce the full messaging model in Rabbit.
  在本教程的前几部分中,我们向队列发送消息和从队列接收消息。现在是时候在Rabbit中引入完整的消息传递模型了

  Let’s quickly go over what we covered in the previous tutorials:
  让我们快速回顾一下前面教程中介绍的内容:

  • A producer is a user application that sends messages.
    生产者是发送消息的用户的应用程序。
  • A queue is a buffer that stores messages.
    队列是存储消息的缓冲器。
  • A consumer is a user application that receives messages.
    消费者是接收消息的用户的应用程序。

  The core idea in the messaging model in RabbitMQ is that the producer never sends any messages directly to a queue. Actually, quite often the producer doesn’t even know if a message will be delivered to any queue at all.
  RabbitMQ中消息传递模型的核心思想是生产者永远不会将任何消息直接发送到队列。实际上,生产者通常甚至不知道消息是否会被传递到任何队列。

  Instead, the producer can only send messages to an exchange. An exchange is a very simple thing. On one side it receives messages from producers and the other side it pushes them to queues. The exchange must know exactly what to do with a message it receives. Should it be appended to a particular queue? Should it be appended to many queues? Or should it get discarded. The rules for that are defined by the exchange type.
  相反,生产者只能向交换器发送消息。交换是一件非常简单的事情。一方面,它接收来自生产者的消息,另一方面将它们推送到队列。交换器必须确切知道如何处理收到的消息。它应该附加到特定队列吗?它应该附加到许多队列吗?或者它应该被丢弃。其规则由交换器类型定义 。
在这里插入图片描述

  There are a few exchange types available: direct, topic, headers and fanout. We’ll focus on the last one – the fanout. Let’s create an exchange of this type, and call it logs:
  有几种交换器类型可供选择:direct, topic, headers and fanout。我们将专注于最后一个 - fanout。让我们创建一个这种类型的交换,并将其称为日志:

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

  The fanout exchange is very simple. As you can probably guess from the name, it just broadcasts all the messages it receives to all the queues it knows. And that’s exactly what we need for our logger.
  fanout 交换器 非常简单。正如您可能从名称中猜到的那样,它只是将收到的所有消息广播到它知道的所有队列中。而这正是我们记录器所需要的。

Listing exchanges 交换器列表

  To list the exchanges on the server you can run the ever useful rabbitmqctl:
  要列出服务器上的交换器,您可以运行有用的rabbitmqctl:

	sudo rabbitmqctl list_exchanges

  In this list there will be some amq.* exchanges and the default (unnamed) exchange. These are created by default, but it is unlikely you’ll need to use them at the moment.

  在此列表中将有一些amq.*交换器和默认(未命名)交换器。这些是默认创建的,但目前您不太可能需要使用它们。

  Nameless exchange 匿名交换器

  In previous parts of the tutorial we knew nothing about exchanges, but still were able to send messages to queues. That was possible because we were using a default exchange, which we identify by the empty string ("").
  在本教程的前几部分中,我们对交换器一无所知,但仍能够向队列发送消息。这是可能的,因为我们使用的是默认交换器,我们通过空字符串(“”)来识别。

Recall how we published a message before:回想一下我们之前是如何发布消息的:

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

  The first parameter is the the name of the exchange. The empty string denotes the default or nameless exchange: messages are routed to the queue with the name specified by routingKey, if it exists.
第一个参数是交换器的名称。空字符串表示默认或匿名交换器:消息被路由到具有routingKey指定名称的队列(如果存在)。
Now, we can publish to our named exchange instead:
现在,我们可以发布到我们的命名交换器:

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

Temporary queues 临时队列

  As you may remember previously we were using queues that had specific names (remember hello and task_queue?). Being able to name a queue was crucial for us – we needed to point the workers to the same queue. Giving a queue a name is important when you want to share the queue between producers and consumers.
  您可能还记得以前我们使用过具有特定名称的队列(还记得hello和task_queue吗?)。能够命名队列对我们来说至关重要 - 我们需要将工作人员指向同一个队列。当您想要在生产者和消费者之间共享队列时,为队列命名很重要。

  But that’s not the case for our logger. We want to hear about all log messages, not just a subset of them. We’re also interested only in currently flowing messages not in the old ones. To solve that we need two things.
  但我们的 logger 日志系统 并非如此。我们希望了解所有日志消息,而不仅仅是它们的一部分。我们也只对目前流动的消息感兴趣,而不是旧消息。要解决这个问题,我们需要两件事。

  Firstly, whenever we connect to Rabbit we need a fresh, empty queue. To do this we could create a queue with a random name, or, even better - let the server choose a random queue name for us.
  首先,每当我们连接到Rabbit时,我们都需要一个新的空队列。为此,我们可以使用随机名称创建队列,或者更好 - 让服务器为我们选择随机队列名称。

  Secondly, once we disconnect the consumer the queue should be automatically deleted.
  其次,一旦我们断开消费者,就应该自动删除队列。

  In the Java client, when we supply no parameters to queueDeclare() we create a non-durable, exclusive, autodelete queue with a generated name:
  在Java客户机中,当我们没有参数的时候,我们会创建一个非持久的,专用的,自动删除的队列,它有一个随机 生成的名字:

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

  You can learn more about the exclusive flag and other queue properties in the guide on queues.
  您可以在队列指南中了解有关独占标志和其他队列属性的更多信息。

  At that point queueName contains a random queue name. For example it may look like amq.gen-JzTY20BRgKO-HjmUJj0wLg.
  此时,queueName包含一个随机队列名称。例如,它可能看起来像amq.gen-JzTY20BRgKO-HjmUJj0wLg。
在这里插入图片描述

Bindings 绑定

  We’ve already created a fanout exchange and a queue. Now we need to tell the exchange to send messages to our queue. That relationship between exchange and a queue is called a binding.
  我们已经创建了一个fanout交换器和一个队列。现在我们需要告诉交换器将消息发送到我们的队列。交换器和队列之间的关系称为绑定

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

From now on the logs exchange will append messages to our queue.
  从现在开始,日志交换器 会将消息附加到我们的队列中。

Listing bindings 列出绑定

  You can list existing bindings using, you guessed it,
  您可以使用它列出现有的绑定

	rabbitmqctl list_bindings

Putting it all together 把它们放在一起

在这里插入图片描述

  The producer program, which emits log messages, doesn’t look much different from the previous tutorial. The most important change is that we now want to publish messages to our logs exchange instead of the nameless one. We need to supply a routingKey when sending, but its value is ignored for fanout exchanges. Here goes the code for EmitLog.java program:
  生成日志消息的生产者程序与前一个教程没有太大的不同。最重要的变化是我们现在想要将消息发布到我们的日志交换器而不是无名交换器。我们需要在发送时提供routingKey,但是对于扇出(fanout)交换器,它的值会被忽略。这里是EmitLog.java程序的代码 :

  import java.io.IOException;
  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 java.io.IOException {

          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());
          System.out.println(" [x] Sent '" + message + "'");

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

(EmitLog.java source)

  As you see, after establishing the connection we declared the exchange. This step is necessary as publishing to a non-existing exchange is forbidden.
  如您所见,在建立连接后我们声明了交换器。此步骤是必要的,因为禁止发布到不存在的交换器。

  The messages will be lost if no queue is bound to the exchange yet, but that’s okay for us; if no consumer is listening yet we can safely discard the message.
  如果没有队列绑定到交换器,消息将会丢失,但这对我们没有问题; 如果没有消费者在监听,我们可以安全地丢弃该消息。

The code for 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);
	  }
	}

(ReceiveLogs.java source)

Compile as before and we’re done. 像以前一样编译,我们就完成了。

  javac -cp $CP EmitLog.java ReceiveLogs.java

If you want to save logs to a file, just open a console and type: 如果要将日志保存到文件,只需打开控制台并键入:

  java -cp $CP ReceiveLogs > logs_from_rabbit.log

If you wish to see the logs on your screen, spawn a new terminal and run:
如果您希望在屏幕上看到日志,请生成一个新终端并运行:

	java -cp $CP ReceiveLogs

And of course, to emit logs type: 当然,要发出日志类型:

java -cp $CP EmitLog

Using rabbitmqctl list_bindings you can verify that the code actually creates bindings and queues as we want. With two ReceiveLogs.java programs running you should see something like:
使用rabbitmqctl list_bindings,您可以验证代码是否实际创建了我们想要的绑定和队列。 运行两个ReceiveLogs.java程序时,您应该看到如下内容:

sudo rabbitmqctl list_bindings

# => Listing bindings ...
# => logs    exchange        amq.gen-JzTY20BRgKO-HjmUJj0wLg  queue           []
# => logs    exchange        amq.gen-vso0PVvyiRIL2WoV3i48Yg  queue           []
# => ...done.

  The interpretation of the result is straightforward: data from exchange logs goes to two queues with server-assigned names. And that’s exactly what we intended.
  结果的解释很简单:来自交换日志的数据转到两个具有服务器分配名称的队列。而这正是我们的意图。

  To find out how to listen for a subset of messages, let’s move on to tutorial 4
  要了解如何监听消息的子集,让我们继续学习 教程4

Production [Non-]Suitability Disclaimer

  Please keep in mind that this and other tutorials are, well, tutorials. They demonstrate one new concept at a time and may intentionally oversimplify some things and leave out others. For example topics such as connection management, error handling, connection recovery, concurrency and metric collection are largely omitted for the sake of brevity. Such simplified code should not be considered production ready.

  Please take a look at the rest of the documentation before going live with your app. We particularly recommend the following guides: Publisher Confirms and Consumer Acknowledgements, Production Checklist and Monitoring.

Getting Help and Providing Feedback

  If you have questions about the contents of this tutorial or any other topic related to RabbitMQ, don’t hesitate to ask them on the RabbitMQ mailing list.

Help Us Improve the Docs ❤️

  If you’d like to contribute an improvement to the site, its source is available on GitHub. Simply fork the repository and submit a pull request. Thank you!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值