架构风格与基于网络的软件架构设计

架构风格与基于网络的软件架构设计

如今许多服务都采用了 RESTful API, 而 REST 这一架构风格,最早即来源于 Roy Thomas Fielding 的博士论文 Architectural Styles and
the Design of Network-based Software Architectures
,本文即是阅读此文后的总结,并结合论文给出大量实例。通常人们都是因为 RESTful 风格才提到这篇论文,有的人甚至只读涉及 REST 的那章。但是我读完论文后,感觉最大的收获不是 REST 这一架构风格,而是为基于网络的软件设计架构时,应该考虑哪些因素,不同的因素会导致哪些不同的结果。因此,本文将重点对基于网络的软件架构进行介绍,而非介绍 REST 本身

背景

首先介绍一下作者,Roy Thomas Fielding。

Roy Thomas Fielding

Fielding 博士是 HTTP/1.1 规范的主要制定者,并且深度参与了 URI, HTML 规范的设计。同时是 Apache HTTP Server 项目的联合创始人,Apache 软件基金会的联合创始人,并在成立前三年担任软件基金会主席。Fielding 博士的这篇博士论文,是 Web 发展史上极其重要的文献,奠定了现代 Web 架构。可以看出,此人不仅理论功底扎实,还亲自写代码践行自己的理论。

Fielding 博士之所以写这篇论文,也与 Web 早期的发展状况有关。早期的 Web 架构,基于一些基本的原则,例如简单性,分离关注点,通用性,但是缺少对架构的精确描述。Web 早期只在政府及研究机构部署,但随着公众及商业公司的介入,Web 的规模在指数级增长,但早期的 Web 架构已经难以支撑这样的规模。因此,当时需要现代 Web 架构,来支持 Web 规模的极速增长,同时,也要避免新架构的引入,破坏了早期 Web 架构中一些良好的特性。因此,作者在论文中提出了 REST 架构风格,来指导现代 Web 架构的设计。这就是这篇论文出现的主要背景。

软件架构

软件架构元素

我们经常听别人谈起 “软件架构”,“系统架构”,“架构师”,这样的名词。那么,到底什么是软件架构呢?Fielding 认为,软件架构主要由三部分元素组成:数据元素,连接元素,处理元素。

  • 数据元素是指系统中被使用,被转化的信息,例如 HTML 文档,JPEG 图片等。
  • 连接元素是将系统中不同部分连接起来的元素,例如我们常用的一些用于组件和组件之前网络连接的库,HttpClient, OkHttp, gRPC 等。
  • 处理元素是指对数据进行转化的元素,例如 HTTP 服务器 (Apache, Nginx), 网关,代理,浏览器等。

软件架构本身是对系统运行时的一种抽象。一方面,系统存在多个层次的抽象,每层都有自己的架构,另一方面,系统又由多个操作阶段构成,每个阶段都有自己的架构。

那么,什么是多个层次的抽象呢?例如,你的公司有一款面向用户的移动应用。从上层的抽象来看,这款应用由客户端及服务端构成。在这一层次上,处理元素有客户端,服务端,连接元素是用于在客户端与服务器传送数据的组件,数据元素即是在客户端与服务端之间传递的信息。

cs

如果你把抽象层次下降一点,就能在整个系统中发现更多的架构元素。例如,把客户端抽象层次下降一些,会发现客户端有 Android 和 iOS 之分。

ios_android_server

同理,服务端将抽象层次再下降一点,就能识别出更多的处理元素,例如,用于负载均衡的处理元素。

ios_andoid_multi_server

从上面的例子可以看出,从不同的抽象层次观察,可以得到不同的架构。另一方面,对同一系统而言,在不同操作阶段,也有不同的架构,这是什么意思呢?

对一个系统而言,都有初始化,提供服务,停止等阶段。在不同阶段,都有着不同的数据元素,处理元素。

arch-phase-open

