【RabbitMQ】RPC 通信(使用案例)

文章目录

  • [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 中有一个消费者(它就相当于是消费者)

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

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

相关推荐
天下财经热2 小时前
2026年4月分布式工商业光伏建设企业技术解析!
分布式
梵得儿SHI2 小时前
SpringCloud 进阶拓展:分布式事务终极解决方案 Seata AT/TCC 模式全栈实战(含生产级避坑指南)
分布式·spring·spring cloud·seata·分布式事务·tcc·tc集群部署
喜欢流萤吖~3 小时前
分布式事务:微服务的数据一致性之困
分布式·微服务·架构
lifewange5 小时前
RPC 是什么
网络·网络协议·rpc
白晨并不是很能熬夜15 小时前
【PRC】第 2 篇:Netty 通信层 — NIO 模型 + 自定义协议 + 心跳
java·开发语言·后端·面试·rpc·php·nio
大G的笔记本20 小时前
分布式事务
分布式
weixin_4196583120 小时前
RabbitMQ 的高级特性
java·分布式·rabbitmq
白晨并不是很能熬夜20 小时前
【RPC】第 1 篇:全景篇 — 一次 RPC 调用的完整旅程
java·网络·后端·网络协议·面试·rpc·java-zookeeper
_F_y21 小时前
仿RabbitMQ实现消息队列-服务端核心模块实现(1)
分布式·rabbitmq