RPC ——远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC 协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在 OSI 网络通信模型中,RPC 跨越了传输层和应用层。RPC 使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC 采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息。最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
有多种 RPC 模式和执行。最初由 Sun 公司提出。IETF ONC 宪章重新修订了 Sun 版本,使得 ONC RPC 协议成为 IETF 标准协议。现在使用最普遍的模式和执行是开放式软件基础的分布式计算环境(DCE)。
说起 RPC,就不能不提到分布式,这个促使 RPC 诞生的领域。
假设有一个计算器接口 Calculator,以及它的实现类 CalculatorImpl。在系统还是单体应用时,要调用 Calculator 的 add 方法来执行一个加运算,直接 new 一个 CalculatorImpl,然后调用 add 方法就行了。这其实就是非常普通的本地函数调用。因为在同一个地址空间,或者说在同一块内存,所以通过方法栈和参数栈就可以实现。
现在,基于高性能和高可靠等因素的考虑,决定将系统改造为分布式应用,将很多可以共享的功能都单独拎出来。比如上面说到的计算器,单独把它放到一个服务里头,让别的服务去调用它。
这下问题来了,服务 A 里头并没有 CalculatorImpl 这个类,那它要怎样调用服务 B 的 CalculatorImpl 的 add 方法呢?
方案1️⃣:
可以模仿 B/S 架构的调用方式,在 B 服务暴露一个 Restful 接口,然后 A 服务通过调用这个 Restful 接口来间接调用 CalculatorImpl 的 add 方法。
很好,这已经很接近 RPC 了。不过这样每次调用时,都需要写一串发起 http 请求的代码,比如 httpClient.sendRequest…之类的。能不能像本地调用一样,去发起远程调用,让使用者感知不到远程调用的过程呢,像这样:
@Reference
private Calculator calculator;
...
calculator.add(1,2);
...
方案2️⃣:
用代理模式!而且最好是结合 Spring IoC 一起使用,通过 Spring 注入 calculator 对象,注入时,如果扫描到对象加了 @Reference 注解,那么就给它生成一个代理对象,将这个代理对象放进容器中。而这个代理对象的内部,就是通过 httpClient 来实现 RPC 远程过程调用的。这样描述比较抽象,不过这就是很多 RPC 框架要解决的问题和解决的思路,比如阿里的 Dubbo。
RPC要解决的两个问题:
1️⃣解决分布式系统中,服务之间的调用问题。
2️⃣远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
实际情况下,RPC 很少用到 http 协议来进行数据传输,毕竟只是想传输一下数据而已,何必动用到一个文本传输的应用层协议呢。为什么不直接使用二进制传输?比如直接用 Java 的 Socket 协议进行传输?
不管用何种协议进行数据传输,一个完整的RPC过程,都可以用下面这张图来描述:
以左边的 Client 端为例,Application 就是 RPC 的调用方。Client Stub 就是上面说到的代理对象,也就是那个看起来像是 Calculator 的实现类,其实内部是通过 RPC 方式来进行远程调用的代理对象。至于 Client Run-time Library,则是实现远程调用的工具包,比如 jdk 的 Socket。最后通过底层网络实现实现数据的传输。
这个过程中最重要的就是序列化和反序列化了。因为数据传输的数据包必须是二进制的,直接丢一个 Java 对象过去,人家可不认识,必须把 Java 对象序列化为二进制格式,传给 Server 端,Server 端接收到之后,再反序列化为 Java 对象。
其实这两者并不是一个维度的概念,总得来说 RPC 涉及的维度更广。如果非要比较,那么可以从 RPC 风格的 url 和 Restful 风格的 url 上进行比较。比如一个查询订单的接口,用 RPC 风格,可能会这样写:
/queryOrder?orderId=123
用 Restful 风格:
Get
/order?orderId=123
RPC是面向过程,Restful是面向资源,并且使用了 Http 动词。从这个维度上看,Restful 风格的 url 在表述的精简性、可读性上都要更好。
严格来说这两者也不是一个维度的。
RMI 是 Java 提供的一种访问远程对象的协议,是已经实现好的,可以直接用。
RPC 只是一种编程模型,并没有规定具体要怎样实现。甚至都可以在的RPC框架里面使用RMI来实现数据的传输,比如 Dubbo:Dubbo - rmi 协议。
要实现一个RPC不算难,难的是实现一个高性能高可靠的RPC框架。
比如,既然是分布式了,那么一个服务可能有多个实例,在调用时,要如何获取这些实例的地址呢?
这时候就需要一个服务注册中心,比如在 Dubbo 里头,就可以使用 Zookeeper 作为注册中心。在调用时,从 Zookeeper 获取服务的实例列表,再从中选择一个进行调用。
那么选哪个调用好呢?这时候就需要负载均衡了,于是又得考虑如何实现负载均衡,比如 Dubbo 就提供了好几种负载均衡策略。
这还没完,总不能每次调用时都去注册中心查询实例列表吧,这样效率多低呀,于是又有了缓存,有了缓存,就要考虑缓存的更新问题……
你以为就这样结束了,没呢,还有这些: