简介
RabbitMQ是一个消息中间件:它接收和发送消息。你可以把它作为一个邮局:当你想要发送邮件并将信封放到邮箱后,你可以确定邮递员最终将会把这个封信送到收信人手中。基于这个比喻,RabbitMQ就充当的是邮箱,邮局和邮递员的角色。
RabbitMQ和邮局主要的不同之处在于,它不需要以纸作为媒介,不处理信封。相反它接收,存储并发送二进制数据消息。
RabbitMQ和消息传递通常会使用一些术语:
生产仅意味着发送,一段发送消息的代码就是一个生产者:
队列就是RabbitMQ内部邮箱的名称。虽然消息通过RabbitMQ和你的应用程序进行传递,但是它们只能被存储在一个队列中。队列的存储容量仅被主机的内存和硬盘的容量限制,本质是它就是一个大的消息缓冲区。可以有多个生产者向一个队列发送消息,多个消费者尝试从一个对列中接收数据。以下是我们表示队列的方式:
消费与接收有相同的含义,一段主要等待接收消息的程序就是一个消费者:
注意生产者,消费者,中间件不要求必须部署在同一个主机上,相反在大多数的应用程序中它们都是在不同的主机中。一个应用程序既可以是生产者也可以是消费者。
“Hello World”
在教程的这部分我们将用java写两个程序:一个生产者发送一个消息,一个消费者接收消息并把它们打印出来。我们将忽略Java API的一些细节,只要关注实现这个简单的功能即可。我们将发送一个"Hello World"的字符串消息。
如下图所示,"P"是我们的生产者,"C"是我们的消费者。中间的框是一个队列,是RabbitMQ代表消费者保留的消息缓冲区。
RabbitMQ使用了多种协议,这个教程使用的是AMQP 0-9-1, 它是一个开放的,面向消息的通用协议。RabbitMQ支持很多不同语言的客户端。这里我们将使用它提供的Java客户端。
Sending
我们将我们的消息发送者称为Send,消息消费者称为Recv。生产者将连接RabbitMQ,发送一个消息后退出。
在Send.java中,我们需要导入一些类:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
设置类并给队列命名
public class Send {
private final static String QUEUE_NAME = “hello”;
public static void main(String[] args) throws IOException, TimeoutException
.....
}
}
然后我们可以创建一个服务器连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(“localhost”);
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
}
这个连接抽象了套接字(socket)连接,并且它实现了协议版本协商和身份认证等功能。这里我们连接了本地机器上的中间件,所以参数设置为"localhost"。如果我们希望连接其他机器上的中间件,我们可以指定机器的名称或者是IP地址。
接下来,我们创建一个通道(Channel),这是一个大多数API完成工作的地方。注意我们可以使用一个try-with-resource语句,因为Connection和Channel都实现了java.io.Closeable接口,使用这种方式我们不需要在代码中明确的关闭它们。
为实现发送,我们必须声明一个要发送的队列,然后我们可以推送一个消息到此队列,这些代码都在try-with-resource语句中:
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = “hello world”;
channel.basicPublish(“”, QUEUE_NAME, null, message.getBytes());
System.out.println(“[x] Sent '” + message + “'”);
声明一个队列的操作是幂等的,只有声明的队列不存在时才会被创建。 这里消息的内容是一个二进制数组,所以你可以使用你喜欢的方式进行编码。
Receving
以上是我们的生产者,我们的消费者监听来自RabbitMQ的消息,所以不像生产者发送一个消息后就退出,我们要保持程序运行来监听消息并将它们打印出来。
与Send一样要导入相同的类:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
额外的DefaultConsumer是实现了Consumer接口的类,我们使用它来缓存服务器推送给我们的消息。
配置与生产者相似:我们打开一个Connection和Channel,声明我们将要消费的队列,注意这个队列必须要与Send 推送消息的队列相匹配。
public class Recv {
private static final String QUEUE_NAME = "hello";
public static void main(String[] args) throws Exception{
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
System.out.println(" [*] Waiting for message To exit press CTRL+C");
}
}
注意我们在这也声明了队列,因为我们可能在生产者启动之前启动消费者,我们希望确保在消息者尝试从队列消费时,队列已经存在。
为什么我们不使用try-with-resource语句来自动关闭channel和connection呢?通过这个语句我们可以简单的使程序运行关闭所有资源,然后退出。但是在这里我们希望当消费者异步得监听到消息到达时程序能保持活跃状态。
我们知道服务器向我们传递来自队列的消息,因为我们推送消息时候异步的,所以我们提供了一个对象形式的回调,它可以缓存消息直到我们准备使用它们。这就是DeliverCallback子类所做的事情:
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), “UTF-8”);
System.out.println(" [x] Received ‘" + message + "’");
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> {});
验证
Send
Recv