本地调用:单个进程之间的通信通过方法之间的方法名调用,运行时找到内存中方法实际的入口地址,就可完成方法间的调用;
那如果在分布式系统中为了多进程协作共同完成某一任务,且要调用的方法或者函数不再本进程内,那通过何种通信方式能完成这一目的。远程调用此时就可以发挥作用。
远程调用(Remote Procedure Call):进程在调用另一个机器进程的方法时,不关注两个进程底部的通信细节,像调用本地进程方法一般调用另一个机器进程的方法。
说到rpc,不能不聊到http/https。不少数量的开发者会认为rpc和http/https是两种不同形式的调用方式。但实际上rpc包含了序列化协议和传输协议,而http/https只是传输协议。其实rpc完全也可以使用http/https作为传输协议使用。所以说rpc不依赖具体的传输协议,只不过是使用场景上不同而已。此处也可知道远程调用的第一个属性--网络传输协议。
以下是RPC的调用构成:
创建stub functions让开发者认为是在调用本地方法;
client stub(proxy):打包参数,调用server方法
server stub (skeleton):接收请求,调用server本地方法
图片来源:https://people.cs.rutgers.edu/~pxk/417/notes/rpc.html
复制
1、客户端调用stub
2、stub 将参数打包,并序列化发送网络信息
3、网络信息发送到server
4、接收信息并发送到server stub
5、反序列化参数,并调用server 方法
6、返回执行结果
7、序列化返回结果并发送网络信息
8、网络信息发送到client
9、client stub接受网络信息
10、client stub反序列化结果,并发送给client
以上10步,是一个rpc过程的拆解,其中2~8被封装起来,用户看不到调用细节,就像是调用本地方法一样调用远程方法。
同样,也可发现rpc第二个属性,序列化与反序列化。
本文的开头也提到了,本地调用可以直接通过方法在内存中的地址来达到调用目的,但是远程调用如此实现不了。解决办法就是需要在client stub在调用时传递‘类名+方法名’,对应server sleketon也能通过维护的service映射表在表中找到对应的方法。此处便是RPC第三个属性,调用类+方法的映射。
目前Java使用比较多的RPC方案主要有RMI(JDK自带)、Dubbo、Hessian以及Thrift等。
RMI(Remote Method Invocation)
图片来源:https://people.cs.rutgers.edu/~pxk/417/notes/pdf/02c-rpc-studies-slides.pdf
复制
1、在服务器上运行rmi registry,Server中的Remote object 将对象绑定到注册表registry上;
Stuff obj = new Stuff();
Naming.bind("MyStuff", obj);
复制
2、client lookup 名称来查找对象,获取远程对象引用以执行远程对象调用;
MyInterface test = (MyInterface)
Naming.lookup("rmi://www.pk.org/MyStuff");
复制
3、rmi registry service 返回test对象给本地stub; 现在本地stub知道去哪里发送请求并调用远端方法
test.func(1, 2, "hi");
复制
序列化与反序列化,传输层的走向以及stub和skeleton的作用与rpc一致,不再赘述。
RPC与RMI的区别
1、调用方式:
RMI调用方法,RMI中是通过在客户端的Stub对象作为远程接口进行远程方法的调用。每个远程方法都具有方法签名(例如上文例子中的Mystuff)。如果一个方法在服务器上执行,但是没有相匹配的签名被绑定到服务器registry上,那么这个新方法就不能被RMI客户方所调用。
RPC调用函数,RPC中是通过网络服务协议向远程主机发送请求,请求包含了一个参数集和一个文本值,通常形成“classname.methodname(参数集)”的形式。这就向RPC服务器表明,被请求的方法在“classname”的类中,名叫“methodname”。然后RPC服务器就去搜索与之相匹配的类和方法,并把它作为那种方法参数类型的输入。这里的参数类型是与RPC请求中的类型是匹配的。一旦匹配成功,这个方法就被调用了,其结果被编码后通过网络协议发回。
2、语言支持:
RMI只用于Java,支持传输对象。
RPC是基于C语言的,不支持传输对象,是网络服务协议,与操作系统和语言无关。
3、调用结果的返回形式:
1、RMI是面向对象的,Java是面向对象的,RMI的调用结果可以是对象类型或者基本数据类型。
2、RPC的结果统一由外部数据表示(External Data Representation,XDR)语言表示,这种语言抽象了字节序类和数据类型结构之间的差异。只有由XDR定义的数据类型才能被传递,可以说RMI是面向对象方式的Java RPC。
4、定位:
RPC:一种网络服务协议框架;
RMI:基于对象的RPC具体实现,应用编程接口API;
Dubbo
Dubbo 与 Dubbo协议是两个概念,前者指的是SOA服务治理的框架,而后者指的是Dubbo用来实现rpc的协议。Dubbo支持包括dubbo、rmi、hessian、http、webservice、thrift等网络传输协议,其中dubbo协议是其缺省协议。
Dubbo 2.0 框架定义了私有的RPC协议,其中请求和响应协议的具体内容用表格来展示,如下。
具体协议详情见下面链接,不重复造轮子。
dubbo 2.0 协议 https://zhuanlan.zhihu.com/p/98562180
Dubbo 3.0 经过Dubbo 1.0和2.0的演进,推出了Triple 协议,具体可见下面链接:
Dubbo3 支持的通信协议 https://dubbo.apache.org/zh/docs/concepts/rpc-protocol/
Dubbo3.0相对于之前的协议,升级和完善了许多功能,具体可见上面链接。但是有一条特别强调的是,升级之后的Triple协议支持Streaming的场景,之前版本的协议下dubbo 采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况。缺点是对大数据量,比如传文件或者视频等支持不友好,存在调用超时风险。而支持Streaming之后,无疑对这一点有着至关重要的提升,引用上文链接中的一段文字:
通过Triple协议的Streaming RPC方式,会在consumer跟provider之间建立多条用户态的长连接,Stream。同一个TCP连接之上能同时存在多个Stream,其中每条Stream都有StreamId进行标识,对于一条Stream上的数据包会以顺序方式读写。
在上面分析完dubbo协议之后,我们在本文RPC模块中描述到,RPC包含三个方面:1、网络传输协议 2、序列化和反序列化 3、映射关系
网络协议已经说过,序列化和反序列化采用的是Hessian 二进制序列化,会在下面hessian模块中说到。而映射关系,dubbo作为当下比较流行的服务治理框架,集成注册中心register(zookeeper、nacos等)协调client和server地址(ip、port、方法名)的注册与发现。
Hessian
Hessian 是 Caucho 开源的一个 RPC 框架,其通讯效率高于 WebService 和 Java 自带的序列化。
依赖引入:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>quercus</artifactId>
<version>4.0.65</version>
</dependency>
复制
特性
连接个数:多连接
连接方式:短连接
传输协议:HTTP
传输方式:同步传输
序列化:Hessian二进制序列化
适用范围:传入传出参数数据包较大,提供者比消费者个数多,提供者压力较大,可传文件。
适用场景:页面传输,文件传输,或与原生hessian服务互相操作
约束
1、参数及返回值需实现Serializable接口
2、参数及返回值不能自定义实现List, Map, Number, Date, Calendar等接口,只能用JDK自带的实现,因为hessian会做特殊处理,自定义实现类中的属性值都会丢失。
协议原理见下:
hessian协议原理
https://www.cnblogs.com/shangxiaofei/p/4222170.html
Thrift
依赖引入:
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.15.0</version>
</dependency>
复制
在多种不同的语言之间通信thrift可以作为二进制的高性能的通讯中间件,支持数据(对象)序列化和多种类型的RPC服务。Thrift是IDL(interface definition language)描述性语言的一个具体实现,Thrift适用于程序对程序静态的数据交换,需要先确定好它的数据结构,它是完全静态化的,当数据结构发生变化时,必须重新编辑IDL文件,代码生成,再编译载入的流程,跟其他IDL工具相比较可以视为是Thrift的弱项,Thrift适用于搭建大型数据交换及存储的通用工具,对于大型系统中的子系统间数据传输相对于JSON和xml无论在性能、传输大小上有明显的优势。
详见以下链接:
Thrift RPC框架介绍
http://www.blogjava.net/ldwblog/archive/2014/12/03/421011.html
综上,介绍了RPC的原理以及RMI、Dubbo、Hessian、Thrift四种RPC方式。阅完全文可知,协议是RPC的核心,它规范了数据在网络中的传输内容和格式。但是rpc不仅仅是传输协议,还包括传输内容的序列化与反序列化协议、客户端与服务端方法的映射关系。而无论是SUN的RMI、阿里巴巴的Dubbo、Hessian、还是facebook的thirft都是对不同协议的不同实现,都为编程人员提供了应用PRC技术的程序接口(API)。
开发时如何进行选型:
是否允许代码侵入: 即需要依赖相应的代码生成器生成代码,比如Thrift。
是否需要长连接获取高性能: 如果对于性能需求较高,果断选择基于TCP的Thrift、Dubbo。
是否需要跨越网段、跨越防火墙:这种情况一般选择基于HTTP协议的Hessian和Thrift的HTTP Transport。实际情况是,如果是这种情况,一般不走RPC 而是直接spring-cloud fegin(http/https)调用了。
此外,Google推出的基于HTTP2.0的gRPC框架也开始得到应用,其序列化协议基于Protobuf,网络框架使用的是Netty4,但是其需要生成代码。此外,dubbo3.0 Triple协议也兼容了gRPC,dubbo3.0 客户端/服务端可以与原生grpc客户端打通。
有兴趣可参考:
gRPC 官方文档中文版V1.0
http://doc.oschina.net/grpc?t=58008