感觉近几年互联网行业卷的厉害,之前毕业面试的时候都是人均RPC,今天就带大家了解下RPC到底是什么?
1.RPC?
“RPC,就是Remote Procedure Call的简称呀,翻译成中文就是远程过程调用嘛”。
2.本地过程调用?
“是这样,远程过程调用,自然是相对于本地过程调用来说的嘛。”
-
“本地过程调用,就好比你现在在家里,你要想洗碗,那你直接把碗放进洗碗机,打开洗碗机开关就可以洗了。这就叫本地过程调用。”
-
“那远程过程调用嘛,那就是你现在不在家,跟姐妹们浪去了,突然发现碗还没洗,打了个电话过来,叫男朋友去洗碗,这就是远程过程调用啦”,多么通俗易懂的解释,我真是天才!
3.举例子?
假设你有一个计算器接口,Calculator,以及它的实现类CalculatorImpl,那么在系统还是单体应用时,你要调用Calculator的add方法来执行一个加运算,直接new一个CalculatorImpl,然后调用add方法就行了,这其实就是非常普通的本地函数调用,因为在同一个地址空间,或者说在同一块内存,所以通过方法栈和参数栈就可以实现。
现在,基于高性能和高可靠等因素的考虑,你决定将系统改造为分布式应用,将很多可以共享的功能都单独拎出来,比如上面说到的计算器,你单独把它放到一个服务里头,让别的服务去调用它。
这下问题来了,服务A里头并没有CalculatorImpl这个类,那它要怎样调用服务B的CalculatorImpl的add方法呢?
有同学会说,可以模仿B/S架构的调用方式呀,在B服务暴露一个Restful接口,然后A服务通过调用这个Restful接口来间接调用CalculatorImpl的add方法。
很好,这已经很接近RPC了,不过如果是这样,那每次调用时,是不是都需要写一串发起http请求的代码呢?比如httpClient.sendRequest…之类的,能不能像本地调用一样,去发起远程调用,让使用者感知不到远程调用的过程呢,像这样:
@Reference
private Calculator calculator;
...
calculator.add(1,2);
...
这时候,有同学就会说,用代理模式呀!而且最好是结合Spring IoC一起使用,通过Spring注入calculator对象,注入时,如果扫描到对象加了@Reference注解,那么就给它生成一个代理对象,将这个代理对象放进容器中。而这个代理对象的内部,就是通过httpClient来实现RPC远程过程调用的。
可能上面这段描述比较抽象,不过这就是很多RPC框架要解决的问题和解决的思路,比如阿里的Dubbo。
总结一下,RPC要解决的两个问题:
- 解决分布式系统中,服务之间的调用问题。
- 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
4. 如何实现一个RPC?
那么如何实现远程过程调用,也就是RPC呢,一个完整的RPC流程,可以用下面这张图来描述:
其中左边的Client,对应的就是前面的Service A,而右边的Server,对应的则是Service B。
下面一步一步详细解释一下。
- Service A的应用层代码中,调用了Calculator的一个实现类的add方法,希望执行一个加法运算;
- 这个Calculator实现类,内部并不是直接实现计算器的加减乘除逻辑,而是通过远程调用Service B的RPC接口,来获取运算结果,因此称之为Stub;
- Stub怎么和Service B建立远程通讯呢?这时候就要用到远程通讯工具了,也就是图中的Run-time Library,这个工具将帮你实现远程通讯的功能,比如Java的Socket,就是这样一个库,当然,你也可以用基于Http协议的HttpClient,或者其他通讯工具类,都可以,RPC并没有规定说你要用何种协议进行通讯;
- Stub通过调用通讯工具提供的方法,和Service B建立起了通讯,然后将请求数据发给Service B。需要注意的是,由于底层的网络通讯是基于二进制格式的,因此这里Stub传给通讯工具类的数据也必须是二进制,比如calculator.add(1,2),你必须把参数值1和2放到一个Request对象里头(这个Request对象当然不只这些信息,还包括要调用哪个服务的哪个RPC接口等其他信息),然后序列化为二进制,再传给通讯工具类,这一点也将在下面的代码实现中体现;
- 二进制的数据传到Service B这一边了,Service B当然也有自己的通讯工具,通过这个通讯工具接收二进制的请求;
- 既然数据是二进制的,那么自然要进行反序列化了,将二进制的数据反序列化为请求对象,然后将这个请求对象交给Service B的Stub处理;
- 和之前的Service A的Stub一样,这里的Stub也同样是个“假玩意”,它所负责的,只是去解析请求对象,知道调用方要调的是哪个RPC接口,传进来的参数又是什么,然后再把这些参数传给对应的RPC接口,也就是Calculator的实际实现类去执行。很明显,如果是Java,那这里肯定用到了反射。
- RPC接口执行完毕,返回执行结果,现在轮到Service B要把数据发给Service A了,怎么发?一样的道理,一样的流程,只是现在Service B变成了Client,Service A变成了Server而已:Service B反序列化执行结果->传输给Service A->Service A反序列化执行结果 -> 将结果返回给Application,完毕。
理论的讲完了,是时候把理论变成实践了。
请看:https://github.com/youzi530/maven_demo/tree/master/day02_rpcDemo
5.急需解决问题?
- 首先,要解决通讯的问题,主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接,
- 第二,要解决寻址的问题,也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么,这样才能完成调用。比如基于Web服务协议栈的RPC,就要提供一个endpoint URI,或者是从UDDI服务上查找。如果是 RMI调用的话,还需要一个RMI Registry来注册服务的地址。
- 第三,当A服务器上的应用发起远程过程调用时,方法的参数需要通过底层的网络协议如TCP传递到B服务器,由干网络协议是基干二进制的,内存中的参数的值要序列化成二进制的形式,也就
是序列化(Serialize)或编组(marshal),通过寻址和传输将序列化的二进制发送给B服务器。 - 第四,B服务器收到请求后,需要对参数进行反序列化(序列化的逆操作),恢复为内存中的表达方式,然后找到对应的方法(寻址的一部分)进行本地调用,然后得到返回值。
- 第五,返回值还要发送回服务器A上的应用,也要经过序列化的方式发送,服务器A接到后,再反序列化,恢复为内存中的表达方式,交给A服务器上的应用
6.其他?
1、rpc使用场景?
- rpc:内部系统、同步调用
- mq:内部系统、异步调用
- REST/GraphQL:外部系统调用
2、核心流程
3、常见的rpc框架?
- Dubbo:国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源
- Motan:微博内部使用的 RPC 框架,于 2016 年对外开源。
- Tars:腾讯内部使用的 RPC 框架,于 2017 年对外开源
- Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架。
- Thrift:最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持多种语言
- gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言。
- hprose:一个MIT开源许可的新型轻量级跨语言跨平台的面向对象的高性能远程动态通讯中间件。
…