• 软件构件技术引论

https://gitee.com/oliverwyydp

https://github.com/oliverwy

1.1软件基本结构形式

作者刚接触计算机是上个世纪末,那个时代的软件主要以单机软件为主,如画图板、五笔打字等,当时学习使⽤计算机跟学习打字基本上是⼀个概念,那些不需要联⽹的单机软件就是最开始的软件。后来有的程序需要统⼀管理软件中使⽤的数据,所以就将保存数据的数据库统⼀存放在⼀台主机中,所有⽤户在需要数据时都要从主机获取,这时就分出了客户端和服务端,⽤户安装的软件叫客户端(Client),统⼀管理数据的主机中的软件就叫服务端(Server),这种结构就叫CS结构。再后来这种结构的服务端就不只是管理数据了,另外还可以处理⼀些业务逻辑,哪些业务放到客户端处理,哪些业务放到服务端处理就是见仁见智的问题了。业务放到服务端统⼀处理可以提供更好的安全性和稳定性⽽且升级⽐较容易,不过服务器的负担就增加了;业务放到客户端处理可以将负担分配到每个⽤户的机器上,从⽽可以节省服务器的资源,不过安全性和稳定性可能会有⼀些问题,⽽且升级也⽐较⿇烦,每个⽤户安装的客户端程序都需要升级。另外,受当时网络带宽的限制,为了节省⽹络资源,通过⽹络传输的数据应该尽量少。CS结构如图1-1所⽰。

 

 
 
 
 

CS结构的程序已经可以完成⽹络通信了,不过使⽤起来还是有点⿇烦,⾸先软件提供商需要同时开发客户端和服务端两套软件;其次每个⽤户在使⽤时都需要单独安装客户端软件,⽽且升级的时候也需要每个⽤户都进⾏升级。为了解决这个问题⽽设计了统⼀的客户端,⽽且默认安装在⽤户计算机⾥⾯,这就是我们计算机中的浏览器(Browser),⽽且⼀个浏览器可以访问所有同种类型的⽹站,当然它主要⽤作展⽰数据,具体业务处理是在不同的服务端进⾏的,这种结构就叫BS结构。BS结构除了提供了统⼀的客户端,还根据相应协议和标准提供通⽤的服务器程序,服务器程序统⼀处理数据连接、封装和解析等⼯作。BS结构如图1-2所⽰。

这就是软件的三⼤类型:单机类型、CS类型和BS类型。在这三种类型中,因为BS类型开发简单、使⽤⽅便⽽且功能强⼤,所以现在使⽤最⼴,当然并不是说BS结构是最好的,具体使⽤什么结构还需要根据实际的需求来决定,⽐如,现在我们计算机中的记事本、WPS Office以及压缩软件等都是单机软件,⽽它们使⽤得也⾮常⼴泛,另外BS结构虽然⽐CS结构在开发和使⽤上都简单,但是BS结构的灵活性和处理效率都不如CS结构,所以像QQ、⼤型游戏等软件使⽤的还是CS结构。

1.2隐藏在软件结构后面的标准

前⾯介绍的CSBS结构是现在流行的最基本的软件结构,不过即使这种最基础的结构的底层实现也并不简单,因为它需要通过网络(局域网和互联网)传输数据,⽽网络是⼀个错综复杂的⽹络,其中包含的节点数无法精确的界定,⽽且每两个节点之间的距离以及连接的路线都是不确定的,数据在传输的过程中还可能会丢失、被串改、被窃听,所以⾮常复杂。通过日常生活,我们知道凡是问题无论是简单还是复杂,都有对应的方法予以处理,通常对于复杂问题的处理⽅法是将其分解成多个简单的问题,然后通过解决每个简单问题,最终解决复杂问题。为了保证数据能在两个或多个点之间正确流动,于是出现两种网络模型标准:⼀种是标准的OSI参考模型,另⼀种是TCP/IP参考模型。这两个标准的思想就是:把整个数据传输的过程分层处理,每一层只解决一部分问题,学过计算机网络的同学肯定都知道,这两个模型中每一层是处理什么问题,它们的分层⽅式及对应关系如图1-3所⽰。

 

 
 

到这里很多人都能自然而然想起计算机网络上面的很多知识,这些东西我们都学过,好像到目前为止,我们没真正的写过这方面的代码,TCP/IP还能看到相关的代码,但是OSI真的没看到过,你们直觉没错,OSI参考模型尽管也是个标准,在实践中几乎看不到以其标准实现的应用,实际工程中,以TCP/IP参考模型为主:

网络接入层:主要是完成的是物理电器上的互联互通,完成两点:1、物理介质上的互联互通,电信号可以无阻碍的传送;2、电信号高低电平如何区分,也就是在数字电路中1和0分别用什么样的电压标识,提醒一下,并不一定高电压一定表示1。

网路互联层:这层的作用是各个节点的的表示,这一层是在传输层的数据包上加上相关站点的地址信息,这里包括目标地址和原地址,想想我们当初学IP协议的时候IP地址的组织方式,IP是一个分成四节的32位二进制数,这个时候在传输层数据报上加的不是我们通常意义上的192.168.X.X,而是多个完整的32位二进制。

传输层:网络中各个节点电器上都是互联互通的,显然在真实的网络中,两个节点之间的通路不会是一条,数据报在发送的时候肯定不会像你想象的那样仅仅沿着一条通路进行传递,那么就会有终节点会接收多个相同数据报的可能,而且接送的顺序肯定不是按照你发送的顺序到达的,这一层主要完成的工作就是数据的复现,按照发送端发送的顺序重组数据包,这个重组过程中会有一些列的控制差错的方法,控制数据报复原的方法,当发现数据包有问题,还会有通知源点重发的控制方法等等,总结起来,这一层的作用是做数据报质量控制的,保证数据报能在网络中正确传送。

