什么是RPC?
RPC(Remote Procedure Call)即远程过程调用,不同于本地调用,RPC是指调用远端机器的函数或方法,且不需要关心底层的调用细节,如网络协议和传输协议等,对于调用者来说,和调用本地方法没有什么区别。RPC调用的是一个远端对象,调用者和被调用者是处于不同的地址空间,要想完成调用,必须实现以下几个能力:
服务发现
由于调用的是远端对象,因此需要可以定位到调用的服务器地址以及调用的具体方法的进程,一般可以通过注册中心或者哈希查找来实现,将被调的接口注册到某个地方,比如使用CallerId和函数名作为映射,调用者通过CallerId进行调用,服务端收到调用请求后,通过CallerId查找到对应函数,再执行后续处理流程。
序列化和反序列化
调用者和被调用者处于不同的机器上,因此函数参数及其他一些Context信息需要经过网络传输才能到达服务端,网络传输的是二进制数据,因此RPC需要支持将函数参数(如基本类型int、long、map以及自定义类型等)可以序列化为网络可以传输的二进制数据,同时,也需要支持将二进制数据反序列化为程序可以处理的数据类型,是序列化的逆过程。
网络传输
网络传输协议和RPC本身没有关系,一般使用TCP进行网络传输,通过HTTP或者UDP也是可以完成RPC所需要的功能的。
如上,如果某个服务框架实现了上述这些功能,就是一个RPC框架,通过此框架便可以像调用本地方法一样来调用远端方法。
为什么使用RPC?
如果是一个功能比较简单的应用,可以将所有功能实现在一个服务中或者实现在多个服务中,但是部署在一个机器上,如此,接口间调用要么在同一进程内或者是本地进程间通信。随着业务的发展,服务功能持续迭代,单体应用出现性能瓶颈,因此需要考虑对服务进行拆分,根据业务功能划分为不同的模块,不同的模块部署到不同集群上,模块间进行通信完成功能。如将服务的管理域和执行域进行拆分,不仅是服务架构更合理清晰,也方便根据不同的模块划分不同的资源,保证服务的整体性能。
一个服务拆分为不同的模块,或者单体应用拆分为多个微服务时,此时便需要RPC出场了,不同模块及不同服务间都需要RPC才能完成通信。可以说RPC是分布式系统架构或者微服务架构必不可少的实现手段。
RPC原理
理清了RPC的基本概念之后,简单阐述下RPC框架的实现原理。首先看一张RPC调用的全貌图:
当客户端发起一次远程调用时:
- client以本地方式方式调用远程方法;
- client stub收到调用后负责将方法以及参数等组装成网络可以传输的消息体;
- client stub找到服务端地址,通过系统调用,将数据传输服务端;
- server stub收到消息后进行反序列化;
- server stub通过得到的参数调用本地方法;
- 执行结果返回给server stub;
- server stub将执行结果进行序列化;
- server stub进行系统调用将数据发送给客户端;
- client stub收到消息并进行解码;
- cleint stub将结果返回给调用者。
RPC框架所要实现的便是将序列化、反序列化以及网络传输等流程进行封装,这些流程对于调用者来说是透明的,调用者并不需要了解这些细节以及调用流程。
常用的RPC框架有哪些?
Thrift
Thrift是一个轻量级、跨语言的RPC框架,主要用于各个服务之间的RPC通信,最初由Facebook于2007年开发,2008年进入Apache开源项目。它通过自身的IDL中间语言,并借助代码生成引擎生成各种主流语言的RPC服务端和客户端模板代码。Thrift支持多种不同的编程语言,包括C++、Java、Python、PHP、Ruby、Erlag、Haskell、C#、Cocoa、JavaScript、Nodejs、Smalltalk、OCaml和Golang等。
gRPC
gRPC是一个高性能、通用的开源RPC框架,其由Google2015年主要面向移动应用开发并基于HTTP/2标准协议而设计,基于ProtoBuf序列化协议开发,且支持众多语言开发。gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。客户端充分利用高级流和链式功能,从而有助于节省带宽、降低TCP连接次数、节省CPU使用、电池寿命。
支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言
Dubbo
Dubbo是一个分布式服务框架,以及SOA治理方案。其功能主要包括:高性能NIO通讯及多协议集成,服务动态寻址与路由,软负载均衡与容错,依赖分析与降级等。 Dubbo是阿里巴巴内部的SOA服务化治理方案的核心框架,Dubbo自2011年开源后,已被许多非阿里系公司使用,目前Dubbo已经交给Apache基金会维护。