无论是微服务还是分布式服务(都是SOA,都是面向服务编程),都面临着服务间的远程调用。之前只会使用相关框架,但其实没有体系的了解过这一块内容,从这篇Blog开始详细学习远程调用的方式、原理、常用框架,基于两个基本大的分类:HTTP和RPC来进行学习,本篇主要是进行综合性概述,分别了解两种不同方式的基本原理和常用框架,最后依据使用场景进行一个讨论。
RPC调用方式
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易
说得通俗一点就是:A计算机提供一个服务,B计算机可以像调用本地服务那样调用A计算机的服务
- 实现远程调用其他计算机的服务,要实现远程调用,肯定是通过网络传输数据。A程序提供服务,B程序通过网络将请求参数传递给A,A本地执行后得到结果,再将结果返回给B程序。这里需要关注的有两点:
- 采用何种网络通讯协议?现在比较流行的RPC框架,都会采用TCP作为底层传输协议
- 数据传输的格式怎样?两个程序进行通讯,必须约定好数据传输格式。就好比两个人聊天,要用同一种语言,否则无法沟通。所以,我们必须定义好请求和响应的格式。另外,数据在网路中传输需要进行序列化,所以还需要约定统一的序列化的方式。
- 像调用本地服务一样调用远程服务,如果仅仅是远程调用,还不算是RPC,因为RPC强调的是过程调用,调用的过程对用户而言是应该是透明的,用户不应该关心调用的细节,可以像调用本地服务一样调用远程服务。所以RPC一定要对调用的过程进行封装
虽然不同的RPC框架实现方式不同,但是封装的基本范式类似,封装的基本范式如下:
RPC基本原理
两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。为什么要用RPC呢?就是无法在一个进程内,甚至一个计算机内通过本地调用的方式完成的需求,比如不同的系统间的通讯,甚至不同的组织间的通讯,由于计算能力需要横向扩展,需要在多台机器组成的集群上部署应用。RPC就是要像调用本地的函数一样去调远程函数
在上述图中,通过1-10的步骤图解的形式,说明了RPC每一步的调用过程。
客户端发起网络调用
- 客户端想要发起一个远程过程调用,首先通过调用本地客户端Stub程序的方式调用想要使用的功能方法名;
- 客户端Stub程序接收到了客户端的功能调用请求,将客户端请求调用的方法名,携带的参数等信息做序列化操作,并打包成数据包。
网络传递数据
- 客户端Stub查找到远程服务器程序的IP地址,调用Socket通信协议,通过网络发送给服务端。
服务端接收到调用:
- 服务端Stub程序接收到客户端发送的数据包信息,并通过约定好的协议将数据进行反序列化,得到请求的方法名和请求参数等信息。
- 服务端Stub程序准备相关数据,调用本地Server对应的功能方法进行,并传入相应的参数,进行业务处理。
服务端生成调用结果:
- 服务端程序根据已有业务逻辑执行调用过程,待业务执行结束,将执行结果返回给服务端Stub程序。
- 服务端Stub程序将程序调用结果按照约定的协议进行序列化,并通过网络发送回客户端Stub程序。
网络传递数据
- 服务端Stub查找到远程客户端程序的IP地址,调用Socket通信协议,通过网络发送给客户端。
客户端收到响应
- 客户端Stub程序接收到服务端Stub发送的返回数据,对数据进行反序列化操作,并将调用返回的数据传递给客户端请求发起者。
- 客户端请求发起者得到调用结果,整个RPC调用过程结束
从RPC调用中我们发现至少需要生产者(服务提供者)和消费者(服务调用者)两个角色。
从实现技术上分为三层:代理层、序列化层和网络层,对于消费者:
- 代理层:消费者将对应的接口,通过RPC框架的代理来生成一个对象到Spring容器中。代理层将代理接口生成该接口的对象,该对象处理调用时传过来的对象、方法、参数,通过序列化层封装好,调用网络层。
- 序列化层:将请求的参数序列化成报文;将返回的报文反序列化成对象;
- 网络层: 将报文与服务端通信;接收返回结果
对于生产者而言:
- 代理层:一个应用提供服务,必须由一个网络监听的模块,这个模块大多有开源的容器来处理网络上的监听;服务需要注册,只有注册了的服务才可以被调用;注册的服务需要被我们发射调用到,来进行相应的处理。
- 序列化层: 就是相应的做请求的反序列化和结果的序列化。
- 网络层:接收客户端报文;将序列化的结果返回给客户端
所以从上述描述中我们发现,完整的流程其实还需要监听器和注册中心,这在不同的框架中有不同的实现方式,例如Dubbo使用Zookeeper做注册中心
RPC常用框架
RPC常用框架分为两类:一类是跟某种特定语言平台绑定的,另一类是与语言无关即跨语言平台的。跟语言平台绑定的开源 RPC 框架主要有下面几种:
- Dubbo:国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。
- Motan:微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。
- Tars:腾讯内部使用的 RPC 框架,于 2017 年对外开源,仅支持 C++ 语言。
- Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架,仅支持 Java 语言
而跨语言平台的开源 RPC 框架主要有以下几种。
- gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言。
- Thrift:最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持多种语言。
如果你的业务场景仅仅局限于一种语言的话,可以选择跟语言绑定的 RPC 框架中的一种。如果涉及多个语言平台之间的相互调用,就应该选择跨语言平台的 RPC 框架。这几种里我会重点学习和介绍Dubbo和Spring Cloud,如果有多余精力,会学习一下gRPC和Thrift。更多关于这几种框架的对比:RPC框架对比
HTTP调用方式
http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。现在热门的Rest风格,就可以通过http协议来实现。其实这个无需做过多的介绍了,HTTP在我之前的多篇Blog中均有提到,尤其是这篇:【计算机网络基础 六】应用层
HTTP基本原理
Http协议,又叫超文本传输协议,是一种应用层协议。规定了网络传输的请求格式、响应格式、资源定位和操作的方式等。但是底层采用什么网络传输协议,并没有规定,不过现在都是采用TCP协议作为底层传输协议。Http与RPC的远程调用非常像,都是按照某种规定好的数据格式进行网络通信,有请求,有响应。
其实我们之前一直学习的是服务端代码如何编写以及Tomcat上如何部署服务端代码以及代码如何发挥作用,看上图比较简单,实际上很复杂,感兴趣可以参照之前的Blog: 深入理解Tomcat系统架构及工作原理,【Java Web编程 八】深入理解Servlet常用对象,这两篇Blog详细介绍了服务端Servlet代码如何编写,以及Tomcat的工作原理,为何服务端代码部署到Tomcat上能发挥作用。
HTTP常用框架
不同于RPC框架中生产端服务端我们都需要重新配置,通常我们对外封装的Controller接口其实就可以充当服务端,也就是被调用方。所以HTTP的框架这里只要了解调用方的框架即可,其实也就是HTTP的客户端,其实浏览器访问本质上也算HTTP服务,不同的是需要客户端浏览器渲染服务端返回的结果,常见HTTP客户端框架如下:
- HttpURLConnection:JDK自带的HTTP请求客户端。
- HttpClient:HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性,它不仅使客户端发送Http请求变得容易,而且也方便开发人员测试接口(基于Http协议的),提高了开发的效率,也方便提高代码的健壮性。
- OkHttp :OkHttp是一个高效的 HTTP 库,它实现了几乎和java.net.HttpURLConnection一样的API,现在最新版本为OkHttp3
- Retrofit: Retrofit是一个非常好的优化体验的框架,其核心还是调用OkHttp框架进行网络接口请求,其最大的好处就是让我们调用网络接口就像调用普通接口一样方便,通过各种注解方便的进行网络请求,目前最新版本为Retrofit2。
不同于RPC框架的多样选择,我建议HTTP框架直接使用Retrofit,它吸收了OkHttp的所有好处,还帮我们做了最大程度的封装,使用起来非常方便
RPC和HTTP的区别
二者之间的核心区别就是,RPC基于TCP协议,有一定侵入性,但性能高;HTTP基于HTTP协议,侵入性低,性能低:
- 传输协议:RPC可以基于TCP协议,也可以基于HTTP协议。HTTP基于HTTP协议。
- 传输效率:RPC使用自定义的TCP协议,可以让请求报文体积更小,或者使用HTTP2.0协议,也可以很好地减少报文体积,提高传输效率。HTTP如果时基于HTTP1.1的协议,请求中会包含很多无用的内容,如果是基于HTTP2.0,那么简单地封装一下还是可以作为一个RPC使用的,这时标准RPC框架更多是服务治理。
- 性能消耗:RPC可以基于thrift实现高效的二进制传输,HTTP大部分是通过json实现的,字节大小和序列化耗时都比thrift要更消耗性能
- 负载均衡:RPC基本都自带了负载均衡策略,HTTP需要配置Nginx,HAProxy实现
- 服务治理(下游服务新增,重启,下线时如何不影响上游调用者):RPC能做到自动通知,不影响上游,HTTP需要事先通知,修改Nginx/HAProxy配置
RPC主要用于公司内部服务调用,性能消耗低,传输效率高,服务治理方便。HTTP主要用于对外的异构环境,浏览器调用,APP接口调用,第三方接口调用等等。
既然两种方式都可以实现远程调用,我们该如何选择呢?
- 速度来看,RPC要比http更快,虽然底层都是TCP,但是http协议的信息往往比较臃肿
- 难度来看,RPC实现较为复杂,http相对比较简单
- 灵活性来看,http更胜一筹,因为它不关心实现细节,跨平台、跨语言。
因此,两者都有不同的使用场景:
- 如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的,尤其是在内部子系统较多、接口较多的情况下,RPC框架的好处就凸显出现了,首先是长连接,不必每次通信都要像HTTP那样三次握手,减少了网络开销;其次是RPC框架一般都有注册中心,有丰富的监控发布方法;RPC接口的发布、下线、动态扩展等对调用方是无感知的、统一化的操作
- 如果需要更加灵活,跨语言、跨平台,显然http更合适
其实微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。从成本上考虑,如果从一个服务需要调用的接口太多或者入参及响应Model过于复杂,还是建议RPC,因为HTTP的封装代码需要写太多了,反之,则使用HTTP,且要用Retrofit2。RPC接口即相当于调用本地接口一样调用远程服务的接口;HTTP接口是基于http协议的post接口和get接口(等等,2.0版本协议子支持更多)
总结一下
无论是RPC还是HTTP,它们都是远程访问的一种方式,基于不同的网络层级,所以优缺点也显而易见,RPC一般直接基于传输层的TPC协议,所以性能更好,但是RPC要求有注册中心,客户端服务端语言不同需要做兼容(例如thrift和gRpc框架可以进行语言兼容);HTTP基于应用层的HTTP协议,socket传输时多做一层转化,性能差一些,但是HTTP一般不需要注册中心,客户端服务端语言无关,只要能接收和发送HTTP请求即可。使用时如果是内部同语言栈子系统相互调用且接口众多复杂建议使用RPC,如果是异构多语言形式或者接口定义较为简单则建议使用HTTP。