应用层:这一层是就是应用间如何相互理解数据的一个协定,就像我们在看谍战片接头一样,他们之间交换信息肯定不会用大家都能听懂的语言来表述,他们在交流的时候肯定会按照一套约定进行信息交流,你要是仔细看就知道,每个团体之间的约定会不一样,显然对照我们的应用你也会发觉,每个应用对应的协议也不一样。

这种分层模型⾮常容易理解,比如我们在⽹上买东西,⾸先要确定⾃⼰所在的位置有相应的快递,这就相当于⽹络接⼊层,接入层就是提供物理上的互联互通的基础设施,然后需要告诉卖家地址,地址就相当于⽹际互联层,快递送货相当于传输层,最后我们收到货物之后拆包使⽤就相当于应⽤层。

对于⼴泛使⽤的东西就需要制定相应的标准,没有规矩不成⽅圆,如果都按⾃⼰的想法去做就乱套了。对⼀个⼩作坊来说,做事情可以⽐较随意,但是⼀个⼤型公司就需要有很多制度来规范做事情的流程了。由于⽹络传输应⽤⾮常⼴泛,所以需要⼤家都遵守的规矩,不过⽹络传输中的这些规矩并不是强制性的,所以不叫制度也不叫标准⽽叫协议,其实TCP/IP参考模型也可以看作⼀种协议族,TCP/IP模型中的⽹络接⼊层没有定义自己相应协议,⽹际互联层定义了IP协议,传输层给出了TCP协议规范,应⽤层给出了HTTP协议,应用层之上还衍生出了类似SOAPgRPC协议和相关的规范,如Java Web开发中使⽤的是Servlet标准。

数据传输的本质就是按照晶振震动周期或者其整数倍来传输代表0/1的⾼低电平,传输过程中最核⼼就是各种传输协议,对直接连接的硬件来说就是各种总线协议,对⽹络传输来说就是⽹络协议,如果将传输的协议弄明⽩了,那么也就掌握了传输的核⼼

我们这门课要讨论就是在一台计算机上的软件通过网络与另一台计算机上的软件进行通信的不同方式。我们将特别关注客户机如何连接服务器我这么说会让很多人疑惑,你前面不是提到软件的三种结构吗?BS结构不讨论了?BS结构难道也可以CS结构?尽管BS看似和C/S结构不完全相同,甚至后面讨论的很多方法中采用的软件结构看似和CS没有相同点,但本质上都可以是C/S架构,我们可以从数据交换的角度来分析,无论什么样的结构,本质上都是在不同的进程或者机器间交换信息,交换信息的过程中,无论什么样的应用,在每个交换时刻,都会有扮演提供信息的服务器,扮演获取信息的客户端,无论BS结构或者其他结构的应用分多少层,在两层交互信息的过程中都会有服务提供和服务调用者角色的划分,所有后期在本书中不会在严格区分CSBS,都广义的称为CS

1.3 Client/Server相关概念

客户端/服务器模型通过确定一方(服务器)必须先开始执行,并无限期地等待另一方(客户端)连接它来解决两个程序的通信的问题。

服务器:一个等待客户端传入通信请求的程序。当服务器接收到来自客户机的通信时,服务器向客户机提供一些有用的服务,并向客户机发送结果。

客户机:与服务器通信的程序,并使用服务器提供的服务。客户端需要一些服务器可以提供的服务。

客户端通常比服务器更容易构建,并且大多数的情况下,不需要特殊的系统特权来运行。服务器通常需要访问由操作系统提供并受操作系统保护的数据、例程和资源。因此,服务器通常需要特殊的系统特权。

服务器所关注的:

验证:验证客户端是否是它允许接入的客户端

授权:确定是否允许给定的客户端访问服务器提供的相应服务

提供服务

数据安全:确保客户端不能访问客户端不允许访问的数据,防止数据被盗

服务器通常有两种不同的版本:

无状态服务器:不保存关于正在与客户端进行交互的状态的任何信息

有状态服务器:保存关于正在与客户端进行交互的状态的信息

1.3.1 C/S信息传输流程

完成一次网络通信,大致要经过以下5个步骤。

1)客户端产生数据,存放于客户端应用的内存中,然后调用接口将自己内存中的数据发送/拷贝给操作系统内存。

2)客户端操作系统收到数据后,按照客户端应用指定的规则(即协议),调用网卡并发送数据。

3)网络传输数据。

4)服务端应用调用系统接口,想要将数据从操作系统内存拷贝到自己的内存中。

5)服务端操作系统收到指令后,使用与客户端相同的规则(即协议)从网卡读取数据,然后拷贝给服务端应用。

1.3.2 进程通信技术的演化

随着时间的推移,进程间通信技术发生了巨大的变化。各种各样的新技术不断涌现,以满足现代化的需求并提供更好、更高效的开发体验。因此,了解进程间通信技术是如何演化如何形成的就显得非常重要了。接下来介绍最常用的进程间通信技术。

  • SOCKETS(套接字)

TCP/IP协议通常放在⼀起来说,不过它们是两个不同的协议,所起的作⽤也不⼀样。IP协议是⽤来查找地址的,对应着⽹际互联层,TCP协议是⽤来规范传输规则的,对应着传输层。IP只负责找到地址,具体传输的⼯作交给TCP来完成,这就像快递送货⼀样,货单上填写地址的规则以及怎么根据填写的内容找到客户,这就相当于IP协议,⽽送货时要先打电话,然后将货物送过去,最后客户签收时要签字等就相当于TCP协议TCP/IP协议只是⼀套规则,并不能具体⼯作,就像是程序中的接口⼜⼀样,于是针对这个规范就出现了各个语言实现的API,⽽SocketTCP/IP协议的⼀个最早的实现。

