01丨核心原理:能否画张图解释下 RPC 的通信流程?

简介: RPC(远程过程调用)是一种实现跨服务透明调用的技术,屏蔽网络通信细节,让开发者像调用本地方法一样调用远程服务。它通过序列化、协议解析和动态代理等机制完成远程调用,是微服务架构的“经络”,广泛应用于分布式系统中,提升开发效率与系统解耦能力。

只要你做过几年开发,那我相信 RPC 这个词你肯定是不陌生了。我还特意查了下 RPC 的百度指数,发现这些年 RPC 的搜索趋势都是稳步上升的,这也侧面说明了这项技术正在逐步渗透到我们的日常开发中。作为第一讲,我想只围绕 RPC 这个词,和你聊聊它的定义,它要解决的问题,以及工作原理。
在前些年,我面试工程师的时候,最喜欢问候选人一个问题,「你能否给我解释下 RPC 的通信流程」。这问题其实并不难,不过因为很多工程师平时都在用各种框架,他们可能并未停下来思考过框架的原理,所以,问完这问题,有的人就犹豫了,吱唔了半天也没说出所以然来。
紧接着,我会引导他说,「你想想,如果没有 RPC 框架,那你要怎么调用另外一台服务器上的接口呢」。你看,这问题可深可浅,也特别考验候选人的基本功。如果你是候选人,你会怎么回答呢?今天我就来试着回答你这个问题。
什么是 RPC?
我知道你肯定不喜欢听概念,我也是这样,看书的时候一看到概念就直接略过。不过,到后来,我才发现,「定义」是一件多么伟大的事情。当我们能够用一句话把一个东西给定义出来的时候,侧面也说明你已经彻底理解这事了,不仅知道它要解决什么问题,还要知道它的边界。所以,你可以先停下来想想,什么是 RPC。
RPC 的全称是 Remote Procedure Call,即远程过程调用。简单解读字面上的意思,远程肯定是指要跨机器而非本机,所以需要用到网络编程才能实现,但是不是只要通过网络通信访问到另一台机器的应用程序,就可以称之为 RPC 调用了?显然并不够。
我理解的 RPC 是帮助我们屏蔽网络编程细节,实现调用远程方法就跟调用本地(同一个项目中的方法)一样的体验,我们不需要因为这个方法是远程调用就需要编写很多与业务无关的代码。
这就好比建在小河上的桥一样连接着河的两岸,如果没有小桥,我们需要通过划船、绕道等其他方式才能到达对面,但是有了小桥之后,我们就能像在路面上一样行走到达对面,并且跟在路面上行走的体验没有区别。所以 我认为,RPC 的作用就是体现在这样两个方面:
● 屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法;
● 隐藏底层网络通信的复杂性,让我们更专注于业务逻辑。
RPC 通信流程
理解了什么是 RPC,接下来我们讲下 RPC 框架的通信流程,方便我们进一步理解 RPC。
如前面所讲,RPC 能帮助我们的应用透明地完成远程调用,发起调用请求的那一方叫做调用方,被调用的一方叫做服务提供方。为了实现这个的目标,我们就需要在 RPC 框架里面对整个通信细节进行封装,那一个完整的 RPC 会涉及到哪些步骤呢?
我们已经知道 RPC 是一个远程调用,那肯定就需要通过网络来传输数据,并且 RPC 常用于业务系统之间的数据交互,需要保证其可靠性,所以 RPC 一般默认采用 TCP 来传输。我们常用的 HTTP 协议也是建立在 TCP 之上的。
网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。对象是肯定没法直接在网络中传输的,需要提前把它转成可传输的二进制,并且要求转换算法是可逆的,这个过程我们一般叫做 序列化。
调用方持续地把请求参数序列化成二进制后,经过 TCP 传输给了服务提供方。服务提供方从 TCP 通道里面收到二进制数据,那如何知道一个请求的数据到哪里结束,是一个什么类型的请求呢?
在这里我们可以想想高速公路,它上面有很多出口,为了让司机清楚地知道从哪里出去,管理部门会在路上建立很多指示牌,并在指示牌上标明下一个出口是哪里、还有多远。那回到数据包识别这个场景,我们是不是也可以建立一些「指示牌」,并在上面标明数据包的类型和长度,这样就可以正确的解析数据了。确实可以,并且我们把数据格式的约定内容叫做 协议。大多数的协议会分成两部分,分别是数据头和消息体。数据头一般用于身份识别,包括协议标识、数据大小、请求类型、序列化类型等信息;消息体主要是请求的业务参数信息和扩展属性等。
根据协议格式,服务提供方就可以正确地从二进制数据中分割出不同的请求来,同时根据请求类型和序列化类型,把二进制的消息体逆向还原成请求对象。这个过程叫作 反序列化。
服务提供方再根据反序列化出来的请求对象找到对应的实现类,完成真正的方法调用,然后把执行结果序列化后,回写到对应的 TCP 通道里面。调用方获取到应答的数据包后,再反序列化成应答对象,这样调用方就完成了一次 RPC 调用。
那上述几个流程就组成了一个完整的 RPC 吗?
在我看来,还缺点东西。因为对于研发人员来说,这样做要掌握太多的 RPC 底层细节,需要手动写代码去构造请求、调用序列化,并进行网络调用,整个 API 非常不友好。
那我们有什么办法来简化 API,屏蔽掉 RPC 细节,让使用方只需要关注业务接口,像调用本地一样来调用远程呢?
如果你了解 Spring,一定对其 AOP 技术很佩服,其核心是采用动态代理的技术,通过字节码增强对方法进行拦截增强,以便于增加需要的额外处理逻辑。其实这个技术也可以应用到 RPC 场景来解决我们刚才面临的问题。
由服务提供者给出业务接口声明,在调用方的程序里面,RPC 框架根据调用的服务接口提前生成动态代理实现类,并通过依赖注入等技术注入到声明了该接口的相关业务逻辑里面。该代理实现类会拦截所有的方法调用,在提供的方法处理逻辑里面完成一整套的远程调用,并把远程调用结果返回给调用方,这样调用方在调用远程方法的时候就获得了像调用本地接口一样的体验。
到这里,一个简单版本的 RPC 框架就实现了。我把整个流程都画出来了,供你参考:

