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

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

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

相关推荐
Jackyzhe11 小时前
从零学习Kafka:消费者组重平衡
分布式·学习·kafka
海南java第二人12 小时前
ClickHouse 部署模式完全指南:从单机到分布式集群的生产级选型
分布式·clickhouse
gQ85v10Db14 小时前
Redis 分布式锁进阶第三十四篇
数据库·redis·分布式
大G的笔记本16 小时前
Redis 分布式锁自动续期机制
数据库·redis·分布式
逆境不可逃19 小时前
Hello-Agents 第二部分-第六章:框架开发实践
java·人工智能·分布式·学习·架构·rabbitmq
富士康质检员张全蛋20 小时前
Kafka架构 HW和LEO
分布式·kafka
徐子童20 小时前
RabbitMQ---开篇
rabbitmq
gQ85v10Db21 小时前
Redis分布式锁进阶第三十八篇
数据库·redis·分布式
豆沙沙包?21 小时前
SpringCloud01-03---简介/从单体到集群架构/从单体到分布式架构
分布式·微服务·架构·springcloud
敖正炀21 小时前
分布式系统设计流程与实战推演
分布式