RabbitMQ之RPC实现

2024-09-02 11:08
文章标签 实现 rpc rabbitmq

本文主要是介绍RabbitMQ之RPC实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


欢迎跳转到本文的原文链接:https://honeypps.com/mq/rabbitmq-how-to-use-rpc/

什么是RPC?

RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。

为什么RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯。由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用,

RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。


RabbitMQ怎么实现RPC调用?

Callback Queue

一般在RabbitMQ中做RPC是很简单的。客户端发送请求消息,服务器回复响应的消息。为了接受响应的消息,我们需要在请求消息中发送一个回调队列。可以使用默认的队列(which is exclusive in the java client.):

callbackQueueName = channel.queueDeclare().getQueue();
BasicProperties props = new BasicProperties.Builder().replyTo(callbackQueueName).build();
channel.basicPublish("", "rpc_queue",props,message.getBytes());
// then code to read a response message from the callback_queue...

Message properties

AMQP协议为消息预定义了一组14个属性。

private String contentType;
private String contentEncoding;
private Map<String,Object> headers;
private Integer deliveryMode;
private Integer priority;
private String correlationId;
private String replyTo;
private String expiration;
private String messageId;
private Date timestamp;
private String type;
private String userId;
private String appId;
private String clusterId;

大部分的属性是很少使用的。除了以下几种(其余有兴趣可以自行查看):

  • deliveryMode: 标记消息传递模式,2-消息持久化,其他值-瞬态。
  • contentType:内容类型,用于描述编码的mime-type. 例如经常为该属性设置JSON编码。
  • replyTo:应答,通用的回调队列名称,
  • correlationId:关联ID,方便RPC相应与请求关联。

Correlation Id

在上述方法中为每个RPC请求创建一个回调队列。这是很低效的。幸运的是,一个解决方案:可以为每个客户端创建一个单一的回调队列。

新的问题被提出,队列收到一条回复消息,但是不清楚是那条请求的回复。这是就需要使用correlationId属性了。我们要为每个请求设置唯一的值。然后,在回调队列中获取消息,查看这个属性,关联response和request就是基于这个属性值的。如果我们看到一个未知的correlationId属性值的消息,可以放心的无视它——它不是我们发送的请求。

你可能问道,为什么要忽略回调队列中未知的信息,而不是当作一个失败?这是由于在服务器端竞争条件的导致的。虽然不太可能,但是如果RPC服务器在发送给我们结果后,发送请求反馈前就挂掉了,这有可能会发送未知correlationId属性值的消息。如果发生了这种情况,重启RPC服务器将会重新处理该请求。这就是为什么在客户端必须很好的处理重复响应,RPC应该是幂等的。

Summary

这里写图片描述
RPC的处理流程:

  1. 当客户端启动时,创建一个匿名的回调队列。
  2. 客户端为RPC请求设置2个属性:replyTo,设置回调队列名字;correlationId,标记request。
  3. 请求被发送到rpc_queue队列中。
  4. RPC服务器端监听rpc_queue队列中的请求,当请求到来时,服务器端会处理并且把带有结果的消息发送给客户端。接收的队列就是replyTo设定的回调队列。
  5. 客户端监听回调队列,当有消息时,检查correlationId属性,如果与request中匹配,那就是结果了。

Demo Code

这里采用官网的一个例子来说明,RPC客户端通过RPC调用服务器来计算斐波那契额值。
首先是服务端的代码:

public class RPCServer {private static final String RPC_QUEUE_NAME = "rpc_queue";public static void main(String args[]) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost(RabbitConfig.ip);factory.setPort(RabbitConfig.port);factory.setUsername(RabbitConfig.username);factory.setPassword(RabbitConfig.password);Connection connection = factory.newConnection();Channel channel = connection.createChannel();channel.queueDeclare(RPC_QUEUE_NAME,false,false,false,null);channel.basicQos(1);QueueingConsumer consumer = new QueueingConsumer(channel);channel.basicConsume(RPC_QUEUE_NAME, false, consumer);System.out.println(" [x] Awaiting RPC requests");while(true){QueueingConsumer.Delivery delivery = consumer.nextDelivery();BasicProperties props = delivery.getProperties();BasicProperties replyProps = new BasicProperties.Builder().correlationId(props.getCorrelationId()).build();String message = new String(delivery.getBody());int n = Integer.parseInt(message);System.out.println(" [.] fib("+message+")");String repsonse = ""+fib(n);channel.basicPublish("", props.getReplyTo(), replyProps, repsonse.getBytes());channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);}}private static int fib(int n) throws Exception {if (n == 0) return 0;if (n == 1) return 1;return fib(n-1) + fib(n-2);}
}

RPC客户端:

public class RPCClient {private Connection connection;private Channel channel;private String requestQueueName = "rpc_queue";private String replyQueueName;private QueueingConsumer consumer;public RPCClient() throws IOException, TimeoutException {ConnectionFactory factory = new ConnectionFactory();factory.setHost(RabbitConfig.ip);factory.setPort(RabbitConfig.port);factory.setUsername(RabbitConfig.username);factory.setPassword(RabbitConfig.password);connection = factory.newConnection();channel = connection.createChannel();replyQueueName = channel.queueDeclare().getQueue();consumer = new QueueingConsumer(channel);channel.basicConsume(replyQueueName, true,consumer);}public String call(String message) throws IOException,ShutdownSignalException, ConsumerCancelledException,InterruptedException {String response = null;String corrId = UUID.randomUUID().toString();BasicProperties props = new BasicProperties.Builder().correlationId(corrId).replyTo(replyQueueName).build();channel.basicPublish("", requestQueueName, props, message.getBytes());while(true){QueueingConsumer.Delivery delivery = consumer.nextDelivery();if(delivery.getProperties().getCorrelationId().equals(corrId)){response = new String(delivery.getBody());break;}}return response;}public void close() throws Exception{connection.close();}public static void main(String args[]) throws Exception{RPCClient fibRpc = new RPCClient();System.out.println(" [x] Requesting fib(30)");String response = fibRpc.call("30");System.out.println(" [.] Got '"+response+"'");fibRpc.close();}
}

参考资料

  1. Remote procedure call (RPC)
  2. 轻松搞定RabbitMQ(七)——远程过程调用RPC

欢迎跳转到本文的原文链接:https://honeypps.com/mq/rabbitmq-how-to-use-rpc/


欢迎支持笔者新作:《深入理解Kafka:核心设计与实践原理》和《RabbitMQ实战指南》,同时欢迎关注笔者的微信公众号:朱小厮的博客。


这篇关于RabbitMQ之RPC实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1129783

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、