RPC 在架构中的位置
围绕 RPC 我们讲了这么多,那 RPC 在架构中究竟处于什么位置呢?
如刚才所讲,RPC 是解决应用间通信的一种方式,而无论是在一个大型的分布式应用系统还是中小型系统中,应用架构最终都会从「单体」演进成「微服务化」,整个应用系统会被拆分为多个不同功能的应用,并将它们部署在不同的服务器中,而应用之间会通过 RPC 进行通信,可以说 RPC 对应的是整个分布式应用系统,就像是「经络」一样的存在。
那么如果没有 RPC,我们现实中的开发过程是怎样的一个体验呢?
所有的功能代码都会被我们堆砌在一个大项目中,开发过程中你可能要改一行代码,但改完后编译会花掉你 2 分钟,编译完想运行起来验证下结果可能要 5 分钟,是不是很酸爽?更难受的是在人数比较多的团队里面,多人协同开发的时候,如果团队其他人把接口定义改了,你连编译通过的机会都没有,系统直接报错,从而导致整个团队的开发效率都会非常低下。而且当我们准备要上线发版本的时候,QA 也很难评估这次的测试范围,为了保险起见我们只能把所有的功能进行回归测试,这样会导致我们上线新功能的整体周期都特别长。
无论你是研发还是架构师,我相信这种系统架构我们肯定都不能接受,那怎么才能解决这个问题呢?
我们首先都会想到可以采用「分而治之」的思想来进行拆分,但是拆分完的系统怎么保持跟未拆分前的调用方式一样呢?我们总不能因为架构升级,就把所有的代码都推倒重写一遍吧。
RPC 框架能够帮助我们解决系统拆分后的通信问题,并且能让我们像调用本地一样去调用远程方法。利用 RPC 我们不仅可以很方便地将应用架构从「单体」演进成「微服务化」,而且还能解决实际开发过程中的效率低下、系统耦合等问题,这样可以使得我们的系统架构整体清晰、健壮,应用可运维度增强。
当然 RPC 不仅可以用来解决通信问题,它还被用在了很多其他场景,比如:发 MQ、分布式缓存、数据库等。下图是我之前开发的一个应用架构图:

在这个应用中,我使用了 MQ 来处理异步流程、Redis 缓存热点数据、MySQL 持久化数据,还有就是在系统中调用另外一个业务系统的接口,对我的应用来说这些都是属于 RPC 调用,而 MQ、MySQL 持久化的数据也会存在于一个分布式文件系统中,他们之间的调用也是需要用 RPC 来完成数据交互的。
由此可见,RPC 确实是我们日常开发中经常接触的东西,只是被包装成了各种框架,导致我们很少意识到这就是 RPC,让 RPC 变成了我们最「熟悉的陌生人」。现在,回过头想想,我说 RPC 是整个应用系统的经络,这不为过吧?我们真的很有必要学好 RPC,不仅因为 RPC 是构建复杂系统的基石,还是提升自身认知的利器。
总结
本讲我主要讲了下 RPC 的原理,RPC 就是提供一种透明调用机制,让使用者不必显式地区分本地调用和远程调用。RPC 虽然可以帮助开发者屏蔽远程调用跟本地调用的区别,但毕竟涉及到远程网络通信,所以这里还是有很多使用上的区别,比如:
● 调用过程中超时了怎么处理业务?
● 什么场景下最适合使用 RPC?
● 什么时候才需要考虑开启压缩?
无论你是一个初级开发者还是高级开发者,RPC 都应该是你日常开发过程中绕不开的一个话题,所以作为软件开发者的我们,真的很有必要详细地了解 RPC 实现细节。只有这样,才能帮助我们更好地在日常工作中使用 RPC。
课后思考

  1. 你应用中有哪些地方用到了 RPC?
  2. 你认为,RPC 使用过程中需要注意哪些问题?

