远程过程调用(Remote Procedure Call,RPC)框架作为架构微服务化的基础组件,能大大降低架构微服务化的成本,提高服务调用方与服务提供方的开发效率,屏蔽跨进程调用函数(服务)的各类复杂细节,其调用原理如图6-13所示。让服务提供方像实现本地函数一样来实现分布式服务,开发人员不用考虑底层通信协议;让服务调用方像调用本地函数一样调用远端函数,多数RPC框架以面向接口的方式提供远程方法的调用,对开发人员非常友好。
客户端存根(client stub)用于存放服务器端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务器端。服务器端存根接收客户端发送过来的请求消息并进行解包,再调用本地服务进行处理。
RPC的原理与我们平时打电话的过程非常相似。服务消费者叫作客户端,服务提供者叫作服务器端,两者通常位于网络上两个不同的地址,要完成一次RPC调用,就必须先建立网络连接。建立连接后,双方必须按照某种约定的协议进行网络通信,这个协议就是通信协议。双方能够正常通信后,服务器端接收到请求时,需要以某种方式进行处理,处理成功后,把请求结果返回给客户端。为了减少传输的数据大小,要对数据进行压缩,也就是对数据进行序列化。为了实现远程服务的调用,RPC框架要做到如下最基本的4件事。
▲图6-13 RPC框架调用原理
要完成一次服务调用,首先要解决的问题是服务调用方如何得到服务提供方的地址,其中注册中心扮演了关键角色,服务提供方把自己所提供的服务列表以及当前节点地址登记到注册中心,服务调用方就可以查询注册中心以得到服务提供方的地址。为了实现去中心化设计,多数RPC产品采用本地负载均衡策略,即调用方启动时从注册中心拉取服务地址信息后,在本地缓存,运行过程中真正发起调用时,直接从本地缓存读取服务地址信息,根据一定的负载均衡算法选取某一个地址,发起点对点的直接调用。这样就避免了中心化的服务总线进行服务路由,运行效率更高,弹性伸缩更方便。当服务器端地址信息发生变更时(如节点上下线),由注册中心实时推送变更信息到所有的调用方同步更新。
当使用基于RPC的进程间通信时,客户端向服务器端发起请求,服务器端处理该请求并发回响应。有些客户端可能处在阻塞状态直到得到响应,而有些客户端可能会有一个响应式的非阻塞架构。与使用消息机制的完全异步化架构不同,RPC客户端都假定响应会及时到达。
在远程调用中,客户端和服务器端已经建立了网络连接,服务器端又该如何处理客户端的请求呢?通常来讲,服务器端I/O的方式通常分为3种处理方式:同步阻塞(Blocking I/O,BIO)方式、同步非阻塞(Non-blocking I/O,NIO)方式、异步非阻塞(Asynchronous I/O,AIO)方式。
(1)同步阻塞方式。
客户端每发一次请求,服务器端就生成一个线程去处理。当客户端同时发起很多请求时,服务器端需要创建很多线程去处理每一个请求,如果达到了系统最大的线程数,新的请求就没法处理了。
(2)同步非阻塞方式。
NIO本身是基于事件驱动思想来完成的,其主要解决的是BIO的高并发问题:在使用同步I/O的网络应用中,如果要同时处理多个客户端请求或是在客户端同时和多个服务器进行通信,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身对线程的总数有一定的限制。如果客户端的请求过多,服务器端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能因此而瘫痪。
NIO基于reactor,当socket有流可读或可写入socket时,操作系统会相应地通知应用程序进行处理,应用程序再将流读取到缓冲区或写入操作系统。也就是说,这时已经不是一个连接就要对应一个处理线程了,而是有效的请求对应一个线程。当连接没有数据时,是没有工作线程来处理的。BIO与NIO的一个比较重要的不同之处在于,我们使用BIO时往往会引入多线程,每个连接对应一个单独的线程。而NIO使用单线程或者只使用少量的多线程,多个连接共用一个线程。
(3)异步非阻塞方式。
与NIO不同,当进行读写操作时,AIO只需直接调用API的read或write方法。这两种方法均为异步的,对读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。
客户端只需要发起一个 I/O 操作后立即返回,等 I/O 操作真正完成以后,客户端会得到 I/O 操作完成的通知。此时客户端只需要对数据进行处理就可以了,而不需要进行实际的 I/O 读写操作,因为真正的 I/O 读取或者写入操作已经由内核完成了。这种方式的优势是客户端无须等待,不存在阻塞等待问题。
客户端和服务器端交互时将参数或结果转化为字节流在网络中传输,那么将数据转化为字节流或者将字节流转换成能读取的固定格式时,就需要进行序列化(数据编码)和反序列化(数据解码),序列化和反序列化的速度也会影响远程调用的效率。
常用的序列化方式分为两类:文本类(如XML、json等)、二进制类(如Hessian、Thrift等)。而具体采用哪种序列化方式,主要取决于3个方面的因素。
(1)支持数据结构种类的丰富度。数据结构种类支持得越多越好,这样对使用者来说在编程时更加友好,有些序列化框架如Hessian 2.0还支持复杂的数据结构(比如Map、List等)。
(2)跨语言支持。序列化方式是否支持跨语言也是一个很重要的因素,否则使用的场景就比较局限,比如Java序列化只支持Java语言,所以不能用于跨语言的服务调用。
(3)性能。主要看两点,一是序列化后的压缩比,二是序列化的速度。以常用的 protobuf序列化和json序列化协议为例来对比分析,protobuf序列化的压缩比和速度都要比 json 序列化高很多,所以对性能和存储空间要求比较高的系统选用protobuf序列化更合适;而 json 序列化虽然性能要差一些,但可读性更好,更适合对外部提供服务。
多数RPC框架会选择TCP作为传输协议,也有部分会选择HTTP(如gRPC使用HTTP/2)。不同的协议各有利弊,TCP更加高效,而HTTP在实际应用中更加灵活。
REST全称是Representational State Transfer,中文意思是表述性状态转移。它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。他在论文中提到:“我写作这篇文章的目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。”如果一个架构符合REST的约束条件和原则,我们就称它为REST风格的(RESTful)架构。
REST提供了一系列架构约束,当作为整体使用时,它强调组件交互的可扩展性、接口的通用性、组件的独立部署,以及那些能减少交互延迟的中间件。它强化了安全性,也能封装遗留系统。
——Roy Fielding
REST中一个关键概念是“资源”,它把一切程序能够访问到的业务对象或者处理过程统一定义为资源。REST使用HTTP动词来操作这些资源,并采用特定的语义规范,使用URL引用这些资源。例如,GET请求返回资源的表示形式,该资源通常采用XML或者json的格式表示,但也可以使用其他格式(如二进制)。POST请求创建新资源,PUT更新资源,DELETE删除资源。
RESTful是一种网络应用程序API的设计风格和开发方式,基于HTTP,可以使用XML格式定义或json格式定义。最常用的数据格式是json。由于json能直接被JavaScript读取,因此以json格式编写的REST风格的API具有简单、易读、易用的特点,如图6-14所示。相比于RPC协议,HTTP更规范、更标准、更通用,无论哪种语言都支持HTTP。如果你想对外开放API,开放平台外部的编程语言多种多样,你无法拒绝对每种语言的支持,现在开源中间件,基本最先支持的几个协议都包含HTTP RESTful。
▲图6-14 REST风格的服务提供方
前后端分离架构、微服务架构都有一个共同的愿景:使不同的团队之间实现松耦合,各自能独立地开发和部署。这背后需要依靠一套设计良好的API,它必须更加轻量、可靠、跨平台,因此RESTful API脱颖而出。系统应用提供RESTful API有什么好处呢?由于API就是把Web App的功能全部封装,因此通过API操作数据可以把前端和后端的代码隔离,使得后端代码易于测试,前端代码编写更简单。REST风格协议的特点如下。
(1)每个统一资源标识符(Uniform Resource Identifier,URI)代表一种资源。任何事物只要有被引用的必要,它就是一个资源;要让一个资源可以被识别,需要有一个唯一标识,在Web中这个唯一标识就是URI。
(2)客户端使用GET、POST、PUT、DELETE这4个表示操作方式的动词对服务器端资源进行操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
(3)通过操作资源的表述形式来操作资源,资源在外部的具体呈现可以有多种表述形式(例如文本资源可以采用HTML、XML、json等格式,图片可以使用PNG或JPG格式展现出来),在客户端和服务器端之间传送的也是资源的表述,而不是资源本身。
(4)客户端与服务器端之间的交互在请求之间是无状态的,从客户端到服务器端的每个请求都必须包含理解请求所必需的信息。这里说的无状态通信原则,并不是说客户端应用不能有状态,而是指服务器端不应该保存客户端状态。
很多开发人员表示他们的代码是基于HTTP的API进行开发的,那么用了HTTP就叫REST风格吗?答案是否定的。Leonard Richardson曾提出过一个RESTful成熟度模型,模型中从level 0到level 3,数字越大,表示采用RESTful的成熟度越高,如图6-15所示。成熟度模型并不是什么工业标准,这里仅作为参考来讲解RESTful思想。读者可以对比思考自己项目中是如何使用RESTful API的。
▲图6-15 Leonard Richardson提出的RESTful成熟度模型
在此简单说明RESTful成熟度模型的4个等级。
level 0:没有明确的资源概念,仅作为通道,只有一个URL,只使用单个HTTP方法。这个阶段还称不上使用了RESTful,HTTP仅作为RPC的传输通道。想象一下,在本地调用某个方法时,它可能需要一个输入对象,处理完成后返回另一个结果对象。这个阶段的HTTP只是用在请求调用远程方法时,传递输入对象给服务器端,并在方法执行结束后,传递结果对象给客户端。
level 1:有明确的资源概念,存在很多URL,只使用单个HTTP方法。客户端每次请求都是对服务器端某个或某一类资源的操作。服务器端一切可被标识的事物都可以称为资源,例如,一张图片、一个订单、一个产品、一个流程、最活跃的10个用户等。每个资源都可以用URI来表示。所以从现在开始,URI用来标识一个资源。
level 2:有明确的资源操作,有很多URL,使用HTTP作为操作资源的统一接口。通常将对于资源的CRUD式操作分别映射到4个HTTP方法(有时也会使用新增的PATCH方法),这里的每一个动词都有它自身的语义。
level 3:这一阶段的RESTful API具备了自描述的能力,从而实现服务自动发现。自描述是指告诉用户当前状态以及下一步可能的各种操作。如果将应用想象成一个大的状态引擎,那么我们每次针对URI的操作,都是将应用从一种状态转变为另一种状态。而当前状态(可表述的)里又包含了下一步可进行的操作,一步一步地传输状态,直至完成所有的操作。一般达到level 3成熟度模型的应用,使用超媒体作为应用状态的引擎(Hypermedia as the Engine of Application State,HATEOAS)。
REST风格具有开放、标准、通用的特点,尤其在跨语言的异构环境下系统的互联互通具有天然的优势。对于REST风格的应用开发模式,有两点值得注意。
(1)RESTful API并不是十全十美的。由于HTTP是应用层协议,本身比TCP慢一些;加之数据本身是自描述的文本格式,需要占用更多的带宽,因此相比于RPC,RESTful API的速度会稍慢一些。但是,速度可以通过技术手段弥补,例如HTTP/2、CDN、七层负载均衡等。在某些对速度要求不严苛的场景下,RESTful API带来的灵活性和伸缩性更具有价值。
(2)并不是所有业务场景都适合采用RESTful API。也不是在设计API时就一定要遵守所有规则,如何取舍还要看具体业务需求,适合的才是最好的,毕竟架构也是伴随业务的发展而不断演进的。
RPC和RESTful是目前两种主流的微服务之间的通信协议风格,两者各有利弊,分别适用于不同的场景。表6-2是这两种风格的主要技术指标对比。
表6-2 RPC与RESTful两种协议风格对比
RPC主要是基于TCP/IP的,而RESTful服务主要是基于HTTP的。HTTP是在传输层协议(TCP)之上的,由于RPC普遍采用二进制压缩的序列化参数,数据传输量方面会比REST风格的json(或者XML)小很多。所以总的来说,从运行效率来看,RPC当然是要更胜一筹。RESTful的优势主要体现在通用、开放、标准方面。两者的优缺点详细说明如下,以方便读者在实际应用项目中进行综合分析选择。
(1)RPC的优点。
(2)RPC的缺点。
(3)RESTful API的优点。
(4)RESTful API的缺点。
传统的观念认为RESTful API不具备RPC的很多特性,不能作为企业级应用广泛使用的API管理方式。实际上只要能够正确实施,RESTful API也可以具备RPC 框架的许多特性。
基于“服务发现”和“API网关”,RESTful API服务也可以实现统一的服务注册、服务发现,“API网关”可作为服务的统一出口,反向代理全部的底层服务,实现统一的安全控制和权限管理。
RESTful API直接使用HTTP作为应用层协议,实际上只要支持HTTP的任何工具都可以完成对RESTful API的调用,Web层也可以使用原生JavaScript或jQuery调用。
不可否认,传统的HTTP操作库一般只是支持HTTP隧道模式的调用方式,即把HTTP作为传输协议,针对媒体类型转换也没有特殊处理,只返回数据流或者文本数据,对资源的概念也没有特别设计,如Apache Http Components、jQuery等。
但是目前已经有很多专门针对RESTful API编写的客户端调用工具,如Java的Netflix Feign、Sping RestTemplate。Feign使用基于注解的形式定义客户端接口,框架会自动生成本地代理类,直接使用类似于本地方法调用的形式调用。Spring Cloud项目下的Spring Cloud Netflix Feign子项目结合了Spring Boot和Netflix Feign做了封装,更便于使用。
再者在前端领域,现在成熟的前端框架都提供了Resource插件,专门用于RESTful 接口的操作,如Vue下的vue-resource,AngularJS的ng-resource等,针对RESTful API的调用以及资源导航都做了很优秀的设计。
1.基于作者在阿里公司多年的大型项目架构设计实践经验,介绍云原生相关技术及产品
2.内容深入浅出,既有方法论详述也有技术原理深入分析
3.理论与实践并重,深入阐述云原生架构设计
4.紧贴技术趋势,把握主流技术发展
《企业级云原生架构:技术、服务与实践》较为全面、系统地介绍了云原生架构相关的方法论与技术产品,并结合作者多年的大型项目建设实施经验,阐述了分布式环境下面向云原生的架构设计最佳实践。本书主要分为4个部分,分别是云原生概述、云原生技术、云原生服务、云原生架构实践。本书兼顾理论、技术与实践,对从事相关行业的读者具有很好的学习指导意义。
《企业级云原生架构:技术、服务与实践》面向的读者对象为互联网行业的业务咨询师、系统架构师,以及相关领域的技术开发人员。
作者简介
刘景应,具有20年软件开发、架构设计以及解决方案咨询经验,目前就职于阿里云云原生应用平台,熟悉互联网企业的技术栈与开发管理模式,对云原生相关技术、产品、架构有较为全面的理解,是国内云原生技术的先行者和布道者,致力于推动云原生相关理念和技术在国内IT应用中的落地实践;具备丰富的大型实时在线应用系统的架构设计经验,曾负责了多个部委以及行业头部客户的核心业务系统的架构咨询与技术指导。