例如,在初始化阶段,如果我们使用了 Spring Framework, 那么 Spring 框架会根据我们的配置,生成各种对象,这时候,数据元素是我们的配置,处理元素是 Spring 框架。但是到了服务阶段,各种用于处理的元素已经生成,配置已经不再是此时的数据元素,因为他们已经内化到处理元素中了。此时的数据元素,有从客户端发来的请求中的数据,有我们服务端的业务数据。此时的处理元素,是服务端的各种模块,例如用于搜索的模块,用于渲染的模块。

软件架构配置

从上一节我们了解到,软件架构由数据元素,连接元素,处理元素构成。但是我们描述一个软件架构时,除了描述软件架构由什么元素组成,还需要描述这些元素之间的关系。软件架构配置(Configurations),就是用于描述系统运行时,数据元素,连接元素和处理元素之间关系的概念。那么,这种 “关系” 又是指什么呢?我们可以将其理解为对元素之间的一种约束。

软件架构属性

我们设计软件架构时,在选择了数据元素,连接元素,处理元素,并且确定了他们之间的关系后,系统就会体现出一系列性质。这些性质,就是软件架构属性。

软件架构属性包括功能属性和非功能属性。功能属性表示系统是否完成了需要的功能,而非功能属性则代表了系统除了完成功能之外,还具有的一些性质,例如可扩展性,可重用性等等。

软件架构风格

一个软件架构由不同元素构成,元素之间有各种各样的约束,而软件架构风格就是指一组协作的架构约束。有了架构风格的概念,我们就可以方便地比较不同的软件架构。当我们谈到某个系统是某种架构风格时,就代表了这个系统的架构背后有一系列架构约束。

说了这么多概念,他们之间到底有什么关系呢?对我们设计系统又有什么用呢?里面有一条重要的逻辑链条,就是系统需求,约束与属性之间的关系。我们设计任何一个系统,都是要满足特定的需求,除了特定功能需求之外,有的系统需要满足高性能,有的系统需要满足高可用,有的系统需要经常进行灵活的扩展。那么,怎么设计系统才能达到这种需求呢? 那就是对系统进行一些特定的约束。特定的约束会导致特定的属性,特定的属性又会满足特定的需求。

因此,架构风格,即一组约束,导致了一组架构属性,这组架构属性构成了系统需求的一个超集。

所以,要设计好系统,就要正确地识别出,系统到底需要满足哪些需求,当需求不能都满足时,优先满足哪些。然后要了解如何对系统进行划分,如何确定组件之间的关系,有哪些约束的手段,这些约束,又会导致哪些属性。这也是论文后面会仔细探讨的问题。

总的来说,从抽象的角度看,架构风格是对架构的一种抽象,一种架构风格对应多种架构。而架构本身是对系统的抽象,同一架构对应着多种不同的系统。而这篇论文,主要探讨的,就是基于网络的应用架构以及架构风格。

基于网络的应用架构

这一节介绍基于网络的应用架构,主要解决三个问题:

  • 基于网络的应用架构是指什么
  • 如何评估基于网络的应用架构
  • 基于网络的应用架构中,重要的架构属性有哪些

什么是基于网络的应用架构

之前,我们已经介绍了软件架构的概念,基于网络的应用架构是在软件架构的基础上,施加了一个约束,即组件之间的通信限于消息传递。论文中,还特别说明了基于网络的应用和分布式系统的区别。一般而言,我们在使用分布式系统时,就像使用一个集中式的系统一样,网络的存在对用户来说是透明的。而基于网络的应用,“网络”的存在无需表现为对用户透明的形式,有时候还要用户感知到“网络”的存在,特别是“网络”意味着一定的性能开销时。

如何评估基于网络的应用架构

