一次RPC调用过程是怎么样的?

注册中心

RPC(Remote Procedure Call)翻译成中文就是 {远程过程调用}。RPC 框架起到的作用就是为了实现,调用远程方法时,能够做到和调用本地方法一样,让开发人员更专注于业务开发,不用去考虑网络编程等细节。

RPC 框架怎么就实现不让开发人员关注网络编程等细节呢?

首先我们区分两个角色一个服务提供方,一个是服务调用方。服务调用方其实是通过动态代理、负载均衡、网络调用等机制去服务提供方的机器上去执行对应的方法。服务提供方将方法执行完成后,将执行结果再通过网络传输返回到服务提供方。 大致过程如下:

但是现在的服务都是集群部署,那么服务调用方怎么能够实时的知道服务提供方的集群中的变化,例如服务提供方的 IP 地址变了,或者是服务重启时怎么能够及时的切换流量呢?

这就需要{注册中心} 起作用了,我们可以把注册中心看作服务端,然后每个服务都看成客户端,每个客户端都需要将自己注册到注册中心,然后一个服务调用方要调用另一个服务时,需要从注册中心获取服务提供方的信息,主要是获取服务提供方的服务器 IP 地址列表和端口信息。

服务调用方获取到这些信息后缓存到自己本地,并且跟注册中心保持一个长连接当服务提供方有任何变化时,注册中心能够实时的通知给服务调用方,调用方能够及时更新自己本地缓存的信息(也可以采用定时轮询的方式)。

服务调用方获取到服务器 IP 地址信息后,根据自己的负载均衡策略选择一个 IP 地址然后发起网络调用的请求。

那么网络客户端是通过什么发起的网络调用呢?

可以自己使用 JDK 原生的 BIO 或者 NIO 来实现一套网络通信模块,但是这里我们建议直接使用强大的网络通信框架 Netty。它是基于 NIO 的网络通信框架,支持高并发,封装完善,而且性能好传输快。

Netty 不是我们本文的主要内容,这里就不展开说了。

客户端调用过程

因为我们知道数据在网络中传输的时候都是以二进制的形式的,所以在调用方将调用的参数进行传递的时候是需要进行序列化的。服务提供方在接收到参数时也是需要进行反序列化的。

网络协议

调用方既然需要序列化,服务提供方又要进行反序列化,这样双方就要确定好一个协议,调用方传输什么参数,服务提供方就按照这个协议去进行解析,而且在返回结果的时候也是按照这个协议进行结果解析。

那么这个协议应该是怎么样的结构,都是什么样子的呢? 因为这个协议可以自定义,我们为了方便就以 JSON 的形式给举个例子:

{"interfaces": "interface=com.jimoer.rpc.test.producer.TestService;method=printTest;parameter=com.jiomer.rpc.test.producer.TestArgs","requestId": "3","parameter": {"com.jiomer.rpc.test.producer.TestArgs": {"age": 20,"name": "Jimoer"}}
}

首先第一个参数interfaces是,我们要让服务提供方知道调用方要调用哪个接口,以及接口中的哪个方法,并且方法的参数是什么类型的。

第二个参数是当前一次请求的一个唯一标识,在多个线程同时请求一个方法时,用这个 id 来进行区分,以后无论是做链路追踪还是日志管理都可以以此 id 为依据。

第三个参数就是 实际的调用方法中的参数值。具体是什么类型的,每个属性值都是什么。

调用

下面也是举一个简单的例子来说明一下调用的过程。我们一部分采用代码的形式一部分采用文字的形式来将整个调用过程串起来。

// 定义请求的URL
String tcpURL = "tcp://testProducer/TestServiceImpl";
// 定义接口请求
TestService testService = ProxyFactory.create(TestService.class, tcpURL);
// 组装请求参数
TestArgs testArgs = new TestArgs(20,"Jimoer");
// 通过动态代理执行请求
String result = testService.printTest(testArgs);

通过查看上面的代码我们可以看到整个调用过程最核心的地方在 ProxyFactory.create() 方法里,这个方法里面主要的过程是,动态代理生成接口的实际代理对象,然后使用 Netty 的接口发起网络请求。

