RPC,Remote Procedure Call,远程过程调用。允许一台计算机调用另一台计算机上的程序得到结果,在代码中不需要做额外的编程,就像在本地调用一样。
1.RPC要解决的两个问题
1)解决分布式系统(基于高性能和高可靠等因素)中,服务之间的调用问题,即允许像调用本地服务调用远程的其他服务,实现跨进程的交互。
2)远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
2.RPC框架原理
* Server: 暴露服务的服务提供方。
* Client: 调用远程服务的服务消费方。
* Registry: 服务注册与发现的注册中心。
3.RPC调用流程
一次完整的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)步骤封装起来,让用户对这些细节透明。
4.服务注册&发现
服务提供者 启动后 主动向注册中心注册机器ip、port(端口)以及提供的服务列表;
服务消费者 启动时 向注册中心获取服务提供方地址列表,可实现软负载均衡和Failover(容错机制);
5.使用技术
1)动态代理
生成 client stub和server stub需要用到 Java 动态代理技术 ,可以使用JDK原生的动态代理机制,可以使用一些开源字节码工具框架 如:CgLib、Javassist等。
2)序列化
为了能在网络上传输和接收 Java对象,我们需要对它进行 序列化和反序列化 操作。
* 序列化:将Java对象转换成byte[]的过程,也就是编码的过程;
* 反序列化:将byte[]转换成Java对象的过程;
3)NIO
当前很多RPC框架都直接基于netty这一IO通信框架,比如阿里巴巴的HSF、dubbo,Hadoop Avro,推荐使用Netty 作为底层通信框架。
4)服务注册中心(可用技术:Zookeeper、Redis、Consul-服务发现)
RPC框架最核心的模块之一,用于服务的注册和订阅。Zookeeper是一个树形结构的目录服务,支持变更推送,因此非常适合作为Dubbo服务的注册中心。
参考:https://blog.csdn.net/feeltouch/article/details/86670885
6. 如何实现一个RPC?
一个完整的RPC过程,可用如下图描述:
以Client为例,Application-rpc的调用方。Client stub-代理对象,看起来像是Calculator的实现类,其内部通过rpc方式进行远程调用的代理对象。Client Run-time Library,实现远程调用的工具包,如jdk的socket。最后通过底层网络实现数据的传输。
这个过程中最重要的就是序列化和反序列化了,因为数据传输的数据包必须是二进制的,直接丢一个Java对象过去,人家可不认识,必须把Java对象序列化为二进制格式,传给Server端,Server端接收到之后,再反序列化为Java对象。
7. RPC vs Restful
两者并不是一个维度的概念,RPC涉及的唯独更广。
RPC是面向过程,Restful是面向资源,并且使用了Http动词。
8. RPC vs RMI
严格来说这两者也不是一个维度的。
RMI是Java提供的一种访问远程对象的协议,已实现,可直接使用。
RPC只是一种编程模型,并没规定具体要怎样实现,甚至可以在RPC框架里使用RMI来实现数据的传输,如Dubbo-RMI协议。
参考:https://www.jianshu.com/p/2accc2840a1b
4. 一个优秀的RPC框架需要考虑的问题?
实现一个RPC不算难,难的是实现一个高性能高可靠的RPC框架。
分布式—一个服务可能有多个实例,在调用时需要通过一个服务注册中心获取这些实例的地址,如在Dubbo里使用Zookeeper作为注册中心,调用时从Zookeeper获取服务的实例列表,再从中选择一个进行调用。那么选择哪个调用好就需要负载均衡,便需考虑如何实现负载均衡,比如Dubbo就提供了好几种负载均衡策略。每次调用时都去注册中心查询实例列表,效率便会很低,于是有了缓存,就要考虑缓存的更新问题。
客户端不能在每次调用完都等着服务端返回数据,便要支持异步调用;
服务端接口修改,旧接口仍被用,便需要版本控制;
服务端不能在每次接到请求都马上启动一个线程去处理,便需要线程池;
服务端关闭时,还没处理完的请求怎么办?是直接结束呢,还是等全部请求处理完再关闭呢?
网友评论