那么,我们如何评估基于网络的应用架构呢?就是从根本上讲,评估一个应用架构,就不看这个架构是否满足了系统的需求。之前我们讲系统属性的时候说过,系统属性分功能属性和非功能属性。相应地,这里看架构是否满足系统的需求,也要看是否满足了功能需求,是否满足了非功能需求。功能需求和我们的业务相关,而非功能需求,就要使用架构风格来评估。

我们知道,架构风格代表了一组架构约束,用架构风格评估架构,就是看这种约束,是否能使系统的需求得到满足。

evaluate-arch

在上图中,我们的系统有一组架构约束,分别是

  • 缓存
  • 无状态
  • 统一接口

同时,我们还有一组系统需要的属性,即系统的需求,分别是

  • 性能
  • 简单性
  • 可靠性

每种架构约束,会在不同程度上影响某种属性。这样,我们知道了系统的架构风格之后,就能评估架构是否满足了系统的需求。

基于网络的应用架构中的重要属性

下面,我们就介绍在基于网络的应用架构中,重要的属性有哪些,这也是我们在设计系统时,要思考,我们的系统,最终要满足哪些属性。如果我们连有哪些属性都不知道,又怎么设计一个系统?

这里主要包括的属性有:性能,可伸缩性,简单性,可修改性,可见性,可移植性,可靠性。注意,我们这里谈论这些属性性,关注的点,都在 “基于网络” 的环境下。

性能(Performance)

我们首先要讲的一个系统属性,就是性能。我们常说“性能优化”,那么,我们到底在优化什么?性能本身又由什么决定?

ACM Queue 上有一篇文章,Thinking Clearly about Performance,里面提到,多数时候,我们提到性能时,指的都是计算机软件完成某种任务所需要的时间,也有的时候,性能是指“吞吐”,即一定时间间隔内,完成的任务数。这和我们常说的,“响应时间”, “QPS” 等是一致的。这里,我们先讲性能的决定因素有哪些,然后再讲性能度量问题。

性能的决定因素,主要有下面几个方面:

  • 应用需求
  • 交互风格
  • 架构
  • 每个组件的实现

决定性能的,首先是应用的需求。假如,你的应用是用来下载数据的,看视频的,还是实时交易的?不用类型的应用,能达到的性能水平是不一样的,比较不同类型应用的性能指标,也是没有什么意义的。此外,决定性能的还有交互风格,你的系统中,组件之间的通信是基于 TCP 还是 UDP,达到的性能水平也会有所不同。另外,架构的设计也影响系统的性能,例如子系统是如何划分的,连接元素用的什么,有没有使用缓存,组件之间数据传输的格式是什么,这些都会对系统性能造成影响。最后,组件的实现也会影响性能,即使你什么都设计好了,一个糟糕的实现,还是会对性能造成致使打击。

接下来,我们讲如何度量性能,我们谈性能时,通常谈到的就是各种性能指标,这些指标就是通过不同形式的度量得到的。在基于网络的环境下,对性能的度量主要分三个方面:

  • 网络性能
  • 用户可察觉性能
  • 网络使用效率

对网络性能的度量也分为多种。吞吐是指组件之间信息转移的速率;负载(overhead) 是指跨网络传输带来的某种额外的负担,这种“负担”可能是时间,也可能是内存,带宽等等,负载又分初始负载每次交互的负载,在组件之间通讯时,我们要确定,通讯是每次都要建立连接,再传输数据么,还是只建立一次连接,后面的多次传输可以复用这个连接;带宽是指特定网络上最大吞吐量;可用带宽是指真正可以被系统使用的那部分带宽。

用户可察觉的性能,又分为两个方面,一是延迟,即初次请求到首次响应的时间,二是完成时间,即完成一个应用动作所需要的时间。我们看下 Chrome 对一次网络请求时间的划分就能明白这个概念:

user_feel_perf

从 Queueing 到 Wariting(TTFB) 即是初次请求到首次响应的时间,其中 TTFB 的含义就是 (Time to first byte),即首字节响应时间。而完成时间,要等到 Content Download,即所有内容下载完成后,才结束。区分延迟和完成时间是很重要的,在其它条件都相同的条件下,页面是等到所有元素下载完成后,用户才能看到内容并且交互,还是下载部分内容后,用户就可以看到并交互了。这两种形式对用户而言是完全不同的感受,虽然他们的完成时间相同。

关于网络使用效率,作者给我们的忠告是

最佳的应用性能是通过不使用网络而获得的

这就告诉我们,能不使用网络的时候,就不要使用网络。知乎上有一个问题,”大公司里怎样开发和部署前端代码?”, 其中张云龙讲的 前端静态资源部署方案 就是对如何减少网络使用最好的解读。从最初没有缓存(每次请求都要通过网络获取数据),到使用 304 协商缓存(通过网络询问内容是否有变化,没变化就不需要下载资源),再到使用内容摘要精确控制缓存(无需通过网络询问资源是否有变化),步步减少对网络的使用,从而提高了性能,有兴趣可以移步张云龙的答案

另外,在服务端的设计中,我们常会使用 Redis 来作缓存,提高数据访问速度,但是我们也要想下,如果数据可以放在本地内存中,是不是可以减少对网络的使用,从而提高系统的性能和稳定性呢?

性能的问题,是个很宏大的问题,在今后的博文中,我们会专门讨论这个话题。

可伸缩性(Scalability)

可伸缩性是指架构支持大量组件或者大量的组件之间交互的能力。例如,我们的系统用于处理客户端的请求。当请求量太大,系统支持不了如此大量的请求时,通常我们会如何做呢?一般通过水平扩展或者垂直扩展的方式,提高系统的处理能力。水平扩展,就是通过增加服务实例个数的方式提升处理能力,垂直扩展通过增加单个实例可以使用的资源提升系统的处理能力,例如增加 CPU 核数,增加内存,增加存储空间,机械硬盘换 SSD等方式。

系统的可伸缩性就是指,我们是否可以很容易地通过水平扩展或者垂直扩展,增加系统的处理能力。例如,如果我们可以很容易地提高单核 CPU 的性能(垂直扩展),那对我们多核的需求也就没那么强烈了,现实是单核 CPU 性能很难再提高,所以现在已经是多核时代,人们也越来越重视系统的并发能力(水平扩展)。又例如,现在大量使用的各种 NoSQL 数据库,其中很重要的原因之一,就是水平扩展性,单个数据库实例的处理能力毕竟有限,如果能很方便地通过加数据库实例的方式增加系统的处理能力,那系统的可伸缩性就比较强。

简单性

简单性是指系统是否正确地使用了分离关注点的原则,对功能进行了划分,使得每个组件都足够简单。简单的组件容易被理解和实现,因此简单性对系统设计而言,是非常重要的。

可修改性

可修改性是指对应用架构所作的修改的难易程度。可修改性进一步可以划分成可进化性,可扩展性,可定制性,可配置性和可重用性。总的来说,可修改性就是指系统应对变化的能力。当新需求出现,或者现在的系统已经不适合当前的场景时,我们是不是可以通过简单地调整系统,使得系统满足当前的需求。

先谈可扩展性。现在的 IDE, 无论是 Jetbrains 系列,还是 Eclipse,还是有宇宙第一 IDE 之称的 Visual Studio,都提供了插件机制,方便地对系统的功能进行扩展(可扩展性)。又比如著名的 Nginx, 其本身提供了一个稳定的核心,同时我们可以按规范为其开发第三方模块,扩展 Nginx 的能力,本来这种机制就增强了 Nginx 的可扩展性,但是因为 Nginx 第三方模块需要用 C 编写的,对很多人而言,开发效率不高,因此 agentzh(章亦春)的 OpenResty 项目受到广泛的欢迎,OpenResty 把 Nginx 与 LuaJIT 结合,即保留了 Nginx 的高性能,又可以方便地使用 Lua 开发自己的应用,可以说进一步提升了系统的可扩展性。但是这还不够,春哥准备在 OpenResty 之上,再定义一个小语言 EdgeLang, 这是一种基于规则的语言,用它写的代码,经过编译,可以生成高效的 OpenResty Lua 代码,可以说进一步降低了对 Nginx 进行扩展的难度。

其次是可定制性。例如 Java 9 中将出现的模块化功能,Jigsaw。有了模块化,我们就可以根据自己的需要对 JDK 进行细粒度的定制和裁剪,从而可以按需部署,不必将 JDK/JRE 中不需要的东西也部署到生产环境。

再次是可配置性,系统可以正常运行起来后,一些需要经常修改的东西,是写死在了代码里,还是可以很方便地进行配置,配置之后,系统可以自动检测到变化,并加载生效,还是需要重启系统?这些都反映可配置性的高低。

最后是可重用性。例如我们写了个负载均衡器,这个负载均衡器是否与上下游的组件耦合在一起了?是不是可以很方便地用到其它上下游组件的环境中?如果可以在很多其它场景中使用,那重用性就好,如果和特定的上下游组件有强耦合,那重用性就差。

可见性

可见性是指一个组件对于其它两个组件之间的交互进行监视或者仲裁的能力。例如在一个分布式的系统中,可能有大量组件存在,如果设计架构时,没考虑可见性,那系统一旦出了问题,查起来就比较困难。

例如,Google 的论文 Dapper 出现后,就出现一系列用于分布式系统跟踪的开源项目。例如 Zipkin, Pinpoint

pinpoint

从上图可以看出,用 Pinpoint 我们可以观察分布式系统中,组件之间的交互情况,这就使得系统的可见性大大增强。

增强系统可见性,不仅能在系统出现问题时,快速定位系统瓶颈。也能在问题出现之初就感知到故障,而不是等到某一天突然发现某个系统已经不能正常服务,而我们却不知道。

可移植性

如果软件能在不同的环境下运行,那软件就是可移植的。

例如,Java 平台的应用,由虚拟机屏蔽了底层系统的细节,因此可以比较容易地在不同系统之间移植。这样你就不必在多个平台,编写多套代码了。

在设计系统时,我们就需要考虑系统的可移植性,例如移动端应用,是不是可以方便地在 iOS 和 Android 之间移植,服务端应用,是不是可以方便地移植到云端环境? 如果系统的实现,对特定环境的依赖比较重,那移植起来就不大容易。

可靠性

可靠性是指当组件,连接器或数据中出现部分故障时,一个架构容易受到系统层面故障影响的程度。

例如,系统中如果一个组件出现了故障,整个系统都被拖垮了,那这个系统的可靠性就比较差,如果系统有良好的降级策略,在部分组件故障时,仍然可以保证系统核心部分正常运转,那系统的可靠性就比较好。这提示我们设计组件时,要避免由于自身故障,对外界造成大的影响,同时也要减少其它组件出现故障时,自己受到影响的程度,尽力提高整个系统的可靠性。

又例如,如果系统设计成一个单点,能服务一旦挂了,就无法正常提供服务了,这样可靠性就比较差。如果提供了足够的冗余,一个节点挂掉,其它节点还能提供服务,这样可靠性就比较好。

基于网络的架构风格

这一章主要解决的问题有

  • 什么是架构风格
  • 不同架构风格对系统属性有什么影响

什么是架构风格

架构风格就是将一组架构约束组合起来,再给它一个名字。向一种架构风格添加一个约束,就形成了一种新的架构风格。

arch-style

上图是各种架构上的约束,组合起来就组成了架构风格。

我们学习架构风格,其实就是要了解各种架构约束,了解他们会带来什么,他们组合起来又意味着什么。

基于网络的架构风格

