发布/订阅
(使用Java客户端)
在前面的教程中,我们创建了一个工作队列。一个工作队列背后的假设是,每个任务被交付给一个工作者。在这个教程中,我们会做完全不同的东西-我们将邮件传递到多个消费者。这种模式被称为“发布/订阅”。
为了阐明这种模式,我们要建立一个简单的日志记录系统。这将包括两个程序 - 首先会发出日志消息,第二个会接收并打印。
在我们的记录系统,每个正在运行的副本会得到消息的接收程序。这样,我们将能够运行一个接收器和直接记录到磁盘,并在同一时间,我们就可以运行另一个接收机,并在屏幕上看到的日志。
从本质上讲,发表日志消息广播给所有的接收者。
交换
在以前的教程中,我们在一个队列中发送和接收信息。现在是时候引入完整的消息传递模型在rabbit中。
让我们快速温习下我们在前面的教程中包含的内容:
- Â 生产者是一个用户的应用程序发送消息。
- Â 队列是消息存储的缓冲区。
- Â 消费者是一个用户接收消息的应用程序。
在RabbitMQ的消息模型的核心思想是,生产者从来没有直接发送任何消息到队列。其实,生产者并不知道一个消息将被传递到哪个队列。
相反,生产者只能发送消息的交换(exchange)。交换(exchange)是一个非常简单的事情。它一方面接收到的消息生产者,另一方面将消息推到队列。交换必须知道到底该怎么做它接收的消息。它应该被添加到一个特定的队列?它应该被追加到许多队列?或者它应该被丢弃。所定义的规则是通过交换类型(exchange type)。
有几个交换类型可供选择:direct, topic, headers and fanout。我们将重点放在最后一个-the fanout。让我们创建一个这种类型的交换,并调用它记录:
channel.exchangeDeclare("logs", "fanout");扇交换(the fanout exchange)是很简单的。 正如你可能已经猜到了,它只是广播它接收到它所知道的队列的所有消息。 而这正是我们日志系统需要的。
列出所有交换
列出服务器上的交换,你可以运行有用的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.
在这份列表中有一些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 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
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();
}
//...
}
|
正如你看到的,在连接建立之后,我们宣布了交流。发布到一个不存在的交换是被禁止的,此步骤是neccesary的。
消息将丢失,如果没有队列势必交流,但是这是我们没关系,消费者如果不听,但我们可以安全地丢弃这个消息。
的代码ReceiveLogs.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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 + "'");
}
}
}
|
$ 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_bindings Listing bindings ... logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue [] logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue [] ...done.
结果的解释很简单:数据交换日志两个队列服务器分配的名称。这正是我们打算。
要找出如何监听一个子集的消息,让我们继续前进 教程4