我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何使用RabbitMQ实现RPC

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何使用RabbitMQ实现RPC

这篇文章给大家分享的是有关如何使用RabbitMQ实现RPC的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

背景知识

RabbitMQ

RabbitMQ 是基于 AMQP 协议实现的一个消息队列(Message Queue),Message Queue 是一个典型的生产者/消费者模式。生产者发布消息,消费者消费消息,生产者和消费者之间是解耦的,互相不知道对方的存在。

如何使用RabbitMQ实现RPC

RPC

Remote Procedure Call:远程过程调用,一次远程过程调用的流程即客户端发送一个请求到服务端,服务端根据请求信息进行处理后返回响应信息,客户端收到响应信息后结束。

如何使用RabbitMQ实现RPC

如何使用 RabbitMQ 实现 RPC?

使用 RabbitMQ 实现 RPC,相应的角色是由生产者来作为客户端,消费者作为服务端。

但 RPC 调用一般是同步的,客户端和服务器也是紧密耦合的。即客户端通过 IP/域名和端口链接到服务器,向服务器发送请求后等待服务器返回响应信息。

但 MQ 的生产者和消费者是完全解耦的,那么如何用 MQ 实现 RPC 呢?很明显就是把 MQ 当作中间件实现一次双向的消息传递:

如何使用RabbitMQ实现RPC

客户端和服务端即是生产者也是消费者。客户端发布请求,消费响应;服务端消费请求,发布响应。

具体实现

MQ部分的定义

请求信息的队列

我们需要一个队列来存放请求信息,客户端向这个队列发布请求信息,服务端消费该队列处理请求。该队列不需要复杂的路由规则,直接使用 RabbitMQ 默认的 direct exchange 来路由消息即可。

响应信息的队列

存放响应信息的队列不应只有一个。如果存在多个客户端,不能保证响应信息被发布请求的那个客户端消费到。所以应为每一个客户端创建一个响应队列,这个队列应该由客户端来创建且只能由这个客户端使用并在使用完毕后删除,这里可以使用 RabbitMQ 提供的排他队列(Exclusive Queue):

channel.queueDeclare(queue:"", durable:false, exclusive:true, autoDelete:false, new HashMap<>())

并且要保证队列名唯一,声明队列时名称设为空 RabbitMQ 会生成一个唯一的队列名。

exclusive 设为 true 表示声明一个排他队列,排他队列的特点是只能被当前的连接使用,并且在连接关闭后被删除。

一个简单的 demo(使用 pull 机制)

我们使用一个简单的 demo 来了解客户端和服务端的处理流程。

发布请求

  • 编写代码前的一个小问题

我们在声明队列时为每一个客户端声明了独有的响应队列,那服务器在发布响应时如何知道发布到哪个队列呢?其实就是客户端需要告诉服务端将响应发布到哪个队列,RabbitMQ 提供了这个支持,消息体的 Properties 中有一个属性 reply_to 就是用来标记回调队列的名称,服务器需要将响应发布到 reply_to 指定的回调队列中。

解决了这个问题之后我们就可以编写客户端发布请求的代码了:

// 定义响应回调队列String replyQueueName = channel.queueDeclare("", false, true, false, new HashMap<>()).getQueue();// 设置回调队列到 PropertiesAMQP.BasicProperties properties = new AMQP.BasicProperties.Builder() .replyTo(replyQueueName) .build();String request = "request";// 发布请求channel.basicPublish("", "rpc_queue", properties, request.getBytes());

Direct reply-to:

RabbitMQ 提供了一种更便捷的机制来实现 RPC,不需要客户端每次都定义回调队列,客户端发布请求时将 replyTo 设为 amq.rabbitmq.reply-to ,消费响应时也指定消费 amq.rabbitmq.reply-to ,RabbitMQ 会为客户端创建一个内部队列

消费请求

接下来是服务端处理请求的部分,接收到请求后经过处理将响应信息发布到 reply_to 指定的回调队列:

// 服务端 Consumer 的定义public class RpcServer extends DefaultConsumer { public RpcServer(Channel channel) { super(channel); } @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { String msg = new String(body); String response = (msg + " Received"); // 获取回调队列名 String replyTo = properties.getReplyTo(); // 发布响应消息到回调队列 this.getChannel().basicPublish("", replyTo, new AMQP.BasicProperties(), response.getBytes()); }}...// 启动服务端 Consumerchannel.basicConsume("rpc_queue", true, new RpcServer(channel));

接收响应

客户端如何接收服务器的响应呢?有两种方式:1.轮询的去 pull 回调队列中的消息,2.异步的消费回调队列中的消息。我们在这里简单实现第一种方案。

GetResponse getResponse = null;while (getResponse == null) { getResponse = channel.basicGet(replyQueueName, true);}String response = new String(getResponse.getBody());

