1.定义:java
RPC 的全称是 Remote Procedure Call 是一种进程间通讯方式。它容许程序调用另外一个地址空间(一般是共享网络的另外一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员不管是调用本地的仍是远程的,本质上编写的调用代码基本相同。程序员
2.功能目标:web
RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者没必要显式的区分本地调用和远程调用。json
3.结构:网络
RPC 的程序包括 5 个部分:User、User-stub、RPCRuntime、 Server-stub、Server框架
RPC 服务方经过 RpcServer 去导出(export)远程接口方法,而客户方经过 RpcClient 去引入(import)远程接口方法。客户方像调用本地方法同样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理RpcProxy 。代理封装调用信息并将调用转交给RpcInvoker 去实际执行。在客户端的RpcInvoker 经过链接器RpcConnector 去维持与服务端的通道RpcChannel,并使用RpcProtocol 执行协议编码(encode)并将编码后的请求消息经过通道发送给服务方。RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,一样使用RpcProtocol 执行协议解码(decode)。解码后的调用信息传递给RpcProcessor 去控制处理调用过程,最后再委托调用给RpcInvoker 去实际执行并返回调用结果。异步
4.调用分类:分布式
(1)同步调用 : 客户方等待调用执行完成并返回结果。 函数
(2)异步调用 : 客户方调用后不用等待执行结果返回,但依然能够经过回调通知等方式获取返回结果。 若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。性能
异步和同步的区分在因而否等待服务端执行完成并返回结果。
5.组件分类:
(1) RpcServer 负责导出(export)远程接口
(2) RpcClient 负责导入(import)远程接口的代理实现
(3)RpcProxy 远程接口的代理实现
(4) RpcInvoker 客户方实现:负责编码调用信息和发送调用请求到服务方并等待调用结果返回 服务方实现:负责调用服务端接口的具体实现并返回调用结果
(5) RpcProtocol 负责协议编/解码
(6) RpcConnector 负责维持客户方和服务方的链接通道和发送数据到服务方
(7)RpcAcceptor 负责接收客户方请求并返回请求结果
(8)RpcProcessor 负责在服务方控制调用过程,包括管理调用线程池、超时时间等
(9)RpcChannel 数据传输通道
6.导出远程接口 :
导出远程接口的意思是指只有导出的接口能够供远程调用,而未导出的接口则不能。
7.导入远程接口与客户端代理 :
导入相对于导出远程接口,客户端代码为了可以发起调用必需要得到远程接口的方法或过程定义。目前,大部分跨语言平台 RPC 框架采用根据 IDL 定义经过 code generator 去生成 stub 代码,这种方式下实际导入的过程就是经过代码生成器在编译期完成的。我所使用过的一些跨语言平台 RPC 框架如 CORBAR、WebService、ICE、Thrift 均是此类方式。 代码生成的方式对跨语言平台 RPC 框架而言是必然的选择,而对于同一语言平台的 RPC 则能够经过共享接口定义来实现。
8.协议编解码 :
客户端代理在发起调用前须要对调用信息进行编码,这就要考虑须要编码些什么信息并以什么格式传输到服务端才能让服务端完成调用。出于效率考虑,编码的信息越少越好(传输数据少),编码的规则越简单越好(执行效率高)。须要的编码信息: 调用编码 -- 接口方法(接口名、方法名)方法参数(参数类型、参数值)调用属性(调用属性信息,例如调用附件隐式参数、调用超时时间等 -- 返回编码 -- 返回结果(接口方法中定义的返回值)返回码(异常返回码)返回异常信息(调用异常信息)除了以上这些必须的调用信息,咱们可能还须要一些元信息以方便程序编解码以及将来可能的扩展。这样咱们的编码消息里面就分红了两部分,一部分是元信息、另外一部分是调用的必要信息。若是设计一种 RPC 协议消息的话,元信息咱们把它放在协议消息头中,而必要信息放在协议消息体中。下面给出一种概念上的 RPC 协议消息设计格式:
-- 消息头 -- magic: 协议魔数,为解码设计 ; header size: 协议头长度,为扩展设计 ;version : 协议版本,为兼容设计;st : 消息体序列化类型 ;hb : 心跳消息标记,为长链接传输层心跳设计 ; ow : 单向消息标记; rp : 响应消息标记,不置位默认是请求消息 ;status code: 响应消息状态码; reserved : 为字节对齐保留;message id : 消息 id;body size : 消息体长度 ;
-- 消息体 -- 采用序列化编码,常见有如下格式 xml: 如 webservie soap;json: 如 JSON-RPC ;binary: 如 thrift; hession; kryo 等。格式肯定后编解码就简单了,因为头长度必定因此咱们比较关心的就是消息体的序列化方式。序列化咱们关心三个方面:(1)序列化和反序列化的效率,越快越好。(2)序列化后的字节长度,越小越好。(3) 序列化和反序列化的兼容性,接口参数对象若增长了字段,是否兼容。
9. 传输服务:
协议编码以后,天然就是须要将编码后的 RPC 请求消息传输到服务方,服务方执行后返回结果消息或确认消息给客户方。RPC 的应用场景实质是一种可靠的请求应答消息流,和 HTTP 相似。所以选择长链接方式的 TCP 协议会更高效,与 HTTP 不一样的是在协议层面咱们定义了每一个消息的惟一 id,所以能够更容易的复用链接。 既然使用长链接,那么第一个问题是到底 client 和 server 之间须要多少根链接?实际上单链接和多链接在使用上没有区别,对于数据传输量较小的应用类型,单链接基本足够。单链接和多链接最大的区别在于,每根链接都有本身私有的发送和接收缓冲区,所以大数据量传输时分散在不一样的链接缓冲区会获得更好的吞吐效率。因此,若是你的数据传输量不足以让单链接的缓冲区一直处于饱和状态的话,那么使用多链接并不会产生任何明显的提高,反而会增长链接管理的开销。 链接是由 client 端发起创建并维持。若是 client 和 server 之间是直连的,那么链接通常不会中断(固然物理链路故障除外)。若是 client 和 server 链接通过一些负载中转设备,有可能链接一段时间不活跃时会被这些中间设备中断。为了保持链接有必要定时为每一个链接发送心跳数据以维持链接不中断。心跳消息是 RPC 框架库使用的内部消息,在前文协议头结构中也有一个专门的心跳位,就是用来标记心跳消息的,它对业务应用透明。
10.执行调用:
client stub 所作的事情仅仅是编码消息并传输给服务方,而真正调用过程发生在服务方。server stub 从前文的结构拆解中咱们细分了 RpcProcessor 和 RpcInvoker 两个组件,一个负责控制调用过程,一个负责真正调用。这里咱们仍是以 java 中实现这两个组件为例来分析下它们到底须要作什么? java 中实现代码的动态接口调用目前通常经过反射调用。除了原生的 jdk 自带的反射,一些第三方库也提供了性能更优的反射调用,所以 RpcInvoker 就是封装了反射调用的实现细节。
11.RPC 异常处理
不管 RPC 怎样努力把远程调用假装的像本地调用,但它们依然有很大的不一样点,并且有一些异常状况是在本地调用时绝对不会碰到的。在说异常处理以前,咱们先比较下本地调用和 RPC 调用的一些差别:(1)本地调用必定会执行,而远程调用则不必定,调用消息可能由于网络缘由并未发送到服务方。(2) 本地调用只会抛出接口声明的异常,而远程调用还会跑出 RPC 框架运行时的其余异常。(3) 本地调用和远程调用的性能可能差距很大,这取决于 RPC 固有消耗所占的比重。 正是这些区别决定了使用 RPC 时须要更多考量。当调用远程接口抛出异常时,异常多是一个业务异常,也多是 RPC 框架抛出的运行时异常(如:网络中断等)。业务异常代表服务方已经执行了调用,可能由于某些缘由致使未能正常执行,而 RPC 运行时异常则有可能服务方根本没有执行,对调用方而言的异常处理策略天然须要区分。因为 RPC 固有的消耗相对本地调用高出几个数量级,本地调用的固有消耗是纳秒级,而 RPC 的固有消耗是在毫秒级。那么对于过于轻量的计算任务就并不合适导出远程接口由独立的进程提供服务,只有花在计算任务上时间远远高于 RPC 的固有消耗才值得导出为远程接口提供服务。