软件架构基本模式,经典书籍。

译作。

oreilly官网对software-architecture-patterns开放下载。

软件架构模式

理解常用的架构模式,掌握适用的场景。–Mark Richards

目录

引言

1. 分层架构

  • 模式描述
  • 关键概念
  • 模式案例
  • 考量
  • 模式分析

2. 事件驱动架构

  • 中介拓扑
  • 经纪拓扑
  • 考量
  • 模式分析

3. 微内核架构

  • 模式描述
  • 模式案例
  • 考量
  • 模式分析

4. 微服务架构

  • 模式描述
  • 模式拓扑
  • 避免依赖以及协调机制
  • 考量
  • 模式分析

5. 基于空间的架构

  • 模式描述
  • 模式力学
  • 考量
  • 模式分析

内容

引言

  • 对于开发者而言,面对一个无架构的应用项目是极为常见的。在实际开发中面对这样的情形,我们只能求助于一些常见的工业界的标准化架构设计,例如分层架构,但往往会简单地将源码分包。这样导致的结果往往是:一些职责不清晰的源码被分成了一个模块,内部的关系却是混乱的。通常这就是【大泥球混乱架构】。
  • 缺乏良好架构的应用往往会有耦合、脆弱、不可变、无清晰的方向等问题。因而,在了解内部组件、模块的内在之前,我们很难去判定应用的架构特征。特征包括了以下问题,关于部署、维护的基本问题:这个架构可伸缩吗?应用性能如何?可变性如何?响应性能怎样?
  • 设计模式帮助我们定义这些基本特征以及应用的行为。比如,有些模式目的是为了提高伸缩性,而有些则是为了更加敏捷。了解多种设计模式的优缺点、特征对于满足我们项目中的商业需求、团队目标是有很大帮助的。
  • 作为一名架构师,你必须去证明自己架构上抉择的有效性,尤其是在架构选型上。本书的目标就是帮助我们做更好的架构抉择。

第一章:分层架构

分层架构是最为常见的架构模式,也被称为多层体系架构。这种模式是J2EE应用常用的工业级标准,因而被很多架构师、设计师、开发者所熟知。分层模式紧密贴合了大多数公司中的传统IT通信关系以及组织结构,这使得大多数商业应用开发将其作为架构首选。

模式描述

  • 分层架构下的每个组件从水平上分层,每一层负责特定的职责,例如展示层、业务层。尽管模式中不强调层级的数量以及类型,但实践中一般包含这四层:
    • 展示层
    • 业务层
    • 持久层
    • 数据库层
  • 在某些情况下,持久层会与业务层合并,尤其是一些SQL逻辑放在业务层处理的情况。因此,小的应用可能只有三层结构,大、复杂的应用则会有更多层。
  • 每一层具有特定的角色、职责。比如,展示层负责处理UI交互以及浏览器的通信逻辑,而业务层则去处理请求过来的具体业务逻辑。架构中的每一层共同组成了围绕一个请求过来后业务处理的抽象实体。比如,展示层无需考虑如何拿到顾客的数据,它只需按既定格式将数据展示即可。类似的,业务层无需考虑格式化数据,也无需操心数据从哪里来。它只需要从持久层拿到数据,处理对应的业务逻辑(计算、整合数据),然后将数据传到展示层。
  • 分层架构模式强大的一个特点:分离了组件的关注点。单一层中的组件,只关注该层级需要的逻辑。比如,展示层的组件只处理展示的逻辑,而业务层的组件只处理业务逻辑。这种组件分类的形式使得在我们的架构中构建有效的职责、角色变得更加容易,也使得我们使用了既定的组件接口以及范围的前提下,开发、测试、把控、维护应用更加得力。

关键概念

  • 注意下图中,每一层被标注了关闭closed状态。这在分层架构中是很重要的概念,关闭的一层意思是:一个请求只能逐层流动,它必须从上层访问下层,不能越级。举例来说,页面上发出的一个请求,只能去到业务层,再去到持久层,最后才能访问数据。 layered_architecture_closed

  • 所以为何不允许展示层直接访问持久层或者数据库层呢?毕竟,直接访问的性能是最佳的。这个问题的答案在于一个关键概念:层级隔离layers of isolation

  • 概念的意思:单一层不会影响其他层的组件,即层内的组件变更被隔离了,改变仅限于本层内部。如果允许了展示层直接访问持久层,意味着SQL的改动会影响到业务层,还有展示层!!!就会造成多个组件间耦合,因为他们之间有过多依赖。这样的架构会有很高的改动成本

  • 层级隔离也意味着,每一层都是独立工作的,不互相依赖,因而对其他层级内部不需要有了解。

  • 为了理解这种概念的重要性,可以想象一个重构任务:将JSP转换为JSF。假定展示层与义务层间的约定model是不变的,那业务层就不会受到展示层重构的影响。

  • 状态关闭closed促成了层级隔离,也分离了各层级的职责,但有些时候我们也需要将某个层状态打开open。比如,我们想要增加一个公用服务层,里面包含了一些原来业务层的封装(数据、字符串工具、日志工具、审核工具)。这种情况下增加一个服务层往往是种好想法,因为从架构上,它限制了服务层到业务层的访问,而不是展示层的限制。如果没有单独的层级,我们就做不到管控这种访问限制。

  • 在这个例子中,新的服务层处于业务层下,意味着从展示层是不能直接访问的。但这样带来一个问题,请求必须要经过服务层才能到达持久层,这完全是多余的一步。在分层架构中这是个老问题,通过创建开放的层级可以解决。

  • 如下图所示,服务层被标注为open状态,意味着请求可以直接穿过这层,访问其下面的层级。自然解决前面所提到的问题。