早在1983年,UNIXBSD就定义了一个名为sockets(套接字)的应用程序接口用于实现TCP/IP协议。这最终成为了一个可移植操作系统接口(POSIX)规范。它们通常被称为BSD sockets(套接字)或POSIX sockets(套接字)。

Windows平台上也有对应sockets(套接字)的版本,叫做Winsock,它最初是基于POSIX sockets(套接字)的。然而,在包含文件、库函数的名称等方面有相当多的不同。根据MSDN(2016)描述,由于WindowsUNIX的系统不同,socket的实现上也存在一些差异。

在本书的后续章节中,我们基于POSIX sockets(套接字)讨论TCP /IP之上的协议和规范,因为它们是其他sockets(套接字)实现的基础。另外,我们关注sockets(套接字)是为了展示它们在现代的中间件技术方面的局限性,即sockets(套接字)要求使用其的程序员比现代中间件技术的程序员更了解网络细节和数据存储和传输的细节,通过这个例子,会让我们发现为什么会渴望使用现代的中间件通信技术

因为本书集中与Java语言,所以我们介绍介绍JavaSocket的⽤法,Java中的Socket可以分为普通SocketNioSocketAioSocket三种,后续会在后面的章节详细介绍基于Java NIONetty通信组件。

二、传统的RPC

在构建客户端服务器端应用程序方面,RPC一直很流行的进程间通信技术它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想。

借助RPC,客户端能够像调用本地方法那样远程调用某个方法的功能。早期有一些很流行的RPC实现,比如通用对象请求代理体系结构(common object request broker architectureCORBA)和Java 远程方法调用(remote method invocationRMI),它们都用来构建和连接服务或应用程序。但是,大多数传统的RPC实现极其复杂,因为它们构建在 TCP 这样的通信协议之上,而这会妨碍互操作性,并且它们还有大量的规范限制。

RPC 是一种技术思想而非一种规范或协议,目前常见RPC技术和框架有:

应用级的服务框架:阿里的 Dubbo/DubboxGoogle gRPCSpring Boot/Spring CloudFacebook ThriftGraphQLTwitter Finagle

远程通信协议:RMISocketSOAP(HTTP XML)REST(HTTP JSON)

通信框架:MINA Netty

三、SOAP

鉴于 CORBA 等传统RPC实现的局限性,简单对象访问协议(simple object access protocolSOAP)应运而生,并且得到了微软、IBM 等企业的大力推广。SOAP 是面向服务的架构(service-oriented architectureSOA)中的标准通信技术,用于在服务(在 SOA 中通常叫作 Web 服务)之间交换基于 XML 的结构化数据,并且能够基于任意的底层通信协议进行通信,其中最常用的协议是 HTTP。通过 SOAP,可以定义服务接口、服务的操作以及调用这些操作的 XML 消息格式。SOAP曾是一项非常流行的技术,但其消息格式的复杂性以及围绕 SOAP 所构建的各种规范的复杂性,妨碍了构建分布式应用程序的敏捷性。因此,在现代分布式应用程序开发中,SOAP Web 服务被认为是一种遗留技术。大多数现有的分布式应用程序采用 REST 架构风格,而非 SOAP

四、REST

描述性状态迁移(representational state transferREST)架构风格起源于 Roy Fielding 的博士论文。Fielding HTTP 规范的主要作者之一,也是 REST 架构风格的创始人。REST 是面向资源的架构(resource-oriented architectureROA)的基础,在这种架构中,需要将分布式应用程序建模为资源集合,访问这些资源的客户端可以变更这些资源的状态(创建、读取、更新或删除)。

REST 的通用实现是 HTTP,通过 HTTP,可以将 RESTful Web 应用程序建模为能够通过唯一标识符访问的资源集合。应用于资源的状态变更操作会采用 HTTP 动词(GETPOSTPUTDELETEPATCH 等)的形式,资源的状态会以文本的格式来表述,如 JSONXMLHTMLYAML 等。

实际上,通过 HTTP JSON 将应用程序构建为 REST 架构风格已成为构建微服务的标准方法。但是,随着微服务的数量及其网络交互的激增,RESTful 服务已经无法满足现代化的需求了。下面介绍 RESTful 服务的 3 个主要局限性,这些局限性妨碍了其作为消息协议在现代微服务应用程序中的运用。

1. 基于文本的低效消息协议

从本质上来讲,RESTful 服务建立在基于文本的传输协议(如 HTTP 1.x)之上,并且会使用人类可读的文本格式,如 JSON。但是,在进行服务与服务之间的通信时,通信双方都不需要这种人类可读的文本化格式,这时使用这种格式非常低效。

客户端应用程序(源)生成需要发送给服务器的二进制内容,然后需要将二进制结构转换成文本(如果使用 HTTP 1.x,就只能发送文本化消息),并通过网络以文本的形式(借助HTTP)发送到另一台机器上,这台机器需要在服务器端(目标)解析文本并将其转换回二进制结构。其实,我们也可以很轻松地发送映射服务和消费者业务逻辑的二进制内容,采用 JSON 格式主要是因为它是人类可读的,相对来说易于使用。这涉及工具选择问题,而不是二进制协议问题。

2. 应用程序之间缺乏强类型接口