Proxy.newProxyInstance(getClass().getClassLoader(), interfaces.getClass().getInterfaces(), new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 第一步:获取调用服务的地址列表ListregistryInfos = interfacesMethodRegistryList.get(clazz);if (registryInfos == null) {throw new RuntimeException("无法找到服务提供者");}// 第二步: 通过自身的负载均衡策略选择一个地址RegistryInfo registryInfo = loadBalancer.choose(registryInfos);// 第三步:Netty的网络请求处理ChannelHandlerContext ctx = channels.get(registryInfo);// 第四步:根据接口类的全路径名和方法生成唯一标识String identify = InvokeUtils.buildInterfaceMethodIdentify(clazz, method);String requestId;// 第五步:通过加锁的方式保证生成的requestId的唯一性synchronized (ApplicationContext.this) {requestIdWorker.increment();requestId = String.valueOf(requestIdWorker.longValue());}// 第六步: 组织参数JSONObject jsonObject = new JSONObject();jsonObject.put("interfaces", identify);jsonObject.put("parameter", param);jsonObject.put("requestId", requestId);System.out.println("发送给服务端JSON为:" + jsonObject.toJSONString());// $$ 多条消息之间的分隔符String msg = jsonObject.toJSONString() + "$$";ByteBuf byteBuf = Unpooled.buffer(msg.getBytes().length);byteBuf.writeBytes(msg.getBytes());// 第七步:这里发起调用ctx.writeAndFlush(byteBuf);// 这里会将线程进行阻塞,知道服务提供方将请求处理好之后返回结果,再唤醒。waitForResult();return result;}});

执行过程大致分为这几步:

  1. 获取调用服务的地址列表。
  2. 通过自身的负载均衡策略选择一个地址。
  3. Netty 的网络请求处理(选择一个渠道 Channel)。
  4. 根据接口类的全路径名和方法生成唯一标识。
  5. 通过加锁的方式保证生成的 requestId 的唯一性。
  6. 组织请求参数。
  7. 发起调用。
  8. 线程阻塞,直到服务提供方返回结果。
  9. 填充返回结果,返回到调用方。
服务端处理过程

上面也说了,服务调用方发起网络请求后,会阻塞住,直到服务提供方返回数据,所以服务提供方处理完调用方法的逻辑后,还是要唤醒阻塞的调用线程的。

服务提供方在处理请求时也是先通过 Netty 获取到数据,然后再进行反序列化,然后再根据协议获取到需要调用的方法,然后通过反射去进行调用。

Netty 的返回入口在下面这部分逻辑里

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {try {String message = (String) msg;if (messageCallback != null) {// 将接收到的消息放到回调方法中messageCallback.onMessage(message);}} finally {ReferenceCountUtil.release(msg);}
}

Netty 的 client 接收到响应的消息后,先将结果返回到调用方,处理完成之后再去释放之前的阻塞调用线程。

client.setMessageCallback(message -> {// 这里收单服务端返回的消息,先压入队列RpcResponse response = JSONObject.parseObject(message, RpcResponse.class);System.out.println("收到一个响应:" + response);String interfaceMethodIdentify = response.getInterfaceMethodIdentify();String requestId = response.getRequestId();// 设定唯一标识String key = interfaceMethodIdentify + "#" + requestId;Invoker invoker = inProgressInvoker.remove(key);// 将结果设置到代理对象中invoker.setResult(response.getResult());// 加锁再释放之前的阻塞线程。synchronized (ApplicationContext.this) {ApplicationContext.this.notifyAll();}
});

setResult() 方法

@Override
public void setResult(String result) {synchronized (this) {this.result = JSONObject.parseObject(result, returnType);notifyAll();}
}

上面的步骤就是这样,按照之前请求的唯一标识放入到返回的信息中,然后将结果设置到代理对象中,再通过返回结果,然后唤醒之前的调用阻塞线程。

总结

其实整个 RPC 的请求过程就是如下(不含异步调用):

做一个总结,用大白话把一个 RPC 请求流程描述出来: 首先无论是调用方还是服务提供方都要注册到注册中心;

  1. 服务调用方把请求参数对象序列化成二进制数据,通过动态代理生成代理对象,通过代理对象,使用 Netty 选择一个从注册中心拉取到的服务提供方的地址,然后发起网络请求。
  2. 服务提供方从 TCP 通道中接收到二进制数据,根据定义的 RPC 网络协议,从二进制数据中反序列化后,分割出接口地址和参数对象,再通过反射找到接口执行调用。
  3. 然后服务提供方再把调用执行结果序列化后,回传到 TCP 通道中。
  4. 服务调用方获取到应答二进制数据后,再反序列化成结果对象。

这样就完成了一次 RPC 网络调用,其实后面框架扩展后,还要考虑限流、熔断、服务降级、序列化多样性扩展,服务监控、链路追踪等等功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1536977.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

数据库基础(MySQL)

目录 1. 概述 2. MySQL安装准备 3. SQL概述 3.1 什么是SQL 3.2 SQL通用语法 3.3 SQL分类 4. DDL 4.1 操作数据库 4.1.1 C(Create)创建 4.1.2 R(Retrieve)查询 4.1.3 U(Update):修改 4.1.4 D(Delete):删除 4.1.5 使用数据库 4.2 操作表 4.2.1 C(Create):创建 4.2…

苹果已确定9月iPhone 16系列发布会,然而这些产品不会亮相

近日,科技巨头苹果公司正式宣布将于9月举办一年一度的iPhone 16系列发布会。 这场备受瞩目的活动吸引了全球众多科技爱好者的目光。 然而,在发布会前夕,有消息传出,部分备受期待的产品将不会亮相。 究竟哪些产品会惊艳登场&…

QT + WebAssembly + Vue环境搭建

Qt6.7.2安装工具 emsdk安装 git clone https://github.com/emscripten-core/emsdk.git cd emsdk emsdk install 3.1.50 emsdk activate 3.1.50 Qt Creator配置emsdk 效果 参考 GitHub - BrockReece/vue-wasm: Vue web assembly loader Emscripten cmake多版本编译-CSDN博客 …

我的标志:奇特的头像

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>与妖为邻</title><style>figure.log…

【PHP代码审计】 PHP环境搭建

&#x1f31d;博客主页&#xff1a;泥菩萨 &#x1f496;专栏&#xff1a;Linux探索之旅 | 网络安全的神秘世界 | 专接本 | 每天学会一个渗透测试工具 安装phpstudy 泥菩萨-CSDN博客 安装vscode 直接去官网下载安装包&#xff0c;然后双击安装即可。官网地址&#xff1a;htt…

re题(27)BUUFCTF-[MRCTF2020]Transform

BUUCTF在线评测 (buuoj.cn) 先到ida&#xff0c;先看一下字符串 找到主函数 int __cdecl main(int argc, const char **argv, const char **envp) {char Str[104]; // [rsp20h] [rbp-70h] BYREFint j; // [rsp88h] [rbp-8h]int i; // [rsp8Ch] [rbp-4h]sub_402230(argc, arg…

Virtuoso配置文件(virtuoso.ini)详解

目录 前言1. Virtuoso ini 文件的作用2. 如何使用 virtuoso.ini3. Virtuoso ini 文件结构详解3.1 数据库相关配置3.2 服务器参数配置3.3 网络设置3.4 内存优化3.5 复制与高可用性配置3.6 SPARQL 查询设置 4. virtuoso.ini文件示例5. 结语 前言 Virtuoso 是一款强大的通用数据集…

深度学习--------------序列模型

目录 序列数据统计工具&#xff08;方案一&#xff09;马尔可夫假设&#xff08;方案二&#xff09;潜变量模型总结 序列模型基于马尔可夫假设方式该部分总代码 单步预测多步预测k步预测该部分总代码 序列数据 实际中数据是有时序结构的。 统计工具 在时间t观察带 x t x_t xt…

git push : RPC failed; HTTP 400 curl 22 The requested URL returned error: 400

git push 出现RPC failed; HTTP 400 curl 22 The requested URL returned error: 400 问题 git push Enumerating objects: 11, done. Counting objects: 100% (11/11), done. Delta compression using up to 8 threads Compressing objects: 100% (10/10), done. error: RPC …

通用接口开放平台设计与实现——(31)API服务线程安全问题确认与修复

背景 在本系列的前面一篇博客评论中&#xff0c;有小伙伴指出&#xff0c;API服务存在线程安全问题&#xff1a; https://blog.csdn.net/seawaving/article/details/122905199#comments_34477405 今天来确认下&#xff0c;线程是否安全&#xff1f;如不安全&#xff0c;如何…

【AIGC】CFG:基于扩散模型分类器差异引导

摘要 分类器指导是最近引入的一种方法&#xff0c;在训练后在条件扩散模型中权衡模式覆盖率和样本保真度&#xff0c;在精神上与其他类型的生成模型中的低温采样或截断相同。分类器引导将扩散模型的分数估计与图像分类器的梯度相结合&#xff0c;因此需要训练与扩散模型分离的…

C#语言依然是主流的编程语言之一,不容置疑

C#语言是由微软在2000年发布的现代面向对象编程语言。尽管在编程语言市场中的占有率相对较低&#xff0c;但C#依然保持了强大的存在感&#xff0c;并未像一些其他语言那样逐渐被淘汰。C#语言不仅有其存在的独特理由&#xff0c;而且拥有许多令人无法忽视的优势。以下从多个方面…

积分电路和滤波电路的主要区别点和应用场合

文章目录 前言一、滤波电路的分类二、有源滤波器和无源滤波器的优缺点和实用范围三、积分电路3.1 无源积分电路3.2 RC充放电的电路响应3.2.1 RC电路的零状态响应3.2.2 RC电路的零输入响应3.2.3 RC电路的全响应3.2.4 选取合适的时间常数四 、无源RC低通滤波器4.3.1 截止频率推导…

kubernetes技术详解,带你深入了解k8s

目录 一、Kubernetes简介 1.1 容器编排应用 1.2 Kubernetes简介 1.3 k8s的设计架构 1.3.1 k8s各个组件的用途 1.3.2 k8s各组件之间的调用关系 1.3.3 k8s的常用名词概念 1.3.4 k8s的分层结构 二、k8s集群环境搭建 2.1 k8s中容器的管理方式 2.2 k8s环境部署 2.2.1 禁用…

VuePress搭建文档网站/个人博客(详细配置)主题配置-导航栏配置

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

针对国内AIGC市场,国内目前出台了那些法律法规?

针对国内AIGC市场&#xff0c;特别是AI生成与合成内容方面&#xff0c;中国已经出台了一系列法律法规来规范其发展和应用。 图片源自“央视新闻” 以下是一些主要的法律法规&#xff1a; 一、国家层面的法律法规 《中华人民共和国网络安全法》 施行时间&#xff1a;2017年6月…

进程监控与管理详解

一、进程的定义: 进程process是正在运行的程序,包括: 分配的内存地址空间 安全属性、包括所有权和特权 一个或多个线程 进程状态 进程的环境包括: 本地和全局变量 当前调度上下文…

java多线程编程示例

程序功能 程序展示了 Java 中如何使用多线程来并行执行任务。具体功能如下&#xff1a; 程序创建了三个线程&#xff0c;每个线程执行相同的任务类 Task。 每个线程在运行时输出自身名称&#xff0c;并模拟执行五次任务&#xff0c;每次任务间隔 1 秒。 主线程在启动这三个线程…

专业学习|系统动力学概观(方法特色、构成要素、建模步骤)

一、系统动力学概览 &#xff08;一&#xff09;系统动力学介绍 系统动力学是一门综合交叉学科&#xff0c;其基础理论涵盖了控制论、信息论和决策论等多个领域。它利用计算机仿真技术对复杂系统进行定量研究&#xff0c;尤其是那些具有非线性、高阶次和多重反馈特征的系统。这…

【可视化大屏系列】数据列表自动滚动效果

要实现列表的自动滚动效果&#xff0c;这里提供两种解决方案&#xff1a; 1.vue插件 官方文档&#xff1a;链接: vue-seamless-scroll &#xff08;1&#xff09;安装依赖 npm install vue-seamless-scroll --save&#xff08;2&#xff09;全局注册&#xff08;main.js中&a…