open_layers_and_request_flow

  • 利用openclosed状态帮助我们定义了架构层级的关系以及请求的流向,同时也为设计师、开发者提供了有用的信息,可有效理解架构中各层间的访问限制。如果各层级间 状态不记录,或者通信不做规范限制,后期就会难以测试、维护、部署。

模式案例

  • 为了描述分层架构是如何跑起来的,我们现在想象一个查询客户信息的功能,如下图所示。

layered_architecture_example

  • 黑色箭头表示请求一直流转到数据库,拿到客户数据,红色箭头表示响应从数据库返回,一直到页面上,将数据展示出来。本例中,客户的信息同时包含了客户以及订单的信息。
  • UI层只负责接收请求以及展示数据信息。他不知道数据在哪里、数据如何查出来的、需要关联几张表才能拿到数据。一旦一个请求为某一客户创建,会同时创建一个客户的查询代理对象,这个对象的职责是:明确业务层哪一模块负责这个处理过程、如何转换为目标模块对象、目标所需数据(约定。下面的Customer Obj负责整合查询所需的信息。业务层调用了customer以及order的dao去持久层查想要的数据。这些模块轮流执行sql语句从库里拿到数据,再返给业务层。业务层的Customer Obj将数据整合完整,再一路返回给展示层的代理对象,进而展示在页面上。
  • 从技术角度看,这个过程有多种实现方式。比如,在Java平台,可以用JSF作为表现层的组件。而业务层的bean可以被Spring管理或者EJB3。持久层的对象可以是一个POJO、mapper.xml文件、甚至JDBC查出的封装对象。如果是微软平台,展示层可以用ASP,使用C#调用业务方法,ADO查询数据。

考量

  • 分层架构模式是一种稳定且通用的模式,为大多数应用架构开了个好头。但从专业角度,选用该架构后也有一些必须考虑到的点。
  • 第一点需要考虑的就是:污水池反模式。这种反模式描述了当一个请求在没有逻辑处理的情况下逐层传递的情况。举例来说,假定展示层请求一个客户数据,请求到了业务层,在这一层没做任何操作,仅仅是调用持久层的方法,接着一条sql执行完毕,将结果再逐层返回。过程中没有任何整合、计算、转换的逻辑处理。
  • 分层架构在某些少有情况下会遭遇污水池反模式。关键在于,要去分析有多少比例的请求是这种情形的。二八法则往往是鉴定架构质量的好的标准,即20%的请求做成了污水池反模式是比较常见的。但如果情况反过来,80%即大多数你的业务请求都做成这样,就属于问题案例,就要修改!就需要适当利用开闭(open closed)来调整这些业务代码,也要铭记,如果不分层隔离,将来做管控就会更难。
  • 关于分层架构另一个关注点:即使我们对表现层与业务层做了部署隔离,分层架构往往会以应用为一个整体来进行设计。对于一些应用,这不是什么毛病,但他会对某些情况造成隐患,关于部署、健壮性、可靠性、性能、可拓展性这些方面。

模式分析

  • 下面的表格包含对常见架构特点的打分以及分析。打分是基于对某特点的常见应用做自然倾向判断,包括模式的通用应用。在结尾的附录A可以查看与其他模式的比较。
  • 整体敏捷性:
    • 打分:低。
    • 分析:敏捷性是指面对一直在变的环境所响应的快慢。尽管分层后隔离了层级特性,但它在修改时依然是笨重且耗时的,因为在大多数实现中,业务代码会经常出现层级耦合的情况。
  • 部署容易度:
    • 打分:低。
    • 分析:这要取决于你实现该模式的方式,部署一些大应用时有可能会是一个大问题。某个很小的代码变更可能就会导致整个应用的重新部署,造成了无法排期、休息日执行的困难。也正因如此,该模式不属于持续交付的一环,进一步降低了该模式在部署方面的打分。
  • 可测试性:
    • 打分:高。
    • 分析:由于该架构下的组件均只属于某一层,因而其他层是可以模拟或者彻底替换的,这提高了架构中的测试容易度。开发者可以模拟展示层组件以此测试业务层逻辑,同理也可以模拟业务层来测试展示层。
  • 性能:
    • 打分:低。
    • 尽管某些分层架构性能不错,但从设计上说,其不是朝着性能这个方向努力的。因为所有请求都必须经过多层传递,这是低效的。
  • 可拓展性:
    • 打分:低。
    • 分析:由于分层架构是面向高耦合、整体实现的,因而使用了分层后应用拓展性自然降低。我们可以通过将不同层分割在不同物理设备上或者将整个应用拷贝到多节点部署,以此拓展应用,但这样的粒度过大,拓展成本很高。
  • 部署容易度:
    • 打分:高。
    • 分析:这里得分高,是因为这种模式是众所周知的容易部署的,因为其设计面向整体。在业务应用开发中,分层架构被广泛采用。公司内部的沟通方式与组织架构以及开发软件的方式之间的关系,在康威定律中阐述的最为彻底通透。

第二章:事件驱动架构

  • 事件驱动模式是一种主流的用于设计高拓展应用的分布式异步模型。适用性也很高,可同时用于大应用与小应用。事件驱动架构从设计上就是高度解耦、单一事件处理(异步收取、处理事件)。
  • 事件驱动架构模式包含了两个主要的拓扑模式:
    • 调停者拓扑:Mediator Topology:通常在一个中心调停器中,用于协调某个事件的多个执行步骤
    • 经纪人拓扑:Broker Topology:在不使用中心调停器时,串联多个事件
  • 这两种拓扑的架构特性、实现都不一样,所以明白哪个更适用于我们项目很关键。

调停者拓扑

  • 该拓扑模式面对多个事件以及多个步骤,并且需要一些级别下的协调时,就变得有用了。举例来说,买一个股票,首先要验证数据,接着检查交易合法性,将交易指派给经纪人,计算佣金,最后再把股票交给经纪人。这些步骤需要有一个协调机制,以此决定步骤执行的顺序,以及哪些序列执行,哪些步骤并行执行。
  • 调停者拓扑模式下,有四种主要的架构组件类型:
    • 事件队列
    • 事件调整者
    • 事件频道
    • 事件处理器
  • 事件的流程从客户端发送一个事件到事件队列开始运作,队列会将事件传送给调停,者。调停者接收到初始化的事件后,可以通过向事件频道发送异步的事件来达到协调事件的目的,发送过去后执行相应程序。事件处理器,监听事件频道,从调停者接收事件,并执行事件中特定的业务逻辑。

general_mediator_topology

  • 上图描述了事件驱动架构模型中,通用的调停者拓扑关系。
  • 在一个事件驱动型架构内,有上百个事件队列也是正常的。架构模式并不指定事件队列的实现方式,可以是MQ,也可以是web service端,或者以上之间的组合。
  • 在这种模式下有两个事件类型:
    • 一个初始化事件: 调停者接收到的原始事件
    • 一个处理中事件 调停者生成的事件,再将其穿传给事件处理组件
  • 事件调停组件的职责:协调初始化事件内执行步骤。对初始化事件中的每个步骤,事件调停者会向其发送特定的处理事件到消息频道中,接着任务就被事件处理器所处理。PS:事件调停者并不对初始事件做任何逻辑处理,但它知道处理他们的步骤。
  • 事件频道:负责将某特定步骤处理中的事件以异步的方式发送到事件处理器。频道可以用MQ也可以用消息主题消费模式,后者是更为常见的实现方式:因为多个处理器可对事件进行处理,每个基于接收到不同的处理中事件进行不同的任务操作。
  • 事件处理器组件:负责应用业务逻辑的处理。处理器是独立、自包含、高解耦的组件,其在应用中执行特定的任务。事件处理器的处理粒度由小到大(fine-grained比如计算一个订单产生的营业税,coarse-grained或者处理一个保险索赔单),千万要记住:每个事件处理组件只处理单一的业务任务,并且不依赖其他的处理组件。
  • 事件调停者可有多种实现方式,作为一名架构师,我们要清楚每种实现的原理与作用,以此来真正匹配满足需求。
  • 最简单、最常见的实现诸如一些开源的集成组件,比如Spring Integra‐ tion, Apache Camel, or Mule ESB。这些方案的实现大多基于Java或者域定义语言。如果追求更加复杂的调节或者组织过程,你可以使用业务处理执行语言实现,其要依赖开源组件如Apache ODE的引擎运作。BPEL是一种标准的类XML语言,可以描述数据与执行的步骤,可以用于描述初始化事件。对于有复杂调节需求的大型应用(包括了人机交互产生的步骤),你可以使用BPM业务处理管家来实现,如jBPM。
  • 对于调停者拓扑架构模式,理解需求与对应的技术实现,是决定该架构选型的关键!使用开源集成组件去处理相当复杂的业务流程会是失败的尝试,愚蠢程度可类比使用BPM去处理简单的路由逻辑。

mediator_topology_example

  • 为了理解调停者拓扑模式,可以想象你去到一家保险公司准备入保,这时你想要移动到下一步。在这个案例中,初始化的事件就可以叫做relocation重定位事件。其中的步骤已经在上面图中有描绘。对事件内的每一步,调停者都将产生一个处理事件,比如更换地址、重新计价。接着将这些处理中事件发送到事件频道,然后等待事件被对应处理器处理,比如客户流程、报价流程。在这个初始化事件被处理完之前,这些处理事件会持续进行这个过程。同一初始化事件内的不同处理事件是可以同时进行处理的。

经纪人拓扑

  • 经纪人拓扑不同于调停者拓扑模式,因为其没有中心事件协调者,事件流顺着事件处理器以一种链式的方式进行分发,比如使用MQ的方式。当我们需要简单的事件处理并且不需要中心事件协调过程的时候,该拓扑模式就恰如其分。

Event-driven_architecture_broker_topology

  • 在该拓扑模式内,有两种主要的架构组件:
    • 一个经纪人组件: 可被中心化或者联邦化,内部包含了事件流所使用的所有事件频道。内部的事件频道可以用MQ、主题订阅,或前者任意组合。
    • 一个事件处理器组件
  • 如上图描绘的,没有中心的事件调停者,相反,每一个事件处理器负责处理一个事件,并且会发布一个新的事件来表明自己所做的任务。举例来说,一个用于平衡证券投资组合的事件处理器可能会收到一个初始化事件,名为股票拆分。基于这个初始化事件,该事件处理可能会对投资组合做重组,接着向经纪人组件发送一个新的事件,名为重组投资组合,接下来其就会分发为多个事件处理。PS:有时候当一个事件被发布,但并没有别的事件处理器来接收它。这在我们为未来功能做拓展时是很常见的。(暂时留用的意思
  • 延续之前讲解调停者拓扑的习惯,我们假设还是在受保这里做操作,由于经纪人模式下没有中心的调停者来接收初始化事件,那这里的客户处理组件就直接接收一个初始化事件,然后发送一个的新的事件,表明其更改了顾客的地址。
  • 在这个例子中,有两个事件处理器会关注更改顾客地址这个时间:
    • 报价流程: 根据更改后的地址重新计算一个新的自动保险率,发布一个新的事件到频道中,同时标注其所做的操作
    • 索赔流程: 收到同样一个更改地址的事件,但在这里,它的工作是更新保单,向频道发送一个更新保单的新事件
  • 新发布的事件都会被另一个处理器接收然后处理,事件链条在处理完毕前会一直进行处理。

broker_topology_example

  • 如上图,经纪人拓扑正是围绕业务功能处理所在的事件链条来工作。
  • 为了更好地理解这种拓扑模式,可以想象其为接力赛跑。在接力赛中,运动员拿着接力棒跑一定距离,然后把棒子传给下一个选手,一直持续这个过程,直到终点。赛跑中,一旦某个运动员手里没了接力棒,他就跟这场赛跑没有关系了(已经完成了他的工作),这也正是经纪人拓扑的真谛:一旦一个事件处理器完成了事件处理,他就再也不会回到处理流程中了。

考量

  • 事件驱动模式架构实现起来相对复杂,本质上是因为他异步分发的特质。当实现这模式时,你必须解决分布式架构的问题,比如远程流程的可用性、响应、经纪人或者调停者失败后重新连接的事件逻辑。
  • 选用这种架构要考虑的一点:单一业务处理的原子性事务。因为事件处理器组件是高度解耦并且分布式的,所以维持同一工作单元内的的统一事务是很困难的。出于这点,当设计应用时,你必须不断思考哪些事件可以独立运行,然后据此去规划事件处理器的粒度。当你发现需要在一个整体中拆分事件处理,意思是,在一个不可分割的事务中你使用了多个独立的处理过程,这对你的设计可能就是一种错误。
  • 或许事件驱动架构中一个最困难的方面在于事件处理器组件的创建、维护、治理。每个事件都会有一个特定的合同来制约它(比如数据值、数据格式)。在使用这种模式时,处理一些标准的数据格式是极为重要的,比如xml、json、java对象等,所以在最开始就要指定一个合约来规范这些方方面面。

模式分析

  • 下面的列表包含了对事件驱动架构的评分、分析。同样评分是基于对该模式通用实现的考核。同样在附录A可以看到与其他模式的横评。

  • 整体敏捷性:

    • 评分:高。
    • 分析:由于事件处理器组件是单一目的并且与其他组件高度解耦的,所以在不影响其他组件的情况下,可以做到快速隔离变化。
  • 部署容易度:

    • 评分:高。
    • 由于模式其内在的高度解耦特质,部署是很容易的。经纪人拓扑相比调停者拓扑又容易一下,因为事件调停者组件与事件处理器组件是耦合的,意味着处理器变动会造成事件调停者的变更。
  • 可测试性:

    • 评分:低。
    • 分析:单元测试无比艰难,需要一些定制的客户端或者工具来生成事件。由于异步的特性,也造成了测试的困难程度。
  • 性能:

    • 评分:高。
    • 分析:通俗来讲,异步的本质提升了性能表现,当然如果使用一些性能较差的消息组件也可能会造成低性能的实现。换句话讲,解耦能力、并行异步的操作要比消息的生产消费更加重要!
  • 可拓展性:

    • 评分:高。
    • 分析:可拓展性也是由该模式的高度解耦、独立的事件处理器机制所造就的。每个事件处理器可以单独拓展,即允许细粒度的拓展性。
  • 开发容易度:

    • 评分:低。
    • 异步模型、合约的创建、未响应或失败响应背景下的错误处理,这些都增加了开发难度。

第三章:微内核架构

  • 微内核架构模式(有时也称为插件架构模式)是一种为实现基于产品的应用的非常合适的模式。基于产品的应用是指:产品是打包的、可提供下载的,并且作为第三方产品有版本标注。然而,许多公司也会把内部的商业应用开发、发布出去,比如软件产品,会附带版本、发布日志、插件特性。这些都跟微内核架构天然匹配。
  • 微内核架构允许你讲应用的特性以插件形式加入到核心应用中,在拓展功能的同时也做到了功能的分离与独立性。

模式描述

  • 为内核架构模式包含来两种组件:

    • 核心系统: 通常会包含基础功能所需的组件,只需让系统运行即可。许多操作系统都实现了微内核架构,这也正是名字的由来。从商业应用的角度来看,核心系统部分通常被定义为通用的业务逻辑会包含特定的案例、规则、复杂的条件处理。
    • 插件模块: 独立的模块,包含定制化的处理、附加功能,意味着是对核心系统的拓展、增强。通常插件都是与其他模块隔离的,当然也可以自己动手做一个依赖其他组件的。不管哪种设计,都要做到依赖最小化。
  • 应用的逻辑在这两者间是隔离的,天然提供了拓展性、灵活性、功能隔离、自定义处理逻辑。下图描述了微内核架构基本的样子。

microkernel_architecture_pattern

  • 核心系统需要明确哪些插件可用、如何使用他们。实现这种理念的一种办法是:插件注册。注册内容会包含每个插件模块的信息,比如名字、数据规约、远程访问协议的细节(取决于插件是如何连接核心系统的)。举例来说,一个计税软件的插件标记来某个高风险的税单审核项,那就对应会有一个审核人的注册服务存在、数据规约(输入、输出数据格式等)、格式。如果插件通过soap访问就应该包含wsdl。
  • 插件可通过多种方式连接到核心系统,包括osgi、消息、web service、点对点绑定。连接的类型取决于你正构建的应用的类型(规模大小)以及你特定的需求(单点部署还是分布式)。架构本身不去制定这些细节,只说明了插件模块要保持独立。
  • 插件与核心系统间的规约可以从标准规范到自定义范围选择。自定义规范往往会在第三方的开发环境下看到,在那里你通过插件无法改变规范内容。
  • 这种情况下,我们可以在标准化规约与插件规约间增加一个适配器层,这样的话核心系统就无需对每个插件都做代码的改动。当准备创建标准的规范时(通常采用xml或者java中的map),要牢记,从一开始的版本策略一定要制定好。

模式案例

  • 或许,对微内核架构最好诠释的一个案例就是:eclipse ide工具。下载eclipse后我们就拥有了一个强大的编辑器。同时可以自由添加插件,这时就有了定义的空间。浏览器则是另一个例子,一些查看器或者插件功能在基本的包中都是不存在的。
  • 基于产品的软件案例太多了。也有一些大型商业应用。这次我们以购买保险为例,会讲到标准的索赔过程。
  • 可以想象,大多数的保险索赔过程都包含了复杂的规则,其中会有对应的引擎来处理这些复杂业务。但随着规则越来越多,并且互相牵扯,最后就导致一个规则的变动就需要大量分析员、开发者、测试员来参与工作。而微内核架构就解决了其中很多问题。

microkernel_architecture_example

  • 上图就说明了一个索赔处理核心系统的工作情况。它首先具备了基本的业务逻辑处理能力,而没有定制化过程。然后,每一个插件模块都对基础功能做了自定义,有状态标示。在本案例中,插件的代码都是独立的,改动不影响核心系统以及其他插件。

考量

  • 微内核架构一个很棒的点:它可以内置、包含在另一个架构模式下。举例来说,在应用中某个易变区域,产生了某个问题,如果可以用微内核模式解决,你就会发现,实现时不能完全改造成这种模式。这种情况,我们就可以在分层架构中,来嵌入微内核架构。同理,事件处理的组件也同样可以用微内核架构应用到其他地方。
  • 微内核架构为演进设计、增量开发提供了很好的支持。你可以先开发核心的基础系统,随着应用的成长演进,就可以以插件的形式丰富功能。
  • 对基于产品的应用,微内核架构可以是你考虑的首选模式,特别是那些随着时间会有附加功能、自定义推送用户的模式。如果慢慢地你发现这种架构满足不了你的需求,也可以将其重构到更符合需求的架构设计之上。

模式分析

  • 下面的表格包含了对微内核架构的特点的评分、分析。评分是基于对该模式的通用实现所做的自然倾向分析。同理,附录a可以看到与其他模式的横评。

  • 整体敏捷性:

    • 评分:高。
    • 分析:整体的敏捷性是指对一直改变的环境所具备的响应能力。在微内核架构中,变更会随着插件模块跟着低耦合的节奏大举隔离开。因而,核心系统模块会表现出很强的稳定性,因为变更都放到了插件区域,没有影响。
  • 部署容易度:

    • 评分:高。
    • 分析:要取决于模式是如何实现的,插件模块可以在运行时(热部署)动态添加到核心系统,将部署宕机时间最小化。
  • 可测试性:

    • 评分:高。
    • 分析:插件模块可以分开测试,同时功能的应用也可以在对核心系统无改动的前提下做到。
  • 性能:

    • 评分:高。
    • 分析:设计意图上,微内核架构并没有朝着性能方向发展,但整体而言,大多实现了微内核架构的应用性能都是优秀的,因为我们可以自定义或者合理化应用功能。jboss web server就是一个很好的例子:你可以把用不到的功能拿掉,把高开销的功能也拿掉,例如:远程访问、消息、捕获消费内存。
  • 可拓展性:

    • 评分:低。
    • 分析:由于基于产品的应用实现微内核架构后通常体积是很小的,他们都作为一个很小的单元运行,因而拓展性不大。当然这也取决于你实现的方式,也可以自己来做一些插件层面的拓展,不过意义不大。
  • 开发容易度:

    • 评分:低。
    • 分析:微内核架构要求大量设计理念、合约管控的支撑,这就造成了实现成本的增高。其中,合约版本化、内部插件注册机制、插件粒度、插件连接可选的宽泛度都对复杂性有所作用。

第四章:微服务架构模式

  • 微服务正快速成为整体设计、面向服务架构之外的高可用选择。由于这种架构模式还在演进,工业界对它还有很多困惑,同时对如何实现也需要摸索。报告的本部分内容会向读者阐述它的关键概念以及使用它的取舍所需要的必备知识。

模式描述

  • 不管你选择了哪种拓扑模式或实现风格,有一些核心的理念是必须要遵守的。首先就是隔离部署的元件。 http://bucket171212-1255655048.file.myqcloud.com/%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84%E6%A8%A1%E5%BC%8F_%E8%AF%91%E4%BD%9C/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84/basic_microservices_architecture_pattern.png

  • 如上图所描绘,微服务架构内的所有组件的部署都是隔离的,这就促成了更方便的部署,因为更有效率、合理、流线型生产,也提高了可拓展性,并且大量应用与组件之间是高度解耦的。

  • 或许理解这种架构,更重要的是理解几个字:服务组件。不要在微服务架构内去考虑服务,而要单独的思考服务组件这回事,这个组件从粒度上有可能是一个单一模块也可能是应用的一大块内容。服务组件包含了一至多个模块,其中他们或许是一个函数,也或许是包含了许多业务逻辑的应用。在合理的层面去设计微服务是一大难点。其中的细节会在服务组件协调子部分进行探讨。

  • 另一个关键概念是:分布式架构,也就是说所有该架构内的组件都是高度解耦的,同时通信访问使用RPC协议(JMS,AMQP,REST,SOAP,RMI…)。分布式的天然属性早就了其高度可拓展并且部署上的特点。

  • 关于微服务另一个亮点是:它并不是出于解决某个问题诞生的,而是从另一个架构中的问题演化来的。微服务架构从以下两种架构中演进而来:

    • 使用了分层架构的巨石型应用;
    • 使用了面向服务架构的分布式应用;
  • 从巨石型应用得到的启发,源于持续交付的开发过程,持续迭代的生产线连接了开发、部署生产。而巨石型应用往往都是紧耦合的组件,内部都是可拆分部署的单元,这就造成了一整个应用过于庞杂、难以改动、测试、部署的困境。所以在很多IT部门造成了一个月部署一次的情况。前面所说的这些因素使得每次部署应用时,让应用变得极为脆弱。微服务架构通过拆分一个大应用为多个可隔离的小应用解决了这些问题。

  • 另一个演进的路径则是从SOA面相服务的架构产生的问题而来。尽管SOA非常强大,其提供了非并行的抽象层次、异种连接、服务协调治理、面向业务目标的承诺实现。虽然它复杂、成本高、无所不在、难以理解以及实现,但往往可以解决大多数应用的问题。而微服务解决了他的复杂性,通过简化服务的概念,消除了一些协调机制,并且也简化了与其他服务组件的连接以及访问。

模式拓扑

  • 尽管微服务架构可有多种实现方式,但其中三种脱颖而出了:
    • RESTful API; 适用于网站向外暴露一些小的、自有的独立服务,方式是通过应用程序接口。 	API_REST-based_topology 图片描述了这种情况,可以看到,这些服务组件都是很小粒度的,内部会包含了特定业务逻辑的处理模块,并且与其他rest服务是独立的。在这种拓扑模式下,这些很细粒度的服务都是通过RESTful接口访问的,在部署上会分开为独立的web api层。这种模式下的案例会包括Yahoo、Google、Amazon的一些云服务web接口。
    • 基于REST的应用; Application_REST-based_topology 与前者不同,基于Rest的应用的api接口是从用户界面访问的,不仅仅是一个简洁的API层。如图所示的那样,用户界面这一层作为一个独立的web服务部署到服务器,通过远程访问的协作匹配对应需要的业务组件。这里的服务组件比前者的往往要复杂、庞大、粗粒度,往往也代表了整个业务应用的一部分,而非单一行为的服务。这种拓扑模式对小型或者中型的业务应用(复杂度相对低)还是较为适用常见的。
    • 中心化消息拓扑; Centralized_messaging_topology. 而这种拓扑模式与前者较为类似,不过访问不要通过REST接口,而是用一个轻量级的中心化消息组件(ActiveMQ)。在学习这种拓扑模式时,能够不将其与面向服务的架构或者SOA-Lite所混淆,是极为关键的。这里的消息中间件不做任何协调、转换、复杂路由的工作,只是作为远程访问的传输路径存在。 中心化消息拓扑模式往往是在大型应用或者对传输层有更高控制需求的应用中出现。好处是利用了队列机制、异步消息、监控、错误处理、更好的负载均衡、拓展性。单点失败或者性能瓶颈往往都是在中心化的那个消息中介上,可以通过集群来解决此类问题。

避免依赖与调度(协调机制)

  • 微服务下的一大主要难点在于:如何为服务组件选择合适的粒度层次。如果粒度过大则无法享受到微服务的好处:部署、拓展、测试、解耦。而过细的粒度则会导致服务调度问题,也就是说,这样会使得我们精干的微服务架构朝着SOA发展,过重,内部引入复杂性、困惑、成本、冗余。
  • 如果你发现从用户界面到API层需要做服务调度,那很有可能就是粒度过细了。同理,如果对单一请求你需要做内部服务通信,那极有可能服务组件是过于细粒度了,亦或是从业务功能的角度没有分区到位。
  • 内部服务的通信,会强制产生组件间没必要的耦合,这种情况可以用共享库表来解决。比如,如果一个服务组件负责处理订单,用到了客户信息,就可以去数据库查询必要的数据,而不是调用客户服务组件。
  • 共享库表可以处理需要的信息,但共享功能如何?如果某个服务组件用到了另一个服务组件的功能,有些时候可以将公用的功能复制过去,但这样破坏了DRY原则。这种做法是在微服务架构内常用的一种折中方案,干掉了服务组件的耦合,而追求了代码与部署上的独立。有些代码重复了,但相对少。
  • 如果你发现当前服务组件的粒度仍然无法避免组件的调度,很有可能这种架构不适用于你当前的项目。微服务有着天然的分布式的特性,所以很难去维护跨服务组件中的整体事务特性。这种情况需要借助事务补偿框架,来做到回滚一致,这样就给我们相对简洁的架构增加了很大的复杂性。

考量

  • 微服务解决了很多巨石应用或者SOA下的问题。因为主要的业务组件被拆分为了更小、更独立的模块,部署也可以隔离,这增加了应用的健壮性、拓展性、对持续交付更好的支持程度。
  • 另一个好处是,可以做到实时生产部署,这样就没必要像之前那样发布一次就兴师动众了。因为变化都被隔离到了某个特定的服务组件内部,所以部署只涉及相关的小组件。如果你只有一个服务组件的实例,可以针对用户界面应用做一些定制化,去检测热部署,并且将用户重定向到一个错误页面或者等待页面。或者,也可以部署时替换实例,这样也做到了部署周期内的持续可用性。(分层架构内很难实现)
  • 最后一点需要考虑的,是微服务架构天然具备分布式特点,这样就会有一些与事件驱动架构共同的毛病存在,比如合约的创建、维护性、管理性、远程访问可用性、远程访问校验问题。

模式分析

  • 下面的表格包括了对微服务架构通用特点的评分、分析。

  • 整体敏捷性:

    • 评分:高。
    • 分析:由于部署是分开的组件单元,每个服务组件被隔离了,这就促成了更快速简介的部署过程。同时也是解耦的,也易于做变更。
  • 部署容易度:

    • 评分:高。
    • 分析:部署的特点是由于细粒度的规划以及远程服务的独立所造就的。每个服务隔离部署,也能做到随时随地更新上线。部署时的风险也降低了,即使某个实例down掉也只影响内部的一小部分功能。
  • 可测试度:

    • 评分:高。
    • 分析:由于业务功能也被隔离开来,测试范围就变小,测试的目标更加明确。回归测试也更加可用、易用。由于服务组件解耦的特点,也帮助测试链路缩短,应对变化更便捷。
  • 性能:

    • 评分:低。
    • 分析:尽管你的实现可能性能很赞,但这个架构设计不是趋于性能的,因为必须做到分布式。
  • 可拓展性:

    • 评分:高。
    • 分析:由于应用被拆分到了多个可部署的组件,每个服务组件都可以单独拓展,应用的拓展更加自如。举例来说,由于股票交易后台的用户量不多,所以管理后台可以不变动,而只需拓展交易前台的服务,这样就可以满足性能要求。
  • 部署容易度:

    • 评分:高。
    • 分析:功能被拆分为单独的服务组件,部署也因此变得更加容易,体量更小、独立的范围。某个服务组件的部署也不会影响其他组件的正常运行,部署团队间就无需有多余的协调工作。

第五章:基于空间的架构

  • 大多数web应用遵循了大致相同的请求流:一个来自浏览器的请求先到达web服务器,接着到达应用服务器,最后到达数据库服务器。对很多小型应用来说,这种架构运行良好,但随着用户数增多瓶颈也就出现了,压力会逐层表现出来。应对这种问题一种通俗的解决方案是拓展web服务器,很多时候这种低成本的方案也会奏效。但,面对大量用户的负载,仅仅拓展web应用是不够的,压力还是会传递到服务层级。最后会导致一个三角拓扑,即web容器最容易拓展,数据库服务最难以拓展。
  • 面对大量用户高并发的访问,数据库就必须解决并发处理事务的问题。尽管很多缓存技术、数据拓展产品可以帮助解决问题,但应用处理并发负载一直是一个遗留难题。
  • 基于空间的架构就是为了解决拓展以及并发难题的。同样也适用于变化性高、不可预测的并发量的情况。从架构上解决极端的并发问题要远远优于拓展应用服务、在不可拓展的架构内改进缓存技术。

模式描述

  • 基于空间的架构(有时候也指的是云架构模式)将限制应用拓展的因素最小化了。这种架构的名字来源于元组空间,即分布式共享内存。通过移除了中心数据库的限制以及使用复制内存中的数据表格达到了高拓展性。应用数据放在内存中,通过正在活动的处理组件进行复制操作。处理组件可以随着用户负载的增多减少而做到动态开启、关闭,因而做到了可变情况的拓展。也正由于去掉了中心化数据库,数据库的性能瓶颈也就没了,拓展性几乎没有上限。
  • 大多数适用该模式的应用通常就是标准的网站,他们从浏览器接收请求并且执行一些操作。一个拍卖竞标网站就是个很好的例子。这种网站不断地从浏览器收到用户竞标的请求,当为某个物品竞标后,后台就要记录一个时间戳,并且为这个物品更新竞标的信息,最后将这个信息返回给浏览器。
  • 有两类组件是实现了该架构模式:
    • 处理原件(组件); 包含了应用的组件(或者应用组件的一部分),这包括了基于web的组件以及后端业务逻辑组件。处理原件的内容会随着应用类型而改变,小的web应用往往会以单独的处理原件部署,而大的应用会根据功能拆分为多个处理原件。处理原件内通常包含了应用模块,同时伴随着内存数据块、可选的异步持久化存储(防止failover)。还会有一个复制引擎,通过虚拟化中间件来复制改变的数据(改变来自于其他活动的处理原件)。
    • 虚拟化中间件; 负责家政、通信问题。包含了控制多路数据同步、请求的组件。在其内部有:消息块、数据块、处理块、部署管家。这些组件在下一部分会进行细节阐述,可以自定义也可以购买第三方产品。
  • 如下图描绘了基于空间架构的基本样子,以及他的相关组件。 space-based_architecture_pattern

模式力学

  • 下图基本说明了处理原件内部包含了应用模块、内存数据、可选的异步持久化存储(防failover)、数据复制引擎。 processing-unit_component
  • 虚拟化中间件往往就是架构中的管理器,他要负责管理请求、session、数据复制、分布式请求处理、处理原件部署。内部有四大架构组件:
    • 消息块 messaging-grid_component 管理请求以及session,他会决定哪个处理原件来处理这个请求,并且把请求推过去。消息块的复杂度可以从简单的循环算法到判断下个可用算法的程度,后者会根据请求被哪个处理原件处理着。
    • 数据块 data-grid_component 数据块是该模式下最重要的组件了。他要与数据复制引擎交互,当数据开始更新时,来管理数据的复制操作。由于前面的消息块可以将请求推到任何一个可用的处理原件,那对应的处理原件就必须保证拥有内存中数据块中的数据。图中说明了数据复制是同步操作,事实上他是以异步并行的方式进行的,非常迅速,完成数据同步只需要微妙ms级别。
    • 处理块 processing-grid_component 处理快组件是可选的,管理分布式请求处理,当有多个处理原件活动于应用的部分时,他就需要工作了。如果一个请求需要协调,比如一个订单处理原件与客户处理原件做交互,这就是处理快协调工作的内容了。
    • 部署管家 部署管家基于加载条件来管控动态的处理原件的开闭。它需要持续监控响应时间、用户负载,当负载增加时就开启新的处理原件,同理负载下降就关闭。应用内想要做到高可用的拓展性,部署关键是一个极为重要的组件。

考量

  • 基于空间的架构模式是一种实现起来复杂、高开销的模式。对于小型应用,如果负载是变动的,就适用于这种模式。(例如社媒网站、拍卖竞标网站)但是对于传统的大型关系型数据库应用来说,他们有着大量操作的数据,这种情况是不适用的。
  • 尽管这种模式不需要中心化的数据存储(往往用于初始化内存数据块、异步持久化数据更新),但实践中通常会创建隔离的分区,用于隔离易变数据、常用的事务型数据,以此降低每个处理原件使用的内存。
  • 架构的另一个名字:基于云的架构,处理原件(以及虚拟化中间件)并不一定要放在云服务或者PaaS上,它可以就部署在本地的服务商,所以不要被名字误导。
  • 从实现的角度来看,你可以通过很多第三方的产品来实现,例如:GemFire, JavaSpaces, GigaSpaces, IBM Object Grid, nCache, and Oracle Coherence。这些实现从开销与拓展性(特别是指数据复制次数)变化特别大,作为一名架构师,你应该首先确定你具体的目标以及需求

模式分析

  • 同理,下面还是有对该架构模式的评分、分析。附录A可以看到横评。
  • 整体敏捷性:
    • 评分:高。
    • 分析:处理原件(应用的部署实例)可以被很快的收放,应用面对变化的用户负载具备了很好的应对能力。使用了这种架构的应用往往对代码变动有着很好的响应能力,原因是其较小的应用体积以及动态变化的特性。
  • 部署容易度:
    • 评分:高。
    • 分析:尽管该架构模式不是解耦、分布式的,但他们是动态的,以及基于服务的云工具,使得应用可以很容易推送到服务器,简化了部署过程。
  • 可测试性:
    • 评分:低。
    • 分析:在测试环境做到高用户负载成本很高,使得很难去测试应用的负载拓展性。
  • 性能:
    • 评分:高。
    • 分析:高性能是通过内存数据访问、缓存机制做到的。
  • 拓展性:
    • 评分:高。
    • 分析:高拓展性能来源于其不依赖中心化数据库的特性,因此从根源直接干掉了影响拓展的因素。
  • 开发容易度:
    • 评分:低。
    • 分析:复杂的缓存以及内存数据产品使得这一模式难以实现,通常也是对相关工具、产品的不熟悉。进一步讲,想要合理使用这种架构模式,就必须做更多投入。

附录A:模式分析总结

pattern-analysis_summary

  • 上图对各项指标、各种模式做了小结。这里会对你决定使用哪种架构模式提供一定帮助。比如,如果你架构首要关注的事拓展性,那可以看下这个图表并且细研究下事件驱动模式、微服务模式、基于空间的架构模式。同理,如果你选择了分层架构,你也可以了解到:部署、性能、拓展性可能会成为瓶颈。
  • 这里只是提供了一些基本的了解,对于选择架构还需要研读更多内容。针对你的环境必须做深度分析,包括基础支持、开发者技能栈、项目预算、项目期限、应用规模等。选择一个对的架构是很重要的,因为一旦选好开始了开发,后期就很难去改架构了。

关于作者

  • Mark Richards是一名架构、设计、实现方面的老司机,参与实现了微服务、SOA、J2EE中的分布式系统等。从1983年就进入了软件工业界,在应用、整合、企业架构方面都有如虎一般的操作。1999-2003期间他担任了New England Java Users Group的主席。也是多个技术书籍、视频的作者,包括了《So ware Architecture Fundamen‐ tals (O’Reilly video)》、《Enterprise Messaging (O’Reilly video)》、《Java Message Service, 2nd Edition (O’Reilly)》、《97 Things Every Software Architect Should Know (O’Reilly)》等作品。他拥有计算机科学与技术的硕士学位,以及来自IBM, Sun, The Open Group, and BEA的多个架构师、开发者认证。他也是No Fluff Just Stuff (NFJS) Symposium系列的常用会议演讲者,在全世界对不同的企业相关的技术话题上演讲过不下一百次。不工作的时候,他喜欢去White Mountains or along the Appalachian Trail远足。

Ref