随着越来越多的服务要通过网络进行交互,而且这些服务使用完全不同的语言来构建,缺乏明确定义和强类型的服务接口成了使用 RESTful 服务的主要阻碍。RESTful 中现有的各种服务定义技术(如 OpenAPI/Swagger 等)都是事后的补救措施,并没有与底层的架构风格或消息协议紧密集成在一起。

在构建这种分散的应用程序时,会遇到很多的不兼容、运行时错误和互操作等问题。例如,在开发 RESTful 服务时,应用程序之间并不需要共享服务定义和类型定义的信息。但是,在开发 RESTful 应用程序时,我们要么通过网络查看文本格式,要么使用第三方 API定义技术(如 OpenAPI)。因此,现在非常重要的任务就是拥有现代化的强类型服务定义技术以及框架,从而为多语言技术生成核心的服务器端代码和客户端代码。

3. REST 架构风格难以强制实施

REST 架构风格有很多好的实践,只有遵循这些实践,才能构建出真正的 RESTful 服务。但是,由于它们并没有作为实现协议(比如 HTTP)的一部分进行强制的要求,因此在实现阶段,这些实践很难实施。事实上,大多数自称 RESTful 的服务并没有遵循基础的REST 架构风格,也就是说,这些所谓的 RESTful 服务不过是通过网络公开的 HTTP 服务。因此,开发团队必须花费大量时间来维护 RESTful 服务的一致性和纯度。

鉴于进程间通信技术在构建现代云原生应用程序时所存在的这些限制,人们开始寻求更好的消息协议。

五、gRPC

长期以来,谷歌有一个名为 Stubby 的通用RPC框架,用来连接成千上万的微服务,这些微服务跨多个数据中心并且使用完全不同的技术来构建。Stubby 的核心RPC层每秒能处理数百亿次的互联网请求。Stubby 有许多很棒的特性,但无法标准化为业界通用的框架,这是因为它与谷歌内部的基础设施耦合得过于紧密。

2015 年,谷歌发布了开源RPC框架gRPC,这个RPC基础设施具有标准化、可通用和跨平台的特点,旨在提供类似 Stubby 的可扩展性、性能和功能,但它主要面向社区。在此之后,gRPC的受欢迎程度陡增,很多大型公司大规模采用了gRPC,如 NetflixSquareLyftDockerCoreOS 和思科。接着,gRPC加入了云原生计算基金会(Cloud Native Computing FoundationCNCF),这是最受欢迎的开源软件基金会之一,它致力于让云原生计算具备通用性和可持续性。gRPCCNCF生态系统项目中获得了巨大的发展动力。

gRPC是一种支持互联网规模的进程间通信技术,可以弥补传统进程间通信技术的大多数缺点。鉴于gRPC所带来的收益,越来越多的现代应用程序和服务器将其进程间通信协议替换成了gRPC。在面对如此众多的可选方案时,为什么选择gRPC作为通信协议呢?下面详细介绍gRPC的关键优势。

1.gRPC的优势

gRPC的优势是它被越来越多的人所采用的关键所在,主要有以下几个方面。

提供高效的进程间通信

gRPC没有使用 JSON XML 这样的文本化格式,而是使用一个基于 protocol buffers的二进制协议与gRPC服务和客户端通信。同时,gRPC HTTP/2 之上实现了 protocol buffers,从而能够更快地处理进程间通信。这样一来,gRPC就变成了最高效的进程间通信技术之一。

具有简单且定义良好的服务接口和模式

gRPC为应用程序开发提供了一种契约优先的方式。也就是说,首先必须定义服务接口,然后才能去处理实现细节。因此,与 RESTful 服务定义中的 OpenAPI/Swagger

SOAP Web 服务中的 WSDL 不同,gRPC提供了简单但一致、可靠且可扩展的应用程序开发体验。

属于强类型

因为使用 protocol buffers 来定义gRPC服务,所以gRPC服务契约清晰定义了应用程序间进行通信所使用的类型。这样一来,在构建跨多个团队和技术类型的云原生应用程序时,对于其所产生的大多数运行时错误和互操作错误,可以通过静态类型来克服,因此分布式应用程序的开发更加稳定。

支持多语言

gRPC支持多种编程语言。基于 protocol buffers 的服务定义是语言中立的。因此,我们可以选择任意一种语言,它们都能与现有的gRPC服务或客户端进行互操作。

支持双工流

gRPC在客户端和服务器端都提供了对流的原生支持,这些功能都被整合到了服务定义本身之中。因此,开发流服务或流客户端变得非常容易。与传统的 RESTful 服务消息风格相比,gRPC的关键优势就是能够同时构建传统的请求响应风格的消息以及客户端流和服务器端流。

具备内置的商业化特性

gRPC提供了对商业化特性的内置支持,如认证、加密、弹性(截止时间和超时)、元数据交换、压缩、负载均衡、服务发现等(第 5 章会讨论这些功能)。

与云原生生态系统进行了集成

gRPCCNCF的一部分,大多数现代框架和技术对gRPC提供了原生支持。例如,CNCF 下的很多项目(如 Envoy)支持使用gRPC作为通信协议。另外,对于横切性的特性,比如度量指标和监控,gRPC也得到了大多数工具的支持,比如使用 Prometheus来监控gRPC应用程序。

业已成熟并被广泛采用

通过在谷歌进行的大量实战测试,gRPC已发展成熟。许多大型科技公司采用了gRPC,如 SquareLyftNetflixDockerCoreOS 和思科等。与其他技术一样,gRPC也存在一定的劣势。在开发应用程序时,了解这些方面非常有用。

