文章目录
- [1. RPC 通信](#1. RPC 通信)
- [2. 引入依赖](#2. 引入依赖)
- [3. 客户端代码编写](#3. 客户端代码编写)
-
- [3.1 声明队列](#3.1 声明队列)
- [3.2 发送请求消息](#3.2 发送请求消息)
- [3.3 使用阻塞队列存储回调结果](#3.3 使用阻塞队列存储回调结果)
- [3.4 获取回调结果](#3.4 获取回调结果)
- [3.5 完整代码](#3.5 完整代码)
- [4. 服务端代码编写](#4. 服务端代码编写)
-
- [4.1 声明队列](#4.1 声明队列)
- [4.2 设置同时最多只能获取一个消息](#4.2 设置同时最多只能获取一个消息)
- [4.3 接收消息并做出相应处理](#4.3 接收消息并做出相应处理)
- [4.4 完整代码](#4.4 完整代码)
- [5. 运行程序](#5. 运行程序)
1. RPC 通信
RPC(Remote Procedure Call),即远程过程调用。它是一种通过网络从远程计算机上请求服务,而不需要了解底层网络的技术。类似于 HTTP 远程调用。
RabbitMQ 实现 RPC 通信的过程,大概是通过两个队列实现一个可回调的过程。

大概流程如下:
- 1、客户端发送消息到一个指定的队列,并在消息属性中设置 replyTo 字段,这个字段指定了一个回调队列,服务端处理后,会把响应结果发送到这个队列。
- 2、服务端接收到请求后,处理请求并发送响应消息到 replyTo 指定的回调队列。
- 3、客户端在回调队列上等待响应消息。一旦收到响应,客户端会检查消息的 correlationId 属性,以确保它是所期望的响应。
接下来我们看看 RPC 模式的实现步骤:
- 1、引入依赖
- 2、编写客户端
- 3、编写服务端
2. 引入依赖
先引入 rabbitmq 的依赖
xml
<!-- Source: https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.20.0</version>
<scope>compile</scope>
</dependency>
那么先去 Constants.java 里面定义队列。
java
// RPC模式
public static final String RPC_REQUEST_QUEUE = "rpc.request.queue";
public static final String RPC_RESPONSE_QUEUE = "rpc.response.queue";
3. 客户端代码编写
客户端代码主要流程如下:
- 1、声明两个队列,包含回调队列 replyQueueName,声明本次请求的唯一标志 corrId。
- 2、将 replyQueueName 和 corrId 配置到要发送的消息队列中。
- 3、使用阻塞队列来阻塞当前进程,监听回调队列中的消息,把请求放到阻塞队列中。
- 4、阻塞队列有消息后,主线程被唤醒,打印返回内容。
3.1 声明队列
代码如下所示:
java
channel.queueDeclare(Constants.RPC_REQUEST_QUEUE, true, false, false, null);
channel.queueDeclare(Constants.RPC_RESPONSE_QUEUE, true, false, false, null);
3.2 发送请求消息
代码如下所示:
java
// 3. 发生请求
String msg = "hello rpc......";
// 设置请求的唯一标识
String correlationId = UUID.randomUUID().toString();
// 设置请求的相关属性
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
.correlationId(correlationId)
.replyTo(Constants.RPC_RESPONSE_QUEUE)
.build();
channel.basicPublish("", Constants.RPC_REQUEST_QUEUE, props, msg.getBytes());
如下所示:

3.3 使用阻塞队列存储回调结果
代码如下所示:
java
// 4. 接收响应
// 使用阻塞队列来存储响应信息(其实就是等待响应完成)
final BlockingQueue<String> msgQueue = new ArrayBlockingQueue<>(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String respMsg = new String(body);
System.out.println("接收到回调消息: " + respMsg);
if (correlationId.equals(properties.getCorrelationId())) {
// 如果correlationId校验一致, 说明就是我们想要的响应
msgQueue.offer(respMsg);
}
}
};
3.4 获取回调结果
代码如下所示:
java
String result = msgQueue.take();
System.out.println("[RPC Client 响应结果]: " + result);
3.5 完整代码
代码如下所示:
java
package rpc;
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
/*
rpc 客户端
1. 发生请求
2. 接收响应
*/
public class RpcClient {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
// 1. 建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Constants.HOST); // MQ所在的服务器地址
factory.setPort(Constants.PORT); // 端口号
factory.setUsername(Constants.USERNAME); // 账号
factory.setPassword(Constants.PASSWORD); // 密码
factory.setVirtualHost(Constants.VIRTUAL_HOST); // 虚拟主机
Connection connection = factory.newConnection();
// 2. 开启 channel 通道
Channel channel = connection.createChannel();
// 3. 发生请求
String msg = "hello rpc......";
// 设置请求的唯一标识
String correlationId = UUID.randomUUID().toString();
// 设置请求的相关属性
AMQP.BasicProperties props = new AMQP.BasicProperties().builder()
.correlationId(correlationId)
.replyTo(Constants.RPC_RESPONSE_QUEUE)
.build();
channel.basicPublish("", Constants.RPC_REQUEST_QUEUE, props, msg.getBytes());
// 4. 接收响应
// 使用阻塞队列来存储响应信息(其实就是等待响应完成)
final BlockingQueue<String> msgQueue = new ArrayBlockingQueue<>(1);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String respMsg = new String(body);
System.out.println("接收到回调消息: " + respMsg);
if (correlationId.equals(properties.getCorrelationId())) {
// 如果correlationId校验一致, 说明就是我们想要的响应
msgQueue.offer(respMsg);
}
}
};
channel.basicConsume(Constants.RPC_RESPONSE_QUEUE, true, consumer);
String result = msgQueue.take();
System.out.println("[RPC Client 响应结果]: " + result);
}
}
4. 服务端代码编写
服务端代码主要流程如下:
- 1、接收消息。
- 2、根据消息内容进行响应处理,把应答结果返回到回调队列中。
4.1 声明队列
代码如下所示:
java
channel.queueDeclare(Constants.RPC_REQUEST_QUEUE, true, false, false, null);
4.2 设置同时最多只能获取一个消息
如果不设置 basicQos,RabbitMQ 会使用默认的 QoS 设置,其 prefetchCount 默认值为 0。当 prefetchCount 为 0 时,RabbitMQ 会根据内部实现和当前的网络状况等因素,可能会同时发送多条消息给消费者。这意味着在默认情况下,消费者可能会同时接收到多条消息,但具体数量不是严格保证的,可能会有所波动。
在 RPC 模式下,通常期望的是一对一的消息处理,即一个请求对应一个响应。消费者在处理完一个消息并确认之后,才会接收到下一条消息。
代码如下所示:
java
channel.basicQos(1);
System.out.println("Awaiting RPC request...");
4.3 接收消息并做出相应处理
代码如下所示:
java
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String request = new String(body, "UTF-8");
System.out.println("接收到请求: " + request);
String response = "针对request: " + request + ", 响应成功";
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
.correlationId(properties.getCorrelationId())
.build();
channel.basicPublish("", Constants.RPC_RESPONSE_QUEUE, basicProperties, response.getBytes());
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(Constants.RPC_REQUEST_QUEUE, false, consumer);
RabbitMQ 消息确定机制:在RabbitMQ中,basicConsume 方法的 autoAck 参数用于指定消费者是否应该自动向消息队列确认消息。
自动确认(autoAck=true):消息队列在将消息发送给消费者后,会立即从内存中删除该消息。这意味着,如果消费者处理消息失败,消息将丢失,因为消息队列认为消息已经被成功消费。
手动确认(autoAck=false):消息队列在将消息发送给消费者后,需要消费者显式地调用 basicAck 方法来确认消息。手动确认提供了更高的可靠性,确保消息不会被意外丢失,适用于消息处理重要且需要确保每个消息都被正确处理的场景。
4.4 完整代码
代码如下所示:
java
package rpc;
import com.rabbitmq.client.*;
import constant.Constants;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/*
* RPC 服务端
* 1. 接收请求
* 2. 发生响应
*/
public class RpcServer {
public static void main(String[] args) throws IOException, TimeoutException {
// 1. 建立连接
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(Constants.HOST); // MQ所在的服务器地址
factory.setPort(Constants.PORT); // 端口号
factory.setUsername(Constants.USERNAME); // 账号
factory.setPassword(Constants.PASSWORD); // 密码
factory.setVirtualHost(Constants.VIRTUAL_HOST); // 虚拟主机
Connection connection = factory.newConnection();
// 2. 开启 channel 通道
Channel channel = connection.createChannel();
// 3. 接收请求
channel.basicQos(1);
System.out.println("Awaiting RPC request...");
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String request = new String(body, "UTF-8");
System.out.println("接收到请求: " + request);
String response = "针对request: " + request + ", 响应成功";
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
.correlationId(properties.getCorrelationId())
.build();
channel.basicPublish("", Constants.RPC_RESPONSE_QUEUE, basicProperties, response.getBytes());
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
channel.basicConsume(Constants.RPC_REQUEST_QUEUE, false, consumer);
}
}
5. 运行程序
先运行客户端代码:可以看到在 request_queue 中有一条消息(它就相当于是生产者)

点击去,然后可以看到详细消息:

另外可以看到在 response_queue 中有一个消费者(它就相当于是消费者)

然后再运行服务端代码:可以看到已经接收到请求

然后再回到客户端,可以看到已经收到响应了