其中网络的架构风格,主要有下面这些,他们都是各种架构约束的组合。

  • 数据流风格
  • 复制风格
  • 分层风格
  • 移动代码风格
  • 点对点风格

每种风格又可以细分出一些风格。

数据流风格

数据流风格分为下面两个风格:

  • 管道和过滤器(Pipe and Filter, PF)
  • 统一管道和过滤器(Uniform Pipe and Filter, UPF)

管道和过滤器风格,在输入端读取数据流,在输出端产生数据流,架构上的约束为组件之间要零耦合,不能共享状态。统一管道和过滤器风格在此基础上,增加了一个约束,即所有组件使用统一的接口。

我们可以看到很多数据流风格的例子。例如,UNIX/LINUX 中的 shell, 命令和命令之间可以任意组合,原因就是命令之间的接口是统一的标准输入输出,数据就可以在任意组合的命令之间流动,从而根据我们的需求,随时组合成各种各样的功能。

又例如,著名的编译器工具链 LLVM。

llvm

源代码经过编译器前端,优化器,编译器后端,最终形成机器码。LLVM 有一套自己的代码表示方式,称为 LLVM IR。在 LLVM 内部,组件之间以 LLVM IR 作为接口统一的方式。编译器前端将源代码生成 LLVM IR, 优化器将 LLVM IR 转化成优化后的 LLVM IR,编译器后端将 LLVM IR 转化成机器码。

llvm-detail

这样做编译器前端的人,可以专注于将各种语言转化成 LLVM IR,不必关心怎么优化,怎么生成各类平台上的机器码。做编译器优化的人,可以专注于优化 LLVM IR,不必关心前端和后端,多个优化器之间也可以组合。做编译器后端的人可以专注由将 LLVM 转化成各类平台的机器码。

通过这些例子,我们可以明白数据流风格的好处,这种风格简化了系统的设计,各个组件之间以统一的方式通信,同时可扩展性,可进化性很好,想加个功能,或者替换某个功能,只需要新加一个组件,或者优化其中一个组件就可以了,其它组件不受到影响。每个组件在这个过程中,可重用性也得到极大提高。

但数据流风格也有缺点,例如组件太多,会影响整个系统性能,要统一接口,需要转化数据格式,也会影响系统性能。

复制风格

复制风格又分为下面两种风格:

  • 复制仓库(Replicated Repository, RR)
  • 缓存(Cache, $)

所谓复制仓库风格,就是通过多个进程提供相同的服务。我们对这种方式应该很熟悉,通过这种方式,整个系统可以处理更多请求,可伸缩性得到提高;每个进程压力的减少,也可以降低响应时间;同时因为存在冗余,系统也变得更可靠。

缓存风格,就是复制之前请求的结果,下次再有请求时,直接返回之前缓存的结果。这也是比较常见的设计,例如浏览器的缓存,文件系统的缓存。采用缓存风格,对性能有改善,但是改善程度也受到缓存命中率的影响,因而改善程度不如复制仓库风格;对网络的使用效率也有提升,因为结果直接缓存了,不必穿过网络,经过各种计算生成;

分层风格

分层风格有很多变种,但最基本的就是 客户-服务器 风格,在这种风格的基础上,再逐渐施加其它约束,形成新的风格。

分层风格分为下面的几个风格:

  • 客户-服务器(CS)
  • 分层与分层-客户-服务器(LCS)
  • 客户-无状态-服务器(CSS)
  • 客户-缓存-无状态-服务器(C$SS)
  • 分层-客户-缓存-无状态-服务器(LC$SS)
  • 远程会话(RS)

客户-服务器风格中,客户端通过连接器将请求发到服务器要求提供服务,而服务器提供一组服务,并监听对这些服务的请求。客户-服务器风格背后的原则是分离关注点,客户端关注用户接口,服务端关注业务逻辑的处理,数据的生成。两端分离后,可以各自独立地演化。我们可以关注一下前后端分离的一些实践,来理解分离关注点原则对互联网开发模式的改变。