2.gRPC的劣势

下面介绍gRPC的一些劣势,在选择它来构建应用程序时,需要注意以下 3 点。

gRPC可能不太适合面向外部的服务

大多数的外部消费者可能对gRPCREST HTTP 等协议很陌生。因此,如果希望将应用程序或服务通过互联网暴露给外部客户端,gRPC可能不是最适合的协议。gRPC

服务具有契约驱动、强类型的特点,这可能会限制我们向外部暴露的服务的灵活性,同

时消费者的控制权会削弱很多(这与GraphQL协议有所不同)。按照设计,gRPC网关将是克服该问题的解决方案。

巨大的服务定义变更是复杂的开发流程

在现代的服务间通信场景中,模式修改很常见。如果出现巨大的gRPC服务定义变更,通常需要重新生成客户端代码和服务器端代码。这需要整合到现有的持续集成过程中,可能会让整个开发生命周期复杂化。但是,大多数gRPC服务定义的变更可以在不破坏服务契约的情况下完成,而且只要不引入破坏性的变更,gRPC就可以与使用不同版本proto 的客户端和服务器端进行交互。因此,大多数情况并不需要重新生成代码。

gRPC生态系统相对较小

与传统的 REST HTTP 等协议相比,gRPC的生态系统依然相对较小。浏览器和移动应用程序对gRPC的支持依然处于初级阶段。在开发应用程序时,必须注意这些方面的问题。由此可以看到,gRPC并不是适用于所有进程间通信需求的万能技术。相反,你需要评估业务场景和需求,选择适当的消息协议。如前所述,目前有很多新兴的进程间通信技术。因此,有一点非常重要,那就是了解如何将gRPC与在现代应用程序开发中流行的类似技术进行对比,从而为服务选择最合适的协议。

六、Thrift

ApacheThrift(以下简称Thrift)是与gRPC类似的RPC框架,最初由 Facebook 开发,后来被捐赠给了 Apache。它有自己的接口定义语言并提供了对多种编程语言的支持。Thrift可以在定义文件中定义数据类型和服务接口。Thrift编译器以服务定义作为输入,能够生成客端代码和服务器端代码。Thrift的传输层为网络 I/O 提供了抽象,并将Thrift从系统的其他组成部分中解耦出来,这意味着Thrift可以在任意传输实现上运行,如 TCPHTTP 等。

如果将ThriftgRPC进行对比,可以发现它们遵循相同的设计理念和使用目标。但是,两者之间也有一些重要的区别。

传输方面

相对于ThriftgRPC的倾向性更强,它为 HTTP/2 提供了一流的支持。gRPC基于HTTP/2 的实现充分利用了该协议的功能,从而实现了高效率并且能够支持像流这样的消息模式。

流方面

gRPC服务定义原生支持双向流(客户端和服务器端),它本身便是服务定义的一部分。

采用情况和社区资源方面

从采用情况来看,gRPC的势头似乎更好,它已围绕CNCF项目成功构建了一个良好的生态系统。同时,gRPC的社区资源非常丰富,比如良好的文档、外部的演讲以及示例。因此,相对于Thrift,采用gRPC会更顺利一些。

性能方面

虽然目前还没有gRPCThrift对比的官方结果,但一些在线资源对比了两者的性能,结果显示Thrift的数据表现更好。然而,gRPC的绝大多数发布版本经过了大量的性能测试。因此,性能问题不太可能是选择Thrift而非gRPC的决定性因素。同时,一些其他RPC框架提供了类似的功能,但不管怎样,gRPC是目前最标准、最具交互性和采用范围最广的RPC技术,处于领先地位

七、 GraphQL

GraphQL是另一项越来越流行的进程间通信技术,该项目由Facebook发起并通过开源进行了标准化。它是一门针对 API 的查询语言,并且是基于既有数据满足这些查询的运行时。GraphQL为传统的客户端–服务器端通信提供了一种完全不同的方法,该方法允许客户端定义希望获得的数据、获取数据的方式以及数据格式。gRPC则有针对远程方法的特定契约,借此实现客户端和服务器端之间的通信。

GraphQL更适合面向外部的服务或API,它们被直接暴露给消费者。在这种情况下,消费者需要对来自服务器端的数据有更多的控制权。以在线零售应用程序场景为例,假设ProductInfo 服务的消费者只需要关于商品的特定信息,而不是商品属性的完整集合,而且他们希望能有一种方法来指定想要的信息,那么我们可以借助 GraphQL来建模一个服务,允许消费者使用 GraphQL查询语言来查询服务并获取想要的信息。在 GraphQLgRPC的大多数使用场景中,GraphQL用于面向外部的服务或 API,而支撑API 的内部服务则使用gRPC来实现。

1.4 Socket进程通信技术演示(Java Socket

因为这本书是面向熟悉Java语言写的,故本书的所有例子都以Java语言实现。前面讲过,Java中的Socket可以分为普通SocketNioSocketNio2(权且称之为AioSocket)三种。这里我们演示的是普通Socket,前面已经阐述过我们演示sockets(套接字)是为了展示它们在现代的中间件技术方面的局限性,即sockets(套接字)要求使用其的程序员比现代中间件技术的程序员更了解网络细节和数据存储和传输的细节,通过这个例子,会让我们发现为什么会渴望使用现代的中间件通信技术

 Java中的⽹络通信是通过Socket实现的,Socket分为ServerSocketSocket两⼤类,ServerSocket⽤于服务端,可以通过accept⽅法监听请求,监听到请求后返回SocketSocket⽤于具体完成数据传输,客户端直接使⽤Socket发起请求并传输数据。

ServerSocket的使⽤可以分为三步:

  1. 创建ServerSocketServerSocket的构造⽅法⼀共有5个,⽤起来最⽅便的是Server-Socketint port),只需要⼀个port(端⼜号)就可以了。你可以尝试查看ServerSocket的代码。

