RPC(Remote Procedure Call)远程过程调用, 允许像调用本地服务那样调用远程其它服务,即实现跨进程交互。RPC 调用分以下两种:
同步调用:客户方等待调用执行完成并返回结果。
异步调用:客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。 若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
异步和同步的区分在于是否等待服务端执行完成并返回结果。
RPC框架如图:
RPC 服务方通过 RpcServer 去导出(export)远程接口方法,而客户方通过 RpcClient 去引入(import)远程接口方法。客户方像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理RpcProxy 。代理封装调用信息并将调用转交给RpcInvoker 去实际执行。在客户端的RpcInvoker 通过连接器RpcConnector 去维持与服务端的通道RpcChannel,并使用RpcProtocol 执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。
RPC 服务端接收器 RpcAcceptor 接收客户端的调用请求,同样使用RpcProtocol 执行协议解码(decode)。解码后的调用信息传递给RpcProcessor 去控制处理调用过程,最后再委托调用给RpcInvoker 去实际执行并返回调用结果。
各组件的详细职责:
1.RpcServer
负责导出(export)远程接口
2. RpcClient
负责导入(import)远程接口的代理实现
3. RpcProxy
远程接口的代理实现
4. RpcInvoker
客户方实现:负责编码调用信息和发送调用请求到服务方并等待调用结果返回
服务方实现:负责调用服务端接口的具体实现并返回调用结果
5. RpcProtocol
负责协议编/解码
6. RpcConnector
负责维持客户方和服务方的连接通道和发送数据到服务方
7. RpcAcceptor
负责接收客户方请求并返回请求结果
8. RpcProcessor
负责在服务方控制调用过程,包括管理调用线程池、超时时间等
9. RpcChannel
数据传输通道
一次完整的RPC调用流程(同步调用)如下:
1)服务消费方(client)调用以本地调用方式调用服务
2)client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体
3)client stub找到服务地址,并将消息发送到服务端
4)server stub收到消息后进行解码
5)server stub根据解码结果调用本地的服务
6)本地服务执行并将结果返回给server stub
7)server stub将返回结果打包成消息并发送至消费方
8)client stub接收到消息,并进行解码
9)服务消费方得到最终结果
RPC框架的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明。
RPC框架的核心技术点:
(1)服务注册&发现:
服务提供者启动后主动向注册中心***器ip、port以及提供的服务列表
服务消费者启动时向注册中心获取服务提供方地址列表,可实现软负载均衡和Failover
(2)远程代理对象:
服务调用者用的服务实际是远程服务的本地代理,是通过动态代理来实现。java 里至少提供了两种技术来提供动态代码生成,一种是 jdk 动态代理,另外一种是cglib。
(3)通信:
RPC 可基于 HTTP 或 TCP 协议
(4)序列化:
两方面会直接影响 RPC 的性能,一是传输方式,二是序列化。
-
序列化方式:毕竟是远程通信,需要将对象转化成二进制流进行传输。不同的RPC框架应用的场景不同,在序列化上也会采取不同的技术。 就序列化而言,Java 提供了默认的序列化方式,但在高并发的情况下,这种方式将会带来一些性能上的瓶颈,于是市面上出现了一系列优秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,它们可以取代 Java 默认的序列化,从而提供更高效的性能。
-
编码内容:出于效率考虑,编码的信息越少越好(传输数据少),编码的规则越简单越好(执行效率高)。如下是编码需要具备的信息:
– 调用编码 –
- 接口方法
包括接口名、方法名 - 方法参数
包括参数类型、参数值 - 调用属性
包括调用属性信息,例如调用附件隐式参数、调用超时时间等
– 返回编码 –
- 返回结果
接口方法中定义的返回值 - 返回码
异常返回码 - 返回异常信息
调用异常信息