理解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。

 

六、思考

1、你应用中有哪些地方用到了 RPC?

2、你认为,RPC 使用过程中需要注意哪些问题?

 
部分同学的回答:

1、你应用中有哪些地方用到了 RPC?
在公司内部不同服务之间的调用都是走的RPC
2、你认为,RPC 使用过程中需要注意哪些问题?
1)下游服务的服务能力,避免因为你的调用把别人给调挂了,要事前协商好qps等,做好限流
2)调用服务异常时,要考虑降级、重试等措施
3)核心的服务不能强依赖非核心的服务,避免核心服务因为非核心服务异常而不可用
1. 你应用中有哪些地方用到了 RPC?
我们的应用是微服务架构的,RPC就是连接这些微服务之间的纽带。
2. 你认为,RPC 使用过程中需要注意哪些问题?
因为RPC也是网络调用,性能方面肯定不如本地调用,所有RPC的API设计要仔细考虑,比如一次性能完成的调用就不要走多次调用。另外我认为最重要的是要有监控系统能监控所有的调用链,方便问题排查和性能调优。
调用过程中超时了怎么处理业务?
重试机制,降级处理。
什么场景下最适合使用 RPC?
网络安全稳定的环境。
什么时候才需要考虑开启压缩?
压缩后,数据量有明显的降低,压缩会使用CPU等资源,还是要看性价比。
1 公司项目不同服务之间的内部调用
2 数据传输量大小/超时处理/异常处理
1.内部服务见调用,访问缓存,MQ,数据库等都是通过RPC调用的;
2. 对于RPC调用要注意设置服务端超时时间和客户端超时时间,重试策略,快速失败,限流,降级,线程池等,对于多中心和机房的还要考虑具体的理由策略。