分层风格中,系统按层次来组织,每一层为上层提供服务,并使用下层的服务。典型的例子就是 TCP/IP 协议栈。

tcpip-stack

我们可以看出,分层之后,每一层只和其上层下层有耦合,减少了跨层的耦合。应用层的协议不必关心低层的数据链路层,各层都在自己的抽象层次上工作。不同的应用层可以复用下层的协议,可重用性得到提高。但分层系统会降低系统的性能,各层会有自己的数据格式,数据格式的层层转换会带来一定开销。分层-客户-服务器风格,是在客户-服务器的基础上添加了代理(proxy) 和网关(gateway) 组件。

无状态-客户-服务器风格,是在客户-服务器风格基础上添加了无状态这一约束。无状态的意思就是客户端与服务端的会话状态,不能在服务端保存。我们举个移动广告平台跟踪广告展示,点击的例子,来说明有状态的设计与无状态的设计的区别。

客户端向服务端请求广告,服务端除了返回广告内容外,还需要返回展示上报和点击上报跟踪链接,当广告有展示时,客户端会访问展示上报链接,服务端收到上报后,对展示进行计数,点击上报也是如此。

我们先来看有状态的设计:

click_state

这种设计中,服务端向客户端返回的跟踪链接只有一个 id,值为 123, 与此值相关的上下文信息,例如广告位,请求 ip,时间等信息被存入数据库。当广告有展示时,客户端上跟踪服务上报,参数中即有 id 123, 跟踪服务收到请求时,从数据库中提取出与 123 相关的上下文信息,之后进入统计流程。

这种设计就是一种有状态的设计,此处的状态就是指 id 123 的上下文信息,可以看出他们被保存在服务端。这种设计的好处是,跟踪链接会比较短,因而网络上传输的数据会少。坏处有很多,一是系统的伸缩性变差,因为广告上下文信息要在服务端存储,并且在有展示时提取出来,当请求量变大,展示周期变大时,需要存储的数据量都会被放大。二是系统的可靠性变差,因为不是请求的广告都会有展示,请求的上下文信息不可能永远存在服务端,总有过期时间,但是如果过期之后,展示上报到达跟踪服务,就会提取不到之前的状态信息。三是可见性下降,如果展示跟踪失效,可能是客户端出问题,也可能数据库里没数据了,数据没存进去,数据过期,出了问题会很难查。

我们再看来无状态的设计。

click_stateless

无状态的设计中,与展示/点击相关的上下文信息,直接放在跟踪链接中。有展示时,上报的链接中就包含了与此展示相关的所有信息。好处就是系统伸缩性提高,因为展示相关的状态不需要在服务端保存了,分散到了客户端;同时可靠性也得到提高,因为状态无需维护;另外,可见性也得到提高,因为展示相关的信息都在跟踪链接里了,直接观察是否上报了正确的链接就可以分析展示上报情况。坏处就是如果展示相关信息很多,链接会很长,在网络上传输的数据会变多,也可能会被截断。

当然,有状态的设计自然有它的用处,但是不在上面我们举例的那类场景。这就是我们要讲的远程会话风格。

远程会话风格是客户-服务器风格的变体,客户端在服务器启动会话,而应用的状态完全保留在服务器上。这样的好处是,客户端的设计可以得到很大简化,可重用性得到提高。而系统功能的扩展性也得到提高,因为升级服务端后,所有客户端可以同时受益,想想升级客户端与服务端的难度就可以明白这一点就多方便。当然,坏处就是因为服务端要保存客户端状态,降低了伸缩性。

移动代码风格

移动代码风格使用移动性(mobility)来动态地改变处理过程与数据源之间的距离。主要有下面几种风格:

  • 虚拟机(VM)
  • 远程求值(REV)
  • 按需代码(COD)