如果说按照这么宽泛的理解的话,我认为:
你应用中有哪些地方用到了 RPC?:只要是跨网络通信的都算
你认为,RPC 使用过程中需要注意哪些问题?:
● 调用超时,太长:如果出了问题,大量的链接被挂起,资源严重浪费,还有可能导致系统崩溃。太短:可能会导致实际业务没有处理完成。
● 重试机制:这个笔者一直都不是很明白,真实环境中,重试的话,大部分情况都是对方服务器某一个时间点出现问题,即使重试也得不到有效的响应,重试多久呢?

相关文章
|
1天前
|
存储 自然语言处理 分布式计算
08 | 索引构建:搜索引擎如何为万亿级别网站生成索引?
针对超大规模数据,可通过分治与多路归并生成内存外倒排索引。先将文档分批在内存建索引,再写入有序临时文件,最后归并为全局索引。检索时结合内存哈希、B+树及分层加载技术,提升效率。
|
1天前
|
存储 算法 Java
03 | 哈希检索:如何根据用户 ID 快速查询用户信息?
本文介绍了哈希表的原理与实现。通过哈希函数将键转换为数组下标,利用数组随机访问特性实现O(1)级查询。针对哈希冲突,讲解了开放寻址法(线性探查、二次探查、双散列)和链表法两种解决方案,并分析其优劣。最后指出哈希表需足够空间以保持低装载因子,且不支持有序操作,适合精确查找但不适合范围查询。
|
1天前
|
存储 缓存 算法
学习数据结构和算法的框架思维
本文系统梳理数据结构与算法核心思想:所有数据结构本质为数组或链表的变形,基本操作均为遍历与访问;算法本质是穷举,关键在于“无遗漏”和“无冗余”。掌握框架思维,方能以不变应万变,高效刷题。
学习数据结构和算法的框架思维
|
1天前
|
存储 负载均衡 搜索推荐
大规模检索系统
本讲介绍大规模检索系统如何通过分布式技术加速检索。通过索引拆分,将倒排索引分散到多台服务器内存中,减少单机数据规模和磁盘访问,从而提升单次查询效率。结合分发服务器与负载均衡,实现高吞吐、低延迟的分布式检索架构。
|
1天前
|
SQL 人工智能 API
Apache Flink 2.2.0: 推动实时数据与人工智能融合,赋能AI时代的流处理
Apache Flink 2.2.0 发布!新增 ML_PREDICT 与 VECTOR_SEARCH 实时 AI 函数,增强物化表、Delta Join 及连接器能力,优化批处理与 PyFlink。73 位贡献者共建,9 大 FLIP,220+ 修复改进,助力智能低延迟数据管道。
Apache Flink 2.2.0: 推动实时数据与人工智能融合,赋能AI时代的流处理
|
1天前
|
存储 关系型数据库 调度
微服务原理篇(XXLJOB-幂等-MySQL)
本课程涵盖XXL-JOB任务调度、幂等性解决方案及MySQL数据库核心知识。学习内容包括:掌握XXL-JOB的分布式调度优势与搭建使用,理解并实现幂等性以避免重复操作;深入MySQL存储引擎差异、索引机制(如B+树)、回表与覆盖索引原理,并熟悉SQL调优与分库分表策略,提升系统性能与数据一致性保障能力。
 微服务原理篇(XXLJOB-幂等-MySQL)
|
1天前
|
Java Nacos Maven
Eureka服务注册与发现
本章介绍Eureka服务注册与发现功能,搭建eureka-server并实现user-service、order-service的注册与多实例部署,掌握服务动态发现机制,为后续Nacos替换奠定基础。
10 0
|
1天前
|
存储 机器学习/深度学习 编解码
预训练技巧
预训练是大模型的核心基础,涵盖混合精度、分布式训练、ZeRO优化、FlashAttention等关键技术,通过高效计算与显存优化,实现大规模模型的快速稳定训练。
|
1天前
|
搜索推荐 算法 Java
2、排序
排序算法分为比较类和非比较类。比较类包括快排、归并、堆排(平均时间O(n log n))和插入排序(O(n²)),适用于不同数据规模与有序度;非比较类如计数、桶、基数排序,可达到O(n),依赖数据特征。实际应用中常结合多种算法优化性能。
|
1天前
|
机器学习/深度学习 搜索推荐 算法
19 | 广告系统:广告引擎如何做到在 0.1s 内返回广告信息?
广告系统是互联网核心营收支柱,支撑Google、Facebook等巨头超80%收入。其背后依赖高性能广告引擎,实现高并发、低延迟的“千人千面”精准投放。本文深入解析广告引擎架构,涵盖标签检索、向量匹配、打分排序与索引优化四大关键技术,揭示如何在0.1秒内完成从请求到广告返回的全过程,打造高效智能的广告生态体系。(238字)