ServerSocket(SocketImpl impl) 

public ServerSocket() throws IOException 

public ServerSocket(int port) throws IOException 

public ServerSocket(int port, int backlog) throws IOException 

public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException 

2)调⽤创建出来的ServerSocketaccept⽅法进⾏监听。accept⽅法是阻塞⽅法,也就是说调⽤accept⽅法后程序会停下来等待连接请求,在接收到请求之前程序将不会往下⾛,当接收到请求后accept⽅法会返回⼀个Socket

3)使⽤accept⽅法返回的Socket与客户端进⾏通信。

下⾯ServerSocket简单的使⽤⽰例。接收到数据原样返给客户端代码如下:

代码1-1

import java.io.*;

import java.net.ServerSocket;

import java.net.Socket;

public class Server {

public static void main(String args[]) {

try {

// 创建⼀个ServerSocket监听8080端⼝

ServerSocket server = new ServerSocket(8080);

// 等待请求

Socket socket = server.accept();

// 接收到请求后使用socket进⾏通信,创建BufferedReader用于读取数据,

BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));

String line = is.readLine();

System.out.println("我是服务器,收到来自客户端的数据: " + line);

// 创建PrintWriter,用于发送数据

PrintWriter pw = new PrintWriter(socket.getOutputStream());

pw.println("Hi,客户端你好,我收到你的数据了,原样返回: " + line);

pw.flush();

// 关闭资源

pw.close();

is.close();

socket.close();

server.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

在上⾯的Server⾥⾯,⾸先创建了ServerSocket,然后调⽤accept等待请求,当接收到请求后,⽤返回的Socket创建ReaderWriter来接收和发送数据,Reader接收到数据后保存到line,然后打印到控制台,再将数据发送到client,告诉client接收到的是什么数据,功能⾮常简单。

然后再来看客户端Socket的⽤法。Socket的使⽤也⼀样,⾸先创建⼀个SocketSocket的构造⽅法⾮常多,这⾥⽤的是SocketString host,int port),把⽬标主机的地址和端⼜号传⼊即可,Socket创建的过程就会跟服务端建⽴连接,创建完Socket后,再⽤其创建WriterReader来传输数据,数据传输完成后释放资源关闭连接就可以了,见代码1-2

代码1-2

import java.io.*;

import java.net.Socket;

public class Client {

public static void main(String args[]) {

String msg = "软件构件技术课程例子-发送测试数据,兔子几条腿?";

try {

// 创建⼀个Socket,跟本机的8080端⼝连接

Socket socket = new Socket("127.0.0.1", 8080);

// 使用Socket创建PrintWriter和BufferedReader进⾏读写数据

PrintWriter pw = new PrintWriter(socket.getOutputStream());

BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));

// 发送数据

pw.println(msg);

pw.flush();

// 接收数据

String line = is.readLine();

System.out.println("从服务器返回 server的数据: " + line);

// 关闭资源

pw.close();

is.close();

socket.close();

} catch (Exception e) {

e.printStackTrace();

}

}

}

如果你不怕麻烦,你打开ServerSocket类,你发觉有一些列的函数等着熟悉,想要完美使用java socket处理业务逻辑还有个大问题,

这个示例代码的功能也⾮常简单,启动后⾃动将msg发送给服务端,然后再接收服务端返回的数据并打印到控制台,最后释放资源关闭连接。

先启动Server然后启动Client就可以完成⼀次通信。我们这⾥只是为了说明原理,所以功能⾮常简单,最后Server端的控制台输出我是服务器,收到来自客户端的数据: 软件构件技术课程例子-发送测试数据,兔子几条腿?Client端控制台输出从服务器返回 server的数据: Hi,客户端你好,我收到你的数据了,原样返回: 软件构件技术课程例子-发送测试数据,兔子几条腿?

这里仅仅是发送一条简单的信息,服务器服务完结束,显然这个与实际应用相去甚远,现实中服务器是一直在运行的,可以持续的给不同的客户端提供服务,显然为了实现这个机制我们就要利用到的多线程,当收到连接请求的时候,开一个独立服务线程给客户端,如果多个用户同时访问服务,资源竞争是不可避免,现在我们也要实现一套机制来控制资源,然后在仔细想还想还有安全问题,服务质量保证问题,显然利用原始的socket实现进程间的通信,通信服务过程中的每个细节我们都要我们自己写代码控制,这还不是最难受的,最让人崩溃的是,如果多个异构平台,异构应用交换信息,使用socket实现进程间通信需要处理的更多。

1.5 开放网络计算(ONC)远程过程调用(rpc)的出现

sockets(套接字)编程的复杂性使我们自然而然的就会想到,我们能不能制定一套标准和或者协议来帮我们完成这些控制,于是就出现了早期的开放网络计算(ONC)远程过程调用(rpc)这样的技术sockets(套接字)在一端显式地向网络写入,在另一端显式地从网络读取,因此它们不执行本机之外的函数调用。对于RPC(例如ONC RPC),其思想是您对待跨网络的调用就像对待对本地过程的调用一样,因此,这个想法的一部分是:与远程计算机之间的数据传输是使用(相当标准的)过程调用来完成的,这个思想的另一部分是:被调用的过程和过程的调用者认为它们位于同一台计算机上,于是位置透明性这个概念就自然而然的出现了