虚拟机风格应该是我们比较熟悉的,例如常用的 Java 语言运行在虚拟机上,虚拟机分离了指令与实现,可移植性比较好,因为虚拟机本身帮我们屏蔽了底层系统的细节,另外扩展性也比较好,例如 Java 虚拟机有了 G1 垃圾收集器后,所有平台上的 Java 代码均可同时受益。

远程求值风格适用的场景是客户端知道如何执行服务,但是缺少执行服务的资源,此时,客户端可以将代码发送到服务端,服务端组件执行完后,将结果返回客户端。例如,我们用 Spark Shell 对大规模的数据作交互式的分析时,数据和计算资源都不在本地,但是客户端知道分析机群上的哪些数据,知道如何分析,代码会被发到 Spark 集群上,对数据进行分析,分析结果最后又返回 Spark Shell。

按需代码风格中,客户端知道如何访问资源,但是不知道如何处理资源,此时客户端可以向远程服务器请求处理资源的代码,在本地执行。典型的例子就是 JavaScript,浏览器从服务器上下载 JS 代码,在浏览器中执行。这种方式的好处是,能够方便地为已经部署的客户端添加功能,改善了可扩展性。我们想添加新功能时,升级 JS 代码就可以了,浏览器下载了新版本的 JS 代码,就能使用新功能了。

点对点风格

点对点风格要介绍的一种风格叫 基于事件的集成(EBI),其含义是一个组件发布一个或者多个事件,其它组件对某类事件进行订阅。

例如,在 Kafka 中,生产者向某个 topic 生产数据,可以有多个消费者消费这个 topic 中的数据。当我们想用这个 topic 中的数据做更多的事情时,只需要新增加一个消费者就可以了。这样,系统的扩展性就很好。同时消费者可以独立地演化,而不会影响其它消费者。想象一下,如果你把这些消费者的功能集中在某一个系统中,那每次添加功能,都要改这个系统,同时如果某个功能对资源的消耗比较大,因为其它功能和这个功能在同一系统中,就会对其它功能造成影响。

到这里,我们对各种架构风格的介绍就结束了。

设计 Web 架构

Roy Fielding 博士在定义了架构属性,架构约束,架构风格之后,用这些理论去指导 Web 架构的设计。

之前已经说过,Web 规模开始扩大后,现有架构的缺陷已经不能支持这么大的规模,需要对其进行增强,摆在眼前的任务有:

  • 定义在现有的早期 Web 架构中被公共地、一致地实现的架构通信的子集
  • 识别出在这个架构中存在的问题
  • 定义标准解决问题: URI, HTTP, HTML

架构风格就被用于解决这些问题,首先架构风格被用于描述现有 Web 架构背后的基本原理,包括分离关注点,简单性,通用性等。然后通过为架构风格添加约束,使其满足现代 Web 架构所需要的属性。在修改 Web 架构时,要与新 Web 架构风格进行比较分析,以便在新提议部署前识别出其与 Web 架构风格的冲突。

表述性状态转移(REST)

在介绍完各种约束,架构风格后,REST 架构风格就变得水到渠成了。它是通过在一个空风格基础上,不断添加约束而形成的。

从空风格开始,先后添加下面的约束:

  • 客户-服务器
  • 无状态
  • 缓存
  • 统一接口
  • 分层
  • 按需代码

之前我们已经讲了这些约束对系统设计而言意味着什么。

最后用两张图总结一下 REST 架构风格推导中的约束与属性的关系。

rest-constrain

上图可以看出,在 REST 架构推荐过程中,添加的各种约束。

rest-property

上图可以看出,各种约束为系统带来的各种特性。

总结

文章到这里就全部结束了,这篇文章主要结合 Roy Fielding 博士论文的前三章介绍了基于风格的应用架构设计中,需要关注的架构属性,架构约束,以及架构风格。有些内容基于经验所限,可能无法做到表达精确,希望以后能不断修改,补充。

  • 10
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值