Rabbitmq教程翻译(三)Publish/Subscribe

来源:互联网 发布:java数组倒置 编辑:程序博客网 时间:2024/05/17 11:58

发布/订阅

(使用Java客户端)

   在前面的教程中,我们创建了一个工作队列。一个工作队列背后的假设是,每个任务被交付给一个工作者。在这个教程中,我们会做完全不同的东西-我们将邮件传递到多个消费者。这种模式被称为“发布/订阅”。
为了阐明这种模式,我们要建立一个简单的日志记录系统。这将包括两个程序 - 首先会发出日志消息,第二个会接收并打印。

  在我们的记录系统,每个正在运行的副本会得到消息的接收程序。这样,我们将能够运行一个接收器和直接记录到磁盘,并在同一时间,我们就可以运行另一个接收机,并在屏幕上看到的日志。

从本质上讲,发表日志消息广播给所有的接收者。

交换

在以前的教程中,我们在一个队列中发送和接收信息。现在是时候引入完整的消息传递模型在rabbit中。

让我们快速温习下我们在前面的教程中包含的内容:

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

在RabbitMQ的消息模型的核心思想是,生产者从来没有直接发送任何消息到队列。其实,生产者并不知道一个消息将被传递到哪个队列。

相反,生产者只能发送消息的交换(exchange)。交换(exchange)是一个非常简单的事情。它一方面接收到的消息生产者,另一方面将消息推到队列。交换必须知道到底该怎么做它接收的消息。它应该被添加到一个特定的队列?它应该被追加到许多队列?或者它应该被丢弃。所定义的规则是通过交换类型(exchange type)。

有几个交换类型可供选择:directtopicheaders and fanout。我们将重点放在最后一个-the fanout。让我们创建一个这种类型的交换,并调用它记录

channel.exchangeDeclare("logs", "fanout");
扇交换(the fanout exchange)是很简单的。正如你可能已经猜到了,它只是广播它接收到它所知道的队列的所有消息。而这正是我们日志系统需要的。

列出所有交换

列出服务器上的交换,你可以运行有用的rabbitmqctl:

$ sudo rabbitmqctl list_exchangesListing exchanges ...        directamq.direct      directamq.fanout      fanoutamq.headers     headersamq.match       headersamq.rabbitmq.log        topicamq.rabbitmq.trace      topicamq.topic       topiclogs    fanout...done.

在这份列表中有一些AMQ *交换和默认(未命名)的交换。默认情况下他们是已经创建的,但它是不可能的,你需要使用它们的那一刻。

无名交换

在以前的教程中,我们不了解交换,但仍然可以将消息发送到队列。这是可能的,因为我们用的是默认的交换,我们确定由空字符串(“” )。

回想一下我们之前发布消息:

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

第一个参数是该交换(exchange)的名称。空字符串表示默认或无名的交换:消息路由到队列由指定名称的routingKey,如果routingkey存在的话。

现在,我们可以发布到我们命名的交换:

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

临时队列

你可能还记得,以前我们使用队列其中有一个指定的名称(记得hello和task_queue?)。能够说出一个队列,对我们来说至关重要-我们需要的工作者指向到同一个队列。给人一种队列名称是很重要的,当你想分享的生产者和消费者之间的队列。

但是这还不是我们的记录器的情况下。我们希望听到所有日志消息,不只是他们的一个子集。我们也只关心在目前流动的消息不会在旧的。要解决这个问题,我们需要两件事情。

首先,当我们连接到rabbit,我们需要一个新的空队列。要做到这一点,我们可以创建一个随机名称的队列,或者甚至更好 - 让服务器为我们选择一个随机队列名称。

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

在Java客户端,当我们没有提供参数到queueDeclare(), 我们创建非持久的,独家的,自动删除队列中生成的名称:

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

在这一点换queuename包含一个随机的队列名称。例如,它可能看起来像amq.gen JzTY20BRgKO HjmUJj0wLg

绑定

我们已经创造了一个扇出交流和队列。现在,我们需要告诉交换,将消息发送到我们的队列。这种交流和队列之间的关系称为绑定

channel.queueBind(queueName, "logs", "");
从现在日志交换消息附加到我们的队列。

Listing bindings

You can list existing bindings using, you guessed it, rabbitmqctl list_bindings.

全部放在一起

生产者程序,它发出的日志消息,看起来并没有太大的不同从以前的教程。最重要的变化是,我们现在要发布的消息,我们的日志,而不是无名的交流。我们需要提供一个routingKey,发送的时候,但它的价值被忽略交流。这里去的代码 脚本EmitLog.java

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829
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)

正如你看到的,在连接建立之后,我们宣布了交流。发布到一个不存在的交换是被禁止的,此步骤是neccesary的。

消息将丢失,如果没有队列势必交流,但是这是我们没关系,消费者如果不听,但我们可以安全地丢弃这个消息。

的代码ReceiveLogs.java

 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930313233343536
import java.io.IOException;import com.rabbitmq.client.ConnectionFactory;import com.rabbitmq.client.Connection;import com.rabbitmq.client.Channel;import com.rabbitmq.client.QueueingConsumer;public class ReceiveLogs {    private static final String EXCHANGE_NAME = "logs";    public static void main(String[] argv)                  throws java.io.IOException,                  java.lang.InterruptedException {        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");        QueueingConsumer consumer = new QueueingConsumer(channel);        channel.basicConsume(queueName, true, consumer);        while (true) {            QueueingConsumer.Delivery delivery = consumer.nextDelivery();            String message = new String(delivery.getBody());            System.out.println(" [x] Received '" + message + "'");        }    }}

(ReceiveLogs.java source)

编译之前,我们就大功告成了。

$ javac -cp rabbitmq-client.jar EmitLog.java ReceiveLogs.java
如果你想将日志保存到一个文件,只需打开一个控制台,然后键入:

$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar ReceiveLogs > logs_from_rabbit.log
如果你想在你的屏幕上看到的日志,生成一个新的终端,运行:

$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar ReceiveLogs
当然,发出日志类型:

$ java -cp .:commons-io-1.2.jar:commons-cli-1.1.jar:rabbitmq-client.jar EmitLog
使用用rabbitmqctl list_bindings您可以验证代码的实际创建绑定和队列,因为我们希望。有两个ReceiveLogs.java 运行的程序,你应该看到的是这样的:

$ sudo rabbitmqctl list_bindingsListing bindings ...logs    exchange        amq.gen-JzTY20BRgKO-HjmUJj0wLg  queue           []logs    exchange        amq.gen-vso0PVvyiRIL2WoV3i48Yg  queue           []...done.

结果的解释很简单:数据交换日志两个队列服务器分配的名称。这正是我们打算。

要找出如何监听一个子集的消息,让我们继续前进 教程4