一个简单的基于 RabbitMQ 的 RPC 模型已经实现了,但这个 demo 并不实用,因为客户端每次发送完请求都要同步的轮询等待响应消息,只能每次处理一个请求。RabbitMQ 的 pull 模式效率也比较低。

实现一个完备可用的 RPC 模式需要做的工作还有很多,要处理的关键点也比较复杂,有句话叫不要重复造轮子,spring 已经实现了一个完备可用的 RPC 模式的库,接下来我们来了解一下。顺便在此给大家推荐一个Java架构方面的交流学习群:698581634,进群即可获取Java架构师资料:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,群里一定有你需要的资料,大家赶紧加群吧。

Spring Rabbit 中的实现

和上面 demo 的 pull 模式一次只能处理一个请求相对应的:如何异步的接收响应并处理多个请求呢?关键点就在于我们需要记录请求和响应并将它们关联起来,RabbitMQ 也提供了支持,Properties 中的另一个属性 correlation_id 用来标识一个消息的唯一 id。

参考 spring-rabbit 中的 convertSendAndReceive 方法的实现,为每一次请求生成一个唯一的 correlation_id :

private final AtomicInteger messageTagProvider = new AtomicInteger();...String messageTag = String.valueOf(this.messageTagProvider.incrementAndGet());...message.getMessageProperties().setCorrelationId(messageTag);

并使用一个 ConcurrentHashMap 来维护 correlation_id 和响应信息的映射:

private final Map<String, PendingReply> replyHolder = new ConcurrentHashMap<String, PendingReply>();...final PendingReply pendingReply = new PendingReply();this.replyHolder.put(correlationId, pendingReply);

PendingReply 中有一个 BlockingQueue 存放响应信息,在发送完请求信息后调用 BlockingQueue 的 pull 方法并设置超时时间来获取响应:

private final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>(1);

public Message get ( long timeout , TimeUnit unit ) throws InterruptedException { Object reply = this . queue . poll ( timeout , unit ); return reply == null ? null : processReply ( reply

);

}

在获取响应后不论结果如何,都会将 PendingReply 从 replyHolder 中移除,防止 replyHolder 中积压超时的响应消息:

try { reply = exchangeMessages(exchange, routingKey, message, correlationData, channel, pendingReply,messageTag);} finally { this.replyHolder.remove(messageTag); ...}

响应信息是何时如何被放到这个 BlockingQueue 中的呢?看一下 RabbitTemplate 接收消息的地方:

