官方文档地址: 1 “Hello World!”
最简单的东西。
前提条件
本教程假设你已经安装了 RabbitMQ 并在本地主机端口(5672)上运行。
简介
RabbitMQ 是一个消息代理,负责接收和转发消息。你可以把它想象成一个邮局:当你把想要投寄的邮件放入一个邮箱时,你可以确定邮递员最终会把邮件送到你指定的收件人那里。在这个类比中,RabbitMQ 是一个邮箱,也是一个邮局和一个邮递员。
RabbitMQ 和邮局之间的主要区别是它不处理纸张,而是接受、存储和转发数据消息的二进制大对象。
RabbitMQ 和一般的消息传递都使用了一些术语。
- 生产无非是为了发送。发送消息的程序是
producer
:
queue
是驻留在 RabbitMQ 内的邮箱的名称。一个队列只受主机的内存和磁盘限制,它本质上是一个大的消息缓冲区。许多生产者可以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。我们是这样表示队列的:
- 消费和接收有类似的含义。
consumer
是一个等待接收消息的程序:
Hello World
在本部分教程中,我们将用Java
编写两个程序:发送消息的生产者和接收消息并将其打印出来的消费者。我们将使用消息队列传递"Hello World"
作为例子进行讲解。
在下面的图表中,"P"
是我们的生产者,"C"
是我们的消费者。中间的盒子是一个队列。
Java客户端库
RabbitMQ 使用多个协议。本教程使用AMQP 0-9-1
,这是一种开放的、通用的消息传递协议。RabbitMQ 有许多不同语言的客户端。我们将使用 RabbitMQ 提供的 Java 客户端。
下载客户端库及其依赖项(SLF4J API 和 SLF4J Simple)。将这些文件以及教程中的 Java 文件复制到您的工作目录中。
请注意,对于教程来说,SLF4J Simple
已经足够了,但是您应该在生产环境中使用完整的日志库,比如Logback
。
(中央 Maven 库中也有 RabbitMQ 的 Java 客户端,groupId 为com.rabbitmq
,artifactId 为amqp-client
)。
现在我们有了 Java 客户端及其依赖项,可以编写一些代码了。
这里补充下 RabbitMQ 的 Java 客户端的 Maven 依赖,方便 Maven 项目使用:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.1</version>
</dependency>
生产者
我们将调用消息发布者(发送方)Send
和消息使用者(接收方)Recv
。发布者将连接到 RabbitMQ,发送一条消息,然后退出。
在Send.java
中,我们需要导入一些类:
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
创建类并命名队列:
public class Send {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) throws Exception {
...
}
}
然后我们可以创建一个到服务器的连接:
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
}
这里我们连接到本地机器上的 RabbitMQ 节点 - 也就是localhost
。如果我们想连接到另一台机器上的节点,我们只需在这里指定它的主机名或IP
地址。
接下来我们创建一个channel
,它是完成任务的大部分 API 所在的地方。注意,我们可以使用try-with-resources
语句,因为Connection
和Channel
都实现了java.io.Closeable
。这样我们就不需要在代码中显式地关闭它们。
要发送消息,必须先声明一个队列;然后发布一条消息到队列中,所有这些都在try-with-resources
语句中实现:
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 + "'");
声明队列是幂等的 - 只有在队列不存在时才会创建它。消息内容是一个字节数组,因此您可以对其中的任何内容进行编码。
这是完整的Send.java
代码:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author wangbo
* @date 2019/10/22 18:05
*/
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();
//声明一个队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "Hello World!";
//发布一条消息
channel.basicPublish("", QUEUE_NAME, null, message.getBytes("UTF-8"));
System.out.println(" [x] Sent '" + message + "'");
}
}
}
生产者不工作
如果这是你第一次使用 RabbitMQ,代码执行了但是没有看到“发送”的消息,原因可能是服务启动时没有足够的空闲磁盘空间(默认情况下,它需要至少200MB
的空闲空间),因此拒绝接收消息。检查服务器日志文件以确认问题并在必要时减少限制。配置文件文档将向你展示如何设置disk_free_limit
。
消费者
我们的消费者监听来自 RabbitMQ 的消息,所以不像发布者只发布一条消息就可以退出了,我们需要保持消费者运行来监听消息并打印出来。
Recv.java
需要导入的类和Send.java
大体一样:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
我们将使用的DeliverCallback
接口来缓冲由服务器推送给我们的消息。
设置与生产者服务器基本相同;我们打开一个连接和一个通道,并声明我们将要使用的队列。注意,需要与生产者发布的队列名称相匹配。
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] argv) 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 messages. To exit press CTRL+C");
}
}
注意,我们在这里也声明了队列。因为消费者可能在生产者启动之前启动,所以这里是为了在使用队列中的消息时确保队列存在。
为什么不使用try-with-resource
语句来自动关闭通道和连接呢?因为我们希望生产者异步侦听消息,当消息到达时,进程保持活动的状态。
我们将告诉服务器将队列中的消息发送给我们。因为它将异步地推送消息,所以我们以对象的形式提供一个回调,它将缓冲消息,直到我们准备好使用它们。这就是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 -> {
});
这是完整的Recv.java
代码:
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author wangbo
* @date 2019/10/22 18:25
*/
public class Recv {
private final static String QUEUE_NAME = "hello";
public static void main(String[] args) throws IOException, TimeoutException {
//创建一个连接器连接到服务器
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 messages. To exit press CTRL+C");
//回调对象
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 -> {
});
}
}
把它们放一起
你可以使用 RabbitMQ 的 Java 客户端在类路径中编译这两个:
javac -cp amqp-client-5.7.1.jar Send.java Recv.java
要运行它们,您需要在类路径上添加rabbitmq-client.jar
及其依赖。在终端中,运行消费者(接收方):
java -cp .:amqp-client-5.7.1.jar:slf4j-api-1.7.26.jar:slf4j-simple-1.7.26.jar Recv
然后,运行生产者(发送方):
java -cp .:amqp-client-5.7.1.jar:slf4j-api-1.7.26.jar:slf4j-simple-1.7.26.jar Send
在 Windows 上,使用分号而不是冒号来分隔类路径中的项。
消费者将打印它通过 RabbitMQ 从生产者获得的消息。消费者将继续运行,等待消息(可以使用Ctrl+C
来停止它),所以需要从另一个终端运行生产者。
队列列表
您可能想要查看 RabbitMQ 有哪些队列以及队列中有多少消息。你可以(以管理员权限)使用rabbitmqctl
工具:
sudo rabbitmqctl list_queues
在 Windows 上:
rabbitmqctl.bat list_queues
接下来是第2
部分,构建一个简单的工作队列。
提示
为了节省输入,你可以为类路径设置一个环境变量。
export CP=.:amqp-client-5.7.1.jar:slf4j-api-1.7.26.jar:slf4j-simple-1.7.26.jar
java -cp $CP Send
在 Windows 上:
set CP=.;amqp-client-5.7.1.jar;slf4j-api-1.7.26.jar;slf4j-simple-1.7.26.jar
java -cp %CP% Send
实际测试
下面的并非官方教程的内容,是我在本地 Windows 系统上对官方教程的测试。
下载三个 Jar 包,放到了目录D:\test
。将Send.java
和Recv.java
也放到该目录下。执行编译命令:
D:\test>javac -cp amqp-client-5.7.1.jar Send.java Recv.java
启动消费者,运行Recv.class
:
D:\test>java -cp .;amqp-client-5.7.1.jar;slf4j-api-1.7.26.jar;slf4j-simple-1.7.26.jar Recv
[*] Waiting for messages. To exit press CTRL+C
在新窗口启动生产者,运行Send.class
:
D:\test>java -cp .;amqp-client-5.7.1.jar;slf4j-api-1.7.26.jar;slf4j-simple-1.7.26.jar Send
[x] Sent 'Hello World!'
这时可以在消费者窗口接收到生产者发送的Hello World!
字符串。
[x] Received 'Hello World!'
再测试下设置环境变量,简化消息发送命令,如下所示,没有问题:
D:\test>set CP=.;amqp-client-5.7.1.jar;slf4j-api-1.7.26.jar;slf4j-simple-1.7.26.jar
D:\test>java -cp %CP% Send
[x] Sent 'Hello World!'
D:\test>java -cp %CP% Send
[x] Sent 'Hello World!'
D:\test>
接下来测试下rabbitmqctl
工具,查看 RabbitMQ 有哪些队列以及队列中有多少消息。
D:\Programmer\RabbitMQ Server\rabbitmq_server-3.7.8\sbin>rabbitmqctl.bat list_queues
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
hello 0
可以看出有个队列,名字是hello
,消息0
条。