rpc是面向功能的,因为它们是在面向对象方法学广泛应用于软件工业之前出现的。面向功能意味着他们关注的是过程调用——数据是独立的,但是,请注意,使用分布式对象的各种技术的工作方式通常也称为远程过程调用,对象包含数据和访问该数据的过程。但是,如果一个对象位于与您的客户机不同的计算机上,那么您的客户机必须调用(该对象上的)远程过程来执行适当的任务。

ONC rpc是一种著名的基于rpc的技术,最初来自SunMicrosystems,可以追溯到20世纪80年代中期。在ONC rpc的技术体系中,要在网络上调用的远程过程被定义在一个以.x为扩展名的文件中,例如,mycall .x。然后使用一个名为rpcgen的程序将这个.x文件转换成C语言中的处理程序,至少会生成一个为客户端创建的存根文件(mycalls_clnt.c)和一个为服务器端创建的框架文件(mycalls_svc.c),服务器本身位于mycalls_svc.c中,编译之后,将mycalls_svc.c文件链接到远程过程调用定义,并将mycalls_svc.c链接到客户机代码。存根和骨架提供必要的网络连接,这样,应用程序编程人员就不必为网络的数据传输头疼了,远程过程定义和客户端代码中需要一些样板代码来完成这个工作。

我们刚才所做的,也就是让你们大致了解rpc是如何工作的,我们不会深入早期的技术,有很多更现代的技术需要我们花大量的时间,在这里浪费时间会得不偿失在我们离开这一节之前,让我们简要地看看同步通信的概念,rpc通常使用同步通信。同步通信的另一种选择是异步通信,在异步通信中,客户端不希望在服务器完成自己的工作之前等待。图1.5中用一个UML图来说明这一点,在图中,客户机调用服务器,不是阻塞本身和等待服务器的响应,而是继续它的业务,当服务器接收到来自客户机的调用时,服务器做客户端要求的工作,当服务器完成时,它通知客户机。

1.4远程过程调用中的同步通信。

1.5回调异步通信。

顺便说一下,请注意图1.3中的箭头与图1.2中的箭头不同。

1.6 一些必须澄清的概念

1.6.1 什么是分布式面向对象组件?

因此,随着业界在20世纪80年代末和90年代初转向使用面向对象方法学,出现了分布式对象相互通信的需求。在面向对象方法中中,数据和过程调用是封装在对象内的。

一般来说,基于组件的软件工程包括将松散耦合的组件组合到系统中事实上,关于基于组件的系统和面向对象的系统之间是否存在差异一直存在争议一般的观点是,面向对象系统中的对象关注于对真实世界的情况进行建模,而基于组件的系统中的组件只关注于将现有组件组合到系统中。(但是现有的组件从何而来呢?)

Gomaa(2011)对分布式组件的定义如下:“分布式组件是具有定义良好的接口的并发对象,接口是分布和部署的逻辑单元。设计良好的组件能够在最初开发它的应用程序之外的其他应用程序中重用。他进一步指出,因为组件可以分配到地理分布环境中的不同节点,所以组件之间的所有通信必须仅限于消息通信。

在所有的业务场景下,我们使用分布式对象组件的情况是这样的:一个或多个服务器对象位于与客户机对象不同的计算机上,客户机包含一个本地代理对象(通常是以前以或多或少自动化的方式生成的),它知道如何调用远程对象上的过程,而应用程序编程人员无需显式地知道如何向网络写入或从网络读取,这个代理对象表示客户机的远程对象,客户机认为它正在对代理对象执行本地过程调用,而实际上代理对象将本地过程调用转换为网络,并将其传递到位于服务器上的远程对象上的该过程。

请注意,我们在本书中讨论的分布式对象中间件同时处理同步和异步通信。

1.6.2 什么是面向消息的中间件?

面向消息的中间件是中间件的一种形式,它的目的是提供一个客户端和服务器之间的中间人,这样客户端和服务器都和中间层交互,客户端没有直接对话的服务器和服务器没有直接对话的客户端,中间层使用消息队列在消息生产者和消息接收者之间存储消息,消息中间件一般是异步的,因此消息生产者和消息使用者独立运行,例如,如果客户端是消息生产者,服务器是消息使用者,那么客户机独立于服务器运行,服务器独立于客户机运行。

在实践中,如果客户机希望服务器接收到一条消息,那么客户机将把消息发送给MOMMOM很可能会将消息放到队列中最终,服务器有时间接收此消息MOM将向服务器发送此消息。关于它的工作方式有一些变化,我们将在后面讨论这些变化(push模型、pull模型、发布/订阅等等)

1.6带有消息队列的消息中间件。

MOM还可以做额外的处理,例如,如果MOM担心服务质量,它可能会将高优先级的消息放在队列前面(或者可能在不同的优先级级别上维护多个队列),或者,服务器可以告诉服务器不想接收的特定类型的消息,在这种情况下,MOM可以丢弃这些不需要的消息,而不用麻烦服务器处理它们。

1.6.3 什么是面向服务的体系结构?

根据Open Group(一个开发开放的、与供应商无关的信息技术(IT)标准的全球联盟),面向服务的体系结构是一种支持面向服务的体系结构风格面向服务是根据服务的结果以及如何开发和组合服务的一种思维方式。在这个定义中,服务是一种可重复的业务活动,可以逻辑地表示,Open Group给出了示例:“检查客户信用”和“提供天气数据”。此外,服务是自包含的,可以由其他服务组成,服务的使用者将服务视为黑盒因此,更通俗地说,以面向服务的体系结构形式出现的应用程序将调用服务来执行特定的任务。应用程序可能调用几个不同的服务来完成部分任务,然后提供一些粘合代码和一些额外的处理,实现任务的全部需求。如图1.5所示。

