2021年第一篇文章,献给向上的你~
01
什么是RPC?
RPC,全称 Remote Procedure Call ,翻译为中文即为“远程过程调用”。它比较完整的定义如下所示:
RPC 是一种语言级别的通讯协议,它允许运行于一台计算机上的程序以某种管道作为通讯媒介(即某种传输协议的网络),去调用另外一个地址空间(通常为网络上的另外一台计算机)的子程序。
02
为什么需要RPC?
为什么需要 RPC?
一方面,由于分布式架构的发展和微服务化的深入,原本部署在单台机器上的一个大型单体服务,被拆分成若干个微服务,微服务之间通过互相调用来实现既定的功能。此时,就需要解决跨进程通信的问题,而且,值得一提的是,这种跨进程的通信,大多数情况下是跨机器的。
另一方面,即便是在原有的单体架构下,部署于不同机器上的服务之间相互调用也是难以避免的。
因此,需要有这样一种方案能够实现如下功能:一台机器上的程序,能够调用另外一台机器上的程序。这种方案就是 RPC。
03
如何实现RPC?
如何实现 RPC?听起来一头雾水。其实,我们真正需要考虑的是,如何使一台机器上的程序,能够调用另外一台机器上的程序。
不同于本地方法调用,远程过程调用需要解决如下三个基本问题:
- 如何确定要调用的方法?
- 如何传输必要的数据(包括方法参数和返回值等)?
- 如何进行网络传输?
只要解决了上述三个问题,远程过程调用在逻辑上,就近似于本地方法调用了。而本地方法调用,是我们最常见也是最拿手的,问题也就得到了相应的解决。
接下来,我们一一来看:
- 如何确定要调用的方法?
对于这一问题,最粗糙的想法便是维护一份方法列表,其中每个方法都有一个唯一标识。这样就能保证客户端在进行远程方法调用的时候,能够命中正确的方法。然而,在实际情况下,还要考虑,客户端和服务器可能是由不同的语言实现的,而不同语言的方法签名是有一定出入的。这时候,一个比较自然的想法是找一个中介,这里的中介就是接口描述语言 (Interface Description Language,简称 IDL)。借助 IDL,我们可以很好地屏蔽不同语言之间的差异,从而解决这个问题。 - 如何传输方法参数和返回值等必要的数据?
对于这一问题,出于对传输性能以及客户端与服务端的兼容性等考虑,需要对传输的数据进行编码和解码工作,这也就是我们常说的序列化和反序列化。数据发送端将待传输数据序列化成字节流,通过网络进行传输至数据接收端,数据接收端将接收到的数据进行反序列化,得到自己能够读取和处理的格式。这同样很好地屏蔽了不同语言之间的差异,从而解决这个问题。 - 如何进行网络传输?
对于这一问题,其实只需要选择一个适合的网络协议就够了,对于应用层的协议,有我们非常熟悉的 HTTP,但对于目前应用最广的 HTTP1.1 版本,其因为性能太差而被普遍诟病,因此大多数 RPC 框架,比如 Facebook 开源的 Thrift,都选择了直接使用传输层协议 TCP (当然,单从原理上来说,UDP也是可以的),而像谷歌开源的 gRPC,则选择了 HTTP2。
到这里,我们已经解决了 RPC 的三个基本问题。根据上述三点,我们可以搭建出一个基本的 RPC 框架。但在实际生产中,我们对于 RPC 框架的优劣判断有更加严苛的标准,多见于考察某个 RPC 框架在性能、易用性以及通用性等方面的表现,从而判定其是否合适。
为了更加直观地理解以上内容,我们用一张图来总结一下,一个 RPC 框架是如何解决上述三点问题的。
其中比较容易理解的是,Client 是服务调用方,Service 是服务提供方。二者实现了具体的业务逻辑。而 Client Stub 和 Service Stub 则负责解决上述三个问题。
我们可以通过下面六个步骤大致模拟一次RPC请求和返回的过程:
- Client 发起请求
- Client Stub 会获取 Service 地址及方法信息,并将请求参数进行序列化,然后通过网络进行传输
- Service Stub 接收到请求字节流之后,进行反序列化,并调用本地 Service 的对应方法
- Service 返回结果
- Service Stub 将结果序列化,然后通过网络返回
- Client Stub 接收到返回字节流之后,进行反序列化,并将返回值传递给 Client
至此,关于 RPC 的相关知识就总结完毕了,在实际使用某个 RPC 框架的时候,结合上述总结,相信会有更加清晰的认识。