public void onMessage(Message message) {String messageTag; if (this.correlationKey == null) { // using standard correlationId property messageTag = message.getMessageProperties().getCorrelationId(); } else { messageTag = (String) message.getMessageProperties() .getHeaders().get(this.correlationKey); } // 存在 correlation_id 才认为是RPC的响应信息,不存在时不处理 if (messageTag == null) { logger.error("No correlation header in reply"); return; } // 从 replyHolder 中取出 correlation_id 对应的 PendingReply PendingReply pendingReply = this.replyHolder.get(messageTag); if (pendingReply == null) { if (logger.isWarnEnabled()) { logger.warn("Reply received after timeout for " + messageTag); } throw new AmqpRejectAndDontRequeueException("Reply received after timeout"); } else { restoreProperties(message, pendingReply); // 将响应信息 add 到 BlockingQueue 中 pendingReply.reply(message); }}

以上的 spring 代码隐去了很多额外部分的处理和细节,只关注关键的部分。

至此一个完整可用的由 RabbitMQ 作为中间件实现的 RPC 模式就完成了。

总结

服务端

服务端的实现比较简单,和一般的 Consumer 的区别只在于需要将请求回复到 replyTo 指定的 queue 中并带上消息标识 correlation_id 即可

服务端的一点小优化:

超时的处理是由客户端来实现的,那服务端有没有可以优化的地方呢?

答案是有的:如果我们的服务端处理比较耗时,如何判断客户端是否还在等待响应呢?

我们可以使用 passive 参数去检查 replyTo 的 queue 是否存在,因为客户端声明的是内部队列,客户端如果断掉链接了这个 queue 就不存在了,这时服务端就无需处理这个消息了。

客户端

客户端承担了更多的工作量,包括:

  • 声明 replyTo 队列(使用 amq.rabbitmq.reply-to 会简单很多)

  • 维护请求和响应消息(使用唯一的 correlation_id 来关联)

  • 消费服务端的返回

  • 处理超时等异常情况(使用BlockingQueue来阻塞获取)

好在 spring 已经实现了一套完备可靠的代码,我们在清楚了流程和关键点之后,可以直接使用 spring 提供的 RabbitTemplate ,无需自己实现。

使用 MQ 实现 RPC 的意义

通过 MQ 实现 RPC 看起来比客户端和服务器直接通讯要复杂一些,那我们为什么要这样做呢?或者说这样做有什么好处:

  1. 将客户端和服务器解耦:客户端只是发布一个请求到 MQ 并消费这个请求的响应。并不关心具体由谁来处理这个请求,MQ 另一端的请求的消费者可以随意替换成任何可以处理请求的服务器,并不影响到客户端。

  2. 减轻服务器的压力:传统的 RPC 模式中如果客户端和请求过多,服务器的压力会过大。由 MQ 作为中间件的话,过多的请求而是被 MQ 消化掉,服务器可以控制消费请求的频次,并不会影响到服务器。

  3. 服务器的横向扩展更加容易:如果服务器的处理能力不能满足请求的频次,只需要增加服务器来消费 MQ 的消息即可,MQ会帮我们实现消息消费的负载均衡

  4. 可以看出 RabbitMQ 对于 RPC 模式的支持也是比较友好地,

  5. amq.rabbitmq.reply-to , reply_to , correlation_id 这些特性都说明了这一点,再加上 spring-rabbit 的实现,可以让我们很简单的使用消息队列模式的 RPC 调用。

感谢各位的阅读!关于“如何使用RabbitMQ实现RPC”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何使用RabbitMQ实现RPC

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何使用RabbitMQ实现RPC

这篇文章给大家分享的是有关如何使用RabbitMQ实现RPC的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。背景知识RabbitMQRabbitMQ 是基于 AMQP 协议实现的一个消息队列(Message Que
2023-06-02

利用RabbitMQ实现RPC(pyth

RPC——远程过程调用,通过网络调用运行在另一台计算机上的程序的函数\方法,是构建分布式程序的一种方式。RabbitMQ是一个消息队列系统,可以在程序之间收发消息。利用RabbitMQ可以实现RPC。本文所有操作都是在CentOS7.3上进
2023-01-31

springboot+HttpInvoke如何实现RPC调用

小编给大家分享一下springboot+HttpInvoke如何实现RPC调用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!开始用springboot2+hession4实现RPC服务时,发现第一个服务可以调用成功,但第二
2023-06-29

Python如何使用RPC

本篇内容介绍了“Python如何使用RPC”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!主要内容所谓RPC,是远程过程调用(Remote P
2023-07-02

如何在 Golang 中使用 RPC 实现文件上传?

使用 rpc 实现文件上传:创建 rpc 服务器来处理文件上传请求,使用 net/rpc 包创建。创建 rpc 客户端来向服务器发起文件上传请求,使用 net/rpc 包创建,将文件序列化并通过 rpc 调用发送。如何在 Go 中使用 RP
如何在 Golang 中使用 RPC 实现文件上传?
2024-05-13

SpringBoot中使用RabbitMQ的RPC功能案例分析

这篇文章主要讲解了“SpringBoot中使用RabbitMQ的RPC功能案例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringBoot中使用RabbitMQ的RPC功能案例分析
2023-06-25

python如何通过protobuf实现rpc

由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc。rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行google,这里只是做个简单的介绍。rp
2022-06-04

Golang如何用RPC实现转发服务

今天小编给大家分享一下Golang如何用RPC实现转发服务的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。首先,我们需要了解一
2023-07-06

使用spring boot如何实现对RabbitMQ进行整合

使用spring boot如何实现对RabbitMQ进行整合?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。springboot集成RabbitMQ非常简单,如果
2023-05-31

laravel如何使用RabbitMQ

这篇文章主要介绍“laravel如何使用RabbitMQ”,在日常操作中,相信很多人在laravel如何使用RabbitMQ问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”laravel如何使用RabbitMQ
2023-06-22

Golang如何实现简易的rpc调用

这篇文章主要介绍“Golang如何实现简易的rpc调用”,在日常操作中,相信很多人在Golang如何实现简易的rpc调用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang如何实现简易的rpc调用”的疑
2023-07-05

.NET Core中RabbitMQ使用死信队列如何实现

本篇内容介绍了“.NET Core中RabbitMQ使用死信队列如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!在.NET Core中
2023-07-05

node中如何实现RPC通信

本篇内容主要讲解“node中如何实现RPC通信”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“node中如何实现RPC通信”吧!什么是RPC?RPC:Remote Procedure Call(远
2023-07-04

如何实现一个XML-RPC server/client

本篇内容介绍了“如何实现一个XML-RPC server/client”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1 从大数据部署系统 M
2023-06-02

rabbitmq削峰限流如何实现

RabbitMQ的削峰限流可以通过以下方式实现:1. 预取(Prefetch)机制:可以设置每个消费者一次从队列中获取的消息数量。通过调整预取数量,可以控制每个消费者处理消息的速度,从而实现限流。例如,将预取数量设置为1,即每次只获取一条消
2023-10-09

使用Java怎么实现一个RPC框架

使用Java怎么实现一个RPC框架?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。一、RPC简介RPC,全称为Remote Procedure Call,即远程过程调用,它是一个
2023-05-30

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录