1.7面向服务的体系结构图。

但是,到目前为止,您可能还看不到服务和库函数之间的区别。

因此,需要进一步区分服务和库函数,服务可能位于与应用程序不同的计算机上也可能是每个单独的服务位于不同的计算机上,与其他服务不同。每个服务都有定义良好的接口,这些接口应该有良好的文档记录。由于这些服务是独立的,并且可能被不同的应用程序重用,因此这些服务与任何调用的应用程序的关系是松散耦合,并且服务彼此松散耦合是非常重要的特征

在面向服务的体系结构中,实现服务的一种常见方法是通过使用web服务,然而,使用分布式对象组件实现面向服务的体系结构也是可能的。使用分布式对象组件实现面向服务的体系结构的困难主要在于:(1)防火墙的问题,以及(2)与使用Web服务相比,分布式对象计算实现的面向服务的体系结构的耦合可能会很紧密。

我们知道的一些分布式对象技术,如CORBA,穿越防火墙工作有困难的。但是,如果您有一个应用程序是在一个公司内部运行,在防火墙后,面那么分布式对象实现就是实现面向服务的体系结构的一种合理方式。

分布式对象往往比web服务耦合得更紧密,例如,在分布式对象体系结构中,客户端与服务器对象的特定实例进行对话是很常见的,这比web服务的耦合要紧密得多。但是一些分布式对象技术将允许无状态地访问对象池(例如无状态会话企业Java bean)。类似地,分布式对象通常存储(正在进行的)通信(有状态的)状态,尽管面向服务的体系结构的一般定义并没有严格禁止有状态服务,但这是一种不常见的实现——通常在面向服务的体系结构中,人们希望单个服务不存储状态,因此可以在不同的应用程序之间轻松地进行重用。

因此,基于这些原因,面向服务的体系结构通常使用web服务技术实现。

1.6.4 什么是WEB服务?

Web服务是通常希望利用万维网提供应用程序服务的应用程序。(请注意,您可能有一个web服务和客户机都只在您自己的计算机上运行,但他们使用的也是万维网的技术。)

在这个讨论中,我们将万维网与因特网区分开来。internet使用TCP/IP协议进行连接

万维网是使用超文本(可单击)链接连接的网页形式的信息集合,这些web页面使用HTTP协议相互连接,并连接到web浏览器(ChromeMozilla Firefox)HTTP协议运行在TCP/IP协议之上(HTTP协议的讨论请参见第四章)

网页由格式化的文本信息以及图像、视频等组成。web页面通常以HTML类型的格式存储。所以在万维网上基本上可以这么认为,web服务就是一个web页面,它和万维网上的任何其他web页面一样,都可以使用HTTP协议消息进行访问,但是,在这种情况下,web页面执行的是客户机需要的一些服务。

有两种(体系结构风格)技术通常用于web服务使用WSDLSOAP技术实现的Non RESTful web服务(这个你们知道就行了,现在已经过时了,不在讲解)使用类似远程过程调用类型接口(HTTP视为独立的底层协议层),以及直接使用HTTPRESTful web服务(我们将在后面会讨论)

1.3.6 什么是云计算?

IBM(2016)对云计算的定义如下:

云计算,通常简称为“云”,是按需提供计算资源-从应用程序到数据中心-在互联网上按需付费的交付。

所以云的想法是,与其自己做计算,自己把数据存储在电脑上,不如雇佣一家公司来做计算,然后把数据存储在他们的大型电脑服务器上,你可以通过网络访问这些服务器,完成自己希望的业务逻辑,这就是公共云的概念。

公司还可以实现自己的私有云。也就是说,公司可以购买自己的大型服务器,而不是在员工的办公桌上进行计算和数据存储,然后员工进行计算并将数据存储在这些服务器上。混合云也是可能的,公司的部分计算由单独的云公司提供,部分由内部完成。

云计算有三种不同的模式:

  1. 基础架构即服务(IaaS)
  2. 平台即服务(PaaS)
  3. 软件即服务(SaaS)

通过IaaS,您或您的公司可以为计算资源向云提供商付费。您提供自己的操作系统和应用程序软件。

使用PaaS,您或您的公司将向云提供商支付费用,以获得该公司提供的环境,该环境提供开发和运行应用程序所需的一切。这个环境包括操作系统、开发工具、网站托管等等。

通过SaaS,您或您的公司可以向云提供商支付使用其软件应用程序的费用。您可以使用此软件应用程序为自己做适当的工作。

运行在云上的应用程序通常采用面向服务的体系结构。我们将在第9章中讨论这是如何工作的。

1.7 本书组织

本书以Java语言为实现语言,我们前面的Java课程关于Java的高级特性几乎没有讲到,

第二部分重点介绍Java的高级特性,反射、依赖注入和注解,之后会使用反射依赖注入和注解的知识写一个简单的ORM中间件模型。

第三部分主要讲解HTTP协议,最后会会用原生Java socket实现一个简易的HTTP服务器

第四部分主要讲解时下流行的Restful web,分别使用原生的Ajax实现,Servlet实现和Spring restful实现

第五部分讲解当下流行的RPC基础技术,Java NIO和使用NIO机制构架的通信框架Netty,并使用Netty构建tomcat和实现一个自己的RPC框架

第六部分讲解时下正在流行的gRPC框架。

第七部分缓存中间件和消息中间件。


0 条 查看最新 评论

没有评论
暂时无法发表评论