对于开发人员来说,在没有正规架构的情况下开始编写应用程序是很常见的。如果没有清晰且定义良好的体系结构,大多数开发人员和架构师通常会采用传统的分层体系结构模式(也被称为N层体系结构),将源代码模块分离到各个包中,进而形成所谓的分层。遗憾的是,这样做的结果只会得到一组没有组织的源码模块,这些模块之间缺乏清晰的角色、职责和关系。
缺乏正规架构的应用程序通常是强耦合的、脆弱的、难以更改的,且没有明确的愿景或方向。因此,如果不完全理解系统中每个组件和模块的内部工作原理,就很难确定应用程序的体系结构特征。关于部署和维护的这些基本问题就很难回答:架构是否可伸缩?应用程序的性能特征是什么?应用程序对于需求变更的响应是否容易?应用程序的部署特征是什么?架构的响应性如何?
架构模式有助于定义应用程序的基本特征和行为。比如,有些架构模式适用于需要高可伸缩性的应用程序,而有些架构模式适用于需要高敏捷性的应用程序。为了选择满足特定业务的需求和目标,了解每种架构模式的特征、优缺点是很有必要的。
作为架构师,你必须始终能证明你的架构决策是正确的,特别是在选定架构模式或方法时。
最常见的架构模式是分层架构模式,即N层架构模式。这种模式是大多数Java EE应用程序事实上的标准,因此被多数架构师、设计人员和开发人员所熟知。很多公司内的传统IT通信和组织都采用分层架构,因此这种架构模式也成为多数业务应用程序开发的自然选择。
分层架构模式中的组件被组织到各个水平层中,每一层在应用程序中执行特定的角色(例如,表示逻辑或业务逻辑)。尽管分层架构模式没有指定必须存在于架构中的层的数量和类型,但大多数分层体系结构由四个标准层组成:表示层、业务层、持久化层和数据库层(图1-1)。在某些情况下,业务层和持久化层被组合成一个单一的业务层,特别是当持久化逻辑(例如SQL或HSQL)嵌入到业务层组件中时。因此,较小的应用程序可能只有三层,而较大或更复杂的业务应用程序可能包含五个层或更多层。
Figure 1-1. Layered architecture pattern
分层架构模式的每一层在应用程序中都有特定的角色和职责。例如,表示层负责处理用户接口,而业务层负责处理与请求相关的具体业务。该架构模式中的每一层都围绕满足特定业务请求所需完成的工作形成了一个抽象。例如,表示层不需要知道或关心如何获取客户数据;它只需要在屏幕上以特定格式显示该信息。类似地,业务层不需要关心如何格式化客户数据以便在屏幕上显示,甚至不需要关心客户数据来自何处;它只需要从持久层获取数据,对数据执行业务逻辑(例如,计算值或聚合数据),并将该信息传递到表示层。分层体系结构模式的强大功能之一是组件之间的关注点分离。特定层中的组件只处理属于该层的逻辑。例如,表示层中的组件只处理表示逻辑,而驻留在业务层中的组件只处理业务逻辑。这种类型的组件分类使得在你的架构中构建有效的角色和责任模型变得容易,并且由于定义良好的组件接口和有限的组件范围,也使得应用这种架构模式开发、测试、治理和维护应用程序变得容易。
注意在图1-2中,该架构中的每一层都被标记为关闭的。这是分层架构模式中一个非常重要的概念。封闭的层意味着当请求从一层移动到另一层时,它必须通过它下面的一层才能到达更下面的一层。例如,来自表示层的请求必须先经过业务层,然后到达持久层,最后才能到达数据库层。
Figure 1-2. Closed layers and request access
那么为什么不允许表示层直接访问持久层或数据库层呢?毕竟,从表示层直接访问数据库要比通过一堆不必要的层来检索或保存数据库信息快得多。这个问题的答案在于一个被称为层隔离
的关键概念。
层隔离概念意味着在架构的某一层中所做的更改通常不会影响其他层中的组件。如果你允许表示层直接访问持久层,那么持久层中的SQL如果变更了,可能会影响到业务层和表示层,这样就会产生一个紧耦合的应用程序,组件之间会有很多依赖关系。这种架构会变的很难维护。
层隔离的概念也意味着每一层都独立于其他层,因此不需要了解架构中其他层的内部工作原理。
虽然封闭的层有助于层隔离,也有助于隔离架构内的更改,但有时使某些层开放也是有意义的。例如,假设你想向架构中添加一个共享服务层,这层包含很多业务层中组件要访问的公共服务组件(例如,数据和字符串实用程序类或审计和日志类)。在这种情况下,创建服务层通常是一个好主意,因为从架构上讲,它将对共享服务的访问限制在业务层(而不是表示层)。如果没有单独的层,在架构上就没有任何东西可以限制表示层访问这些公共服务,因此也就很难控制这种访问限制。
在这个例子中,新的服务层可能位于业务层下面,该服务层中的组件无法被表示层访问。然而,这就产生了一个问题,业务层必须通过服务层才能到持久层,这显然没啥意义。这是分层架构中的一个老问题了,可以通过创建一个开放层来解决。
如图1-3所示,在这种情况下,服务层被标记为开放的,这意味着请求可以绕过这个开放层,直接转到它下面的层。在下面的示例中,由于服务层是开放的,所以业务层现在可以绕过它直接进入持久层,这非常有意义。
Figure 1-3. Open layers and request flow
利用开放层和封闭层的概念有助于定义架构中层和请求流之间的关系,也为设计人员和开发人员提供必要的信息,以理解架构内的各种层访问限制。如果不能记录或正确地交流架构中的哪些层是开放的,哪些层是关闭的(以及为什么),通常会产生非常难以测试、维护和部署的紧密耦合且脆弱的架构。
为了阐述分层架构是如何工作的,考虑来自用户的一个检索某个客户信息的请求(Figure 1-4)。黑色箭头表示向下流到数据库的请求,红色箭头表示向上流到屏幕显示数据的响应。这个例子中,客户信息包括客户数据和订单数据(客户下的订单)。
Figure 1-4. Layered architecture example
客户屏幕负责接受请求并显示客户信息。它不知道数据在哪里、如何检索数据,也不知道必须查询多少个数据库表才能获得数据。一旦客户屏幕接收到获取特定个人的客户信息的请求,它就会将该请求转发到customer delegate(客户委托)模块。customer delegate模块知道业务层中的哪些模块可以处理这个请求,还知道如何访问业务层中的这个模块以及这个模块需要什么数据。业务层中的customer object(客户对象)负责聚合请求的所有信息。该模块调用持久化层中的customer dao (data access object) 模块获取客户数据,也调用order dao获取订单信息。customer dao和order dao模块依次执行SQL语句来检索相应的数据并将其传递回业务层中的customer object。一旦customer object收到数据,它就会聚合数据并将该信息回传给customer delegate,然后customer delegate将这些数据再传回客户屏幕呈现给用户。
从技术角度来看,这些模块实际上有几十种实现方式。
分层架构模式是一种可靠的通用模式,使其成为多数应用程序的良好起点,特别是当你不确定哪种架构模式最适合你的应用程序时。然而,当选择这种架构模式时,有些方面需要考虑:
首先要关注的是所谓的architecture sinkhole anti-pattern(反模式)。此反模式描述了这样一种情况,即请求在架构的多个层中流动,简单的做透传处理,在每一层中执行很少或不执行逻辑。例如,假设表示层响应用户检索客户数据的请求。表示层将请求传递给业务层,业务层将请求传递给持久层,然后持久层对数据库层进行简单的SQL调用以检索客户数据。然后,数据被一路传回堆栈,不需要额外的处理或逻辑聚合、计算或转换数据。
每个分层架构都会至少有一些落入反模式的场景。然而,关键在于分析属于这一类请求的百分比。遵循8-2规则通常是确定是否正在经历反模式的好办法。典型的情况是,大约20%的请求是简单的透传,80%的请求具有与请求相关的业务逻辑。但是,如果你发现这个比例相反,并且大多数请求都是简单的透传处理,那么你可能需要考虑使一些架构中的层开放,请记住,由于缺乏层隔离,控制更改将更加困难。
另外一个注意事项是,即使你将表示层和业务层分割为独立的部署单元,这个模式也更适用于单块应用程序。虽然对于某些应用程序来说,这可能不是一个问题,但它确实在部署、一般健壮性和可靠性、性能和可伸缩性方面提出了一些潜在的问题。
Overall agility(整体敏捷性)
评级:低
分析:整体敏捷性是快速响应环境不断变化的能力。虽然可以通过此模式的隔离层特性隔离变更,但在此体系结构模式中进行更改仍然很麻烦且耗时,因为大多数实现都具有单片特性,并且通常使用此模式的组件之间存在紧密耦合。
Ease of deployment(易部署性)
评级:低
分析:受该架构模式具体实现的影响,部署可能会称为一个问题,特别是对于较大的应用程序。一个组件的小更改,就可能需要重新部署整个应用程序或者应用程序的很大一部分。因此,这种模式不容易形成持续交付,这就进一步降低了易部署性方面的总体评级。
Testability(易测性)
评级:高:
分析:由于组件从属于该架构模式中的特定层,所以其他层是可以被mock或stub的,这就使得分层架构模式相对容易测试。开发人员可以模拟表示层 中的组件来隔壁业务组件中的测试,也可以模拟业务组件来测试表示层。
Performance(性能)
评级:低:
分析:虽然某些分层架构确实运行的很好,但由于必须通过架构的多层来实现业务请求的低效率,这种模式并不适合于高性能应用程序。
Scalability(可扩展性/可伸缩性)
评级:低:
分析:由于这种模式的紧密耦合和通用实现的趋势,使用这种体系结构模式构建的应用程序通常难以扩展。您可以通过将层拆分为单独的物理部署或将整个应用程序复制到多个节点来扩展分层体系结构,但总体而言,粒度太宽,使得扩展成本很高。
Ease of development(可开发性)
评级:高:
分析:开发的容易程度得到了相对较高的分数,主要是因为这种模式非常有名,而且实现起来并不太复杂。因为大多数公司通过分层(表示、业务、数据库)分离技术来开发应用程序,使得这种模式成为大多数业务应用程序开发的自然选择。公司的沟通和组织结构与软件开发方式之间的联系被称为Conway’s law。你可以谷歌“Conway’s law”来获得更多关于这种迷人相关性的信息。
事件驱动架构模式是一种流行的分布式异构模式,用于生成高可伸缩的应用程序。它还具有很强的适应性,既可以用于小型应用程序,也可以用于大型复杂应用程序。事件驱动架构由高度解耦的、单一用途的事件处理组件组成,这些组件异步接收和处理事件。
事件驱动架构模式由两个主要拓扑结构组成,mediator(中介) 和 broker(代理)。中介拓扑通常用于需要通过中心中介在事件中编排多个步骤的情况,而代理拓扑则用于希望在不使用中心中介的情况下将事件链接在一起的情况。由于这两种拓扑结构的架构特征和实现策略不同,因此了解每种拓扑结构以了解哪种拓扑结构最适合您的特定情况是非常重要的。
Mediator Topology对于具有多个步骤并且需要某种级别的编排来处理事件的事件非常有用。例如,进行股票交易的单个事件可能需要您首先验证交易,然后根据各种合规规则检查该股票交易的合规性,将交易分配给经纪人,计算佣金,最后与该经纪人进行交易。所有这些步骤都需要某种程度的编排,以确定步骤的顺序,以及哪些步骤可以串行执行,哪些步骤可以并行执行。
在Mediator Topology中,有四种主要类型的架构组件:event queues(事件队列), event mediator(事件中介器), event channels(事件管道), 和 event processors(事件处理器)。事件流从客户端向事件队列发送事件开始,该事件队列用于将事件传输到event mediator。事件中介器接收初始事件,并通过向事件通道发送额外的异步事件来编排该事件,以执行流程的每个步骤。事件处理器监听事件通道,从事件中介接收事件,并执行特定的业务逻辑来处理事件。如图2-1所示。
Figure 2-1. Event-driven architecture mediator topology
在事件驱动的体系结构中,通常有十几个到几百个事件队列。该模式没有指定事件队列组件的实现;它可以是消息队列、web服务端点或它们的任何组合。
此模式中有两种类型的事件: an initial event(初始事件)和a processing event(处理事件)。初始事件是被中介器接受的原始事件,而处理事件是由中介器生成并由事件处理组件接收的事件。事件中介器组件负责编排初始事件中包含的步骤。对于初始事件中的每一步,事件中介器向事件通道发送一个特定的处理事件,然后由事件处理器接收和处理。需要注意的是,事件中介实际上并不执行处理初始事件所需的业务逻辑; 相反,它知道处理初始事件所需的步骤。
事件中介器使用事件通道,将与初始事件中的每个步骤相关的特定处理事件异步传递给事件处理器。事件通道既可以是消息队列,也可以是消息主题。其中,消息主题常与中介拓扑一起使用,因此处理事件可以由多个事件处理器处理(每个处理器根据接收到的处理事件执行不同的任务)。
事件处理器组件包含处理“处理事件”所需的应用程序业务逻辑。事件处理器是自包含的、独立的、高度解耦的体系结构组件,在应用程序或系统中执行特定的任务。虽然事件处理器组件的粒度可以从细粒度(例如,计算订单上的销售税)到粗粒度(例如,处理保险索赔)不等。但需要特别记住,一般来说,每个事件处理器组件都应该单独形成一个业务任务,而不依赖于其他事件处理器。
事件中介器可以以多种方式实现。作为架构师,您应该了解这些实现选项中的每一个,以确保您为事件中介器选择的解决方案符合您的需求和要求。
事件中介器最简单和最常见的实现是通过开源集成中心,如Spring Integration、Apache Camel或Mule ESB。这些开源集成中心的事件流通常是通过Java代码或DSL(领域特定语言)实现的。对于更复杂的中介和编排,可以将BPEL(业务流程执行语言)与BPEL引擎(如开源Apache ODE)结合使用。BPEL是一种标准的类xml语言,用于描述处理初始事件所需的数据和步骤。对于需要更复杂的业务流程的大型应用程序(包括涉及人工交互的步骤),您可以使用业务流程管理器(BPM)(如jBPM)实现事件中介器。
了解您的需求并将其与正确的事件中介器实现相匹配,对于使用此拓扑的任何事件驱动体系结构的成功都至关重要。使用开源集成集线器来执行非常复杂的业务流程管理编排是一种失败的方法,就像实现BPM解决方案来执行简单的路由逻辑一样。
为了说明中介拓扑如何工作,假设您通过一家保险公司投保,并决定迁移。在这种情况下,初始事件可能被称为类似于relocation event(重新定位事件)的东西。处理重定位事件所涉及的步骤包含在事件中介器中,如图2-2所示。对于每个初始事件步骤,事件中介都会创建一个处理事件(例如,更改地址、重新报价等),将该处理事件发送到事件通道,并等待相应的事件处理器(例如,客户流程、报价流程等)处理“处理事件”。这个过程会一直持续,直到初始事件中的所有步骤都被处理完毕。The single bar over the recalc quote and update claims steps in the event mediator indicates that these steps can be run at the same time.
Figure 2-2. Mediator topology example
代理拓扑与中介拓扑的不同之处在于没有中心事件中介器;相反,消息流通过轻量级消息代理(例如ActiveMQ、ActiveMQ)以链式方式分布在事件处理器组件之间。当你有一个相对简单的事件处理流并且不需要(或不需要)中心事件编排时,此拓扑非常有用。
在代理拓扑中有两种主要类型的架构组件:broker(代理组件)和event processor(事件处理器组件)。代理组件可以集中或联合,并包含事件流中使用的所有事件通道。代理组件中包含的事件通道可以是消息队列、消息主题或两者的组合。
如图2-3所示。从图中可以看出,没有控制和编排初始事件的中心事件中介器组件; 相反,每个事件处理器组件负责处理一个事件,并发布一个指示它刚刚执行操作的新事件。例如,平衡股票投资组合的事件处理器可能会接收一个称为股票拆分的初始事件。基于初始事件,事件处理器可能会执行一些投资组合再平衡,然后向broker发布一个名为投资组合再平衡的新事件,随后这个新事件由不同的事件处理器接收。请注意,有时事件由事件处理器发布,但没有被任何其他事件处理器提取。这在开发应用程序或提供未来的功能和扩展时,是很常见的。
Figure 2-3. Event-driven architecture broker topology
为了说明代理拓扑如何工作,我们将使用与中介拓扑中相同的示例(投保人移动)。由于在代理拓扑中没有接收初始事件的中心事件中介器,因此客户-流程组件直接接收事件,更改客户地址,并发送一个表示更改了客户地址的事件(例如,更改地址事件)。在本例中,有两个事件处理程序对更改地址事件感兴趣: the quote process(报价流程)和the claims process(索赔流程)。报价处理组件根据地址更改重新计算新的汽车保险费率,并向系统的其余部分发布一个事件,指示它所做的工作(例如,recalc报价事件)。报价处理组件根据地址更改重新计算新的汽车保险费率,并向系统的其余部分发布一个事件,指示它所做的工作(例如,重新报价事件)。另一方面,索赔处理组件接收相同的更改地址事件,但在本例中,它更新了未偿付的保险索赔,并将事件作为更新索赔事件发布到系统。然后这些新事件被其他事件处理器组件拾取,事件链继续贯穿整个系统,直到没有针对特定的初始事件发布更多的事件。
Figure 2-4. Broker topology example
从图2-4中可以看到,代理拓扑完全是关于执行业务功能的事件链接。理解代理拓扑的最佳方法是将其视为接力赛。在接力赛中,运动员拿着接力棒跑一段距离,然后把接力棒交给下一个运动员,以此类推,直到最后一个运动员越过终点线。在接力赛中,一旦运动员交出接力棒,她就结束了比赛。代理拓扑也是如此:事件处理器一旦交出事件,就不再参与特定事件的处理。
事件驱动的体系结构模式是一种相对复杂的实现模式,这主要是由于它的异步分布式特性。在实现此模式时,必须解决各种分布式体系结构问题,例如远程进程可用性、缺乏响应性以及在代理或中介失败时的代理重连逻辑。
在选择这种架构模式时需要重视的一个因素是单个业务流程缺少原子事务。由于事件处理器组件是高度解耦和分布式的,因此在它们之间维护事务工作单元非常困难。因此,在使用这种模式设计应用程序时,必须不断考虑哪些事件可以独立运行,哪些事件不能独立运行,并相应地规划事件处理器的粒度。如果您发现需要跨事件处理器分割单个工作单元——也就是说,如果您使用单独的处理器处理某个本应是不可分割事务的事务——这个模式可能不是适合您的应用程序。
Overall agility(整体敏捷性)
评级:高
分析:整体敏捷性是快速响应环境不断变化的能力。由于事件处理器组件是单一用途的,并且与其他事件处理器组件完全解耦,变更通常与一个或 几个事件处理器隔离,并且可以在不影响其他组件的情况下快速进行。
Ease of deployment(易部署性)
评级:高
分析:总的来说,由于事件处理器组件的去耦特性,这个模式相对容易部署。代理拓扑结构往往比中介拓扑结构更容易部署,主要是因为事件中介 组件与事件处理器紧密耦合: 事件处理器组件的更改可能也需要事件中介的更改,对于任何给定的更改都需要部署两者。
Testability(易测性)
评级:低
分析:虽然单个单元测试并不太难,但它确实需要某种专门的测试客户端或测试工具来生成事件。测试也因为这种模式的异步特性而变得复杂。
Performance(性能)
评级:高
分析:当然,由于涉及到所有的消息传递基础设施,实现一个事件驱动的体系结构并不能很好地执行是有可能的,但总的来说,该模式通过其异步 功能来实现高性能;换句话说,执行解耦的并行异步操作的能力超过了消息排队和解排队的成本。
Scalability(可扩展性)
评级:高
分析:在这种模式中,通过高度独立和解耦的事件处理器自然可以实现可伸缩性。每个事件处理器都可以单独伸缩,从而实现细粒度的可伸缩性。
Ease of development(可开发性)
评级:低
分析:由于模式和契约创建的异步特性,以及需要在代码中为响应不及时的事件处理器和失败的代理提供更高级的错误处理条件,因此开发可能有些复杂。
微内核架构模式(有时称为插件架构模式)是实现基于产品的应用程序的自然模式。基于产品的应用程序是作为典型的第三方产品打包并提供版本供下载的应用程序。然而,许多公司也像软件产品一样开发和发布他们的内部业务应用程序,包括版本、发布说明和可插拔功能。这些也很适合这个模式。微内核架构模式允许您将额外的应用程序功能作为插件添加到核心应用程序,提供可扩展性。
微内核架构主要包含两类组件:a core system(核心系统) 和 plug-in modules(插件模块)。应用程序逻辑被拆分到独立的插件模块和核心系统中,以支持扩展性、灵活性,应用程序特性隔离和自定义处理逻辑的能力。图3-1展示了基本的微内核架构模式。
Figure 3-1. Microkernel architecture pattern
微内核架构中的“核心系统”通常仅包含使系统具备可操作的最小功能。许多操作系统都实现了微内核架构模式,这也是这个模式名字的由来。从应用程序业务的角度来看,核心系统通常被定义为通用业务逻辑,没有针对特殊情况、特殊规则或复杂条件处理的自定义代码。
插件模块都是独立的组件,其中包含特定的处理、附加特性和用于增强或扩展核心系统以产生附加业务功能的自定义代码。通常地,插件模块之间应该是彼此独立的,但是你也可以设计一个需要其他插件介入的插件。不管怎样,保持插件间最小的交互代价,以避免依赖问题,这一点是很重要的。
核心系统需要知道哪些插件是可用的以及如何获取这些插件。一种常见的实现方式是通过某种插件注册表。这个注册表包含了每个插件模块的信息,比如名字,数据协议,远程访问协议细节(依赖于插件如何被连接到核心系统)。例如,标记高风险税务审计项目的税务软件插件注册表项,可能包含服务名称(AuditChecker),数据协议(input data and output data)和格式化协议(XML)。如果这个软件可以通过SOAP访问,它可能还含有WSDL(Web Services Definition Language)。
插件模块可以通过多种方式与核心系统建立连接,比如OSGI(open service gateway initiative),消息,web服务,或者直接点对点绑定(比如,对象实例化)。连接类型取决于你正在构建的应用程序类型(小产品还是大业务应用)和你的具体需求(比如,单体部署还是分布式部署)。这个架构模式除了要求插件模块之间必须保持彼此独立外,并没有指定上述的实现细节。
微内核架构的最好示例是Eclipse IDE。下载Eclipse后,它只提供一个漂亮的编辑器。你可以给它不断的添加插件,它的功能也会越来越多。另一个应用微内核架构的常用产品是互联网浏览器。
基于产品的软件例子数不胜数,但大型业务应用程序呢?微内核体系结构也适用于这些情况。为了说明这一点,让我们使用另一个保险公司的例子,这次涉及保险索赔处理。
索赔处理是一个非常复杂的过程。每个州对于保险索赔中允许和不允许的内容都有不同的规则和规定。例如,如果你的挡风玻璃被石头损坏,一些州允许免费更换挡风玻璃,而其他州则不允许。这为标准索赔流程创造了几乎无限的条件集。
不足为奇的是,大多数保险索赔应用程序利用大型复杂的规则引擎来处理这种复杂性。然而,这些规则引擎可能会发展成一个复杂的大泥球,其中更改一个规则会影响其他规则,或者进行一个简单的规则更改需要大量的分析人员、开发人员和测试人员。使用微内核体系结构模式可以解决许多这些问题。
在图3-2中看到的文件夹堆栈表示索赔处理的核心系统。它包含保险公司处理索赔所需的基本业务逻辑,除非不进行任何自定义处理。每个插件模块都包含所代表州的特殊规定。在本例中,插件模块可以使用自定义源代码或单独的规则引擎实例来实现。无论采用何种实现,关键的是个别州的特殊规则和处理与核心索赔系统是分开的,可以添加、删除和更改,而对核心系统的其余部分或其他插件模块几乎没有影响。
Figure 3-2. Microkernel architecture example
微内核架构模式的一个优点是,它可以被嵌入或用作其他架构模式的一部分。比如,为了解决一个特定问题,你发现无法实现整个架构。这种情况瞎,你可以将微内核嵌入到你正在适用的架构模式中(比如,分层架构)。类似地,前面关于事件驱动架构中的事件处理器组件也可以使用微服务体系结构模式实现。
微内核架构模式为设计演变和开发提速提供了很好的支持。您可以首先生成一个可靠的核心系统,然后随着应用程序的逐步发展,添加特性和功能,而不必对核心系统进行重大更改。
对于基于产品的应用程序,微内核架构模式应该是您初始架构的首选,特别是对于那些随着时间的推移而发布附加功能并希望控制哪些用户获得哪些功能的产品。如果随着时间的推移,你发现这个模式不能满足你所有的需求,你也总是可以将你的应用程序重构为另一个更适合你特定需求的架构模式。
以下包含了对微内核架构模式中常见架构特征的评级和分析
Overall agility(整体敏捷性)
评级:高
分析:整体敏捷性是快速响应环境不断变化的能力。通过松散耦合的插件模块,可以很大程度地将变更隔离开来。而且,多数微核架构的核心系统会快速的趋于稳定,健壮,并且随着时间的推移也不需要太多的变更。
Ease of deployment(易部署性)
评级:高
分析:根据模式的实现方式,插件模块可以在运行时被动态的添加到核心系统上(比如,热插拔),最大限度地减少部署期间的停机时间。
Testability(易测性)
评级:高
分析:插件模块可以单独测试,并且在几乎不更改核心系统的情况下来模拟一些特别的特性。
Performance(性能)
评级:高
分析:虽然微内核模式本身并不适用高性能应用程序,但一般来说,大多数使用微内核架构模式构建的应用程序性能都很好,因为你可以自定义和简化应用程序,以达到只包含你需要功能的目的。JBoss 服务应用程序就是一个很好的例子: 通过它的插件架构,你可以将应用程序服务器精简到只保留那些你需要的功能,删除昂贵的不使用的功能,如远程访问、消息传递和消耗内存的缓存,CPU,线程和减慢应用的服务。
Scalability(可扩展性)
评级:低
分析:因为大多数微内核架构的实现都是基于产品的,通常尺寸较小,且作为独立单元实现,因此伸缩性不高。虽然根据实现插件模块的方式,可以在插件级别上提供可伸缩性,但总体而言,这种模式并适用于需要高伸缩性的应用程序。
Ease of development(可开发性)
评级:低
分析:微内核架构需要经过深思熟虑的设计和contract governance,因此实现起来相当复杂。Contract versioning、内部插件注册中心、插件粒度以及插件连接性的广泛选择都增加了实现此模式的复杂性。
微服务体系结构模式作为单片应用程序和面向服务的体系结构的可行替代方案,在行业中迅速获得了一席之地。因为这个体系结构模式仍在不断发展,所以业界对于这个模式到底是什么以及它是如何实现它还存在很多困惑。这一部分只提供关键概念和必要的基础知识。
第一个概念是 separately deployed units(独立部署单元)。如图4-1所示,微服务架构中的每个组件都是以独立单元部署的,且通过高效的交付管道和高度的应用程序与内部组件结构,会更容易的进行部署。
Figure 4-1. Basic Microservices architecture pattern
最重要的概念或许是 service component(服务组件)。与其在微服务架构中思考服务,不如在其中思考服务组件,它们的粒度可以从单个模块到应用程序的很大一部分不等。服务组件包含一个或多个模块(例如,Java类),它们要么代表单一用途的功能(例如,为特定城市或城镇提供天气预报),要么代表大型业务应用程序的独立部分(例如,股票交易安排或确定汽车保险费率)。设计合适的服务组件粒度级别是微服务架构中最大的挑战之一。这一挑战将在下面的服务组件编制小节中更详细地讨论。
另一个关键概念是,它是一个分布式架构,这意味着架构中的所有组件彼此完全解耦,并通过某种远程协议访问(例如,JMS, AMQP, REST, SOAP, RMI等)。这种架构的分布式本质是它如何实现一些优越的可伸缩性和部署特性。
关于微服务架构的一个令人兴奋的事情是,它是从其他常见架构模式相关的问题演变而来的,而不是作为等待问题发生的解决方案而创建的。微服务架构风格是从两个主要来源发展而来的:使用分层架构模式开发的单体应用程序和通过面向服务的架构模式开发的分布式应用程序。从单体应用到微服务架构风格的演进路径主要是通过持续交付的开发来推动的,即从开发到生产的持续部署管道的概念,它简化了应用程序的部署。单体应用程序通常由紧密耦合的组件组成,这些组件是单个可部署单元的一部分,这使得更改、测试和部署应用程序变得繁琐且困难(因此出现了通常在大多数大型应用程序中常见的“每月部署”周期)。这些因素通常会导致脆弱的应用程序,每次部署新东西时都会崩溃。微服务体系结构模式通过将应用程序分离为多个可部署单元(服务组件)来解决这些问题,这些单元可以独立于其他服务组件单独开发、测试和部署。
导致微服务架构模式的另一个进化路径是在应用程序实现面向服务架构模式(SOA)时发现的问题。尽管SOA模式非常强大,并提供了无与伦比的抽象级别、异构连接、服务编排以及将业务目标与IT功能对齐的承诺,但它仍然复杂、昂贵、无处不在、难以理解和实现,并且通常对大多数应用程序来说是过度的。微服务体系结构风格通过简化服务的概念、消除编排需求以及简化对服务组件的连接和访问来解决这种复杂性。
虽然实现微服务体系结构模式的方法有几十种,但最常见和最流行的是三种主要拓扑: the API REST-based topology, application REST-based topology, and the centralized messaging topology.
基于REST API的拓扑通常对于暴露少量API且自包含的网站很有用。如图4-2所示,这种拓扑结构包含非常细粒度的服务组件,每个组件包含一到两个用于执行特定业务的功能模块,且不同服务组件中的模块互相独立。在这种拓扑中,这些细粒度的服务组件通常使用基于rest的接口进行访问,这些接口的实现通常单独部署在web的API层。在Yahoo,Google和Amazon中有很多目的简单的web服务都用到了这种拓扑结构。
Figure 4-2. API REST-based topology
基于REST的应用程序拓扑不同于基于API REST的方法,因为客户端请求是通过传统的基于web或胖客户端的业务应用程序屏幕接收的,而不是通过简单的API层。如图4-3所示,应用程序的用户界面层部署为单独的web应用程序,通过简单的基于rest的界面远程访问单独部署的服务组件(业务功能)。此拓扑中的服务组件与基于API REST的拓扑中的服务组件不同,因为这些服务组件往往更大、更粗粒度,并且只代表整个业务应用程序的一小部分,而不是细粒度的单一操作服务。这种拓扑对于复杂性相对较低的中小型业务应用程序很常见。
Figure 4-3. Application REST-based topology
微服务体系结构模式中的另一种常用方法是集中式消息传递拓扑。这种拓扑结构(如图4-4所示)类似于之前基于REST的应用程序拓扑结构,不同之处在于,这种拓扑结构使用轻量级的集中消息代理(例如,ActiveMQ, HornetQ等)。在查看此拓扑时,不要将其与面向服务的体系结构模式混淆或将其视为“SOA-Lite”,这一点非常重要。在这种拓扑结构中出现的轻量级消息代理不执行任何编排、传输或复杂的路由;相反,它只是访问远程服务组件的轻量级传输。
Figure 4-4. Centralized messaging topology
集中式消息传递拓扑通常出现在较大的业务应用程序或需要对用户界面和服务组件之间的传输层进行更复杂控制的应用程序中。这种拓扑结构优于简单拓扑结构的优点前面讨论的基于rest的拓扑,包括高级排队机制、异步消息传递、监视、错误处理以及更好的整体负载平衡和可伸缩性。通常与集中式代理相关的单点故障和体系结构瓶颈问题通过代理集群和代理联合(将单个代理实例拆分为多个代理实例,以根据系统的功能区域划分消息吞吐量负载)来解决。
微服务架构模式的主要挑战之一是为服务组件确定正确的粒度级别。如果服务组件太粗粒度,您可能没有意识到这种架构模式带来的好处(部署、可伸缩性、可测试性和松耦合)。然而,过于细粒度的服务组件将导致服务编排需求,这将迅速将你的微服务架构转变为重量级的面向服务的架构,包括通常在基于soa的应用程序中发现的所有复杂性、混乱性、费用和蓬松性问题。
如果你发现你需要在应用程序的user interface或API layer中编排你的服务组件,那么说明你的服务组件粒度太细了。类似地,如果你发现你需要在服务组件之间执行服务间通信来处理单个请求,也可能是你的服务组件粒度太细了,或者从业务功能的角度来看它们没有被正确划分。
服务间通信可能会导致组件之间产生不必要的耦合,但可以通过共享数据库来处理。例如,如果处理Internet订单的服务组件需要客户信息,它可以访问数据库来检索必要的数据,而不是在客户服务组件中调用功能。
共享数据库可以处理信息需求,但是共享功能呢?如果一个服务组件需要包含在另一个服务组件中的功能或对所有服务副组件通用的功能,您有时可以在服务组件之间复制共享的功能(因此违反了DRY原则:不要重复自己)。在大多数实现微服务架构模式的业务应用程序中,这是一种相当常见的实践,为了保持服务组件的独立性和分离部署,权衡重复业务逻辑的小部分的冗余。小型实用程序类可能属于这类重复代码。
如果您发现无论服务组件粒度级别如何,仍然无法避免服务组件编排,那么这可能不是适合您的应用程序的架构模式。由于这种模式的分布式本质,在服务组件之间(或服务组件之间)维护单个事务工作单元是非常困难的。这样的实践需要某种用于回滚事务的事务补偿框架,这就为这个相对简单而优雅的架构模式增加了极大的复杂性。
微服务架构模式解决了在单体应用程序和面向服务的架构中发现的许多常见问题。由于多数的应用程序组件被分割成较小的、独立的部署单元,使用微服务架构模式构建的应用程序通常更健壮,提供更好的可伸缩性,并且方便支持持续交付。
这种模式的另一个优点是,它提供了实时生产部署的能力,从而大大减少了传统的每月或周末“大爆炸”生产部署的需求。由于更改通常与特定的服务副组件隔离,因此只需要部署更改的服务组件。如果只有服务组件的单个实例,则可以在用户界面应用程序中编写专门的代码来检测活动热部署,并将用户重定向到一个错误页面或等待页面。或者,你可以在实时部署期间交换一个服务组件的多个实例,从而允许在部署周期内保持持续可用性(这在分层架构模式中是很难做到的)。
最后一个需要考虑的问题是,由于微服务架构模式是一种分布式架构,它也会遇到一些与事件驱动架构模式相同的负责问题,包括契约创建、维护和管理、远程系统可用性以及远程访问身份验证和授权。
以下包含了对微服务架构模式中常见架构特征的评级和分析
Overall agility(整体敏捷性)
评级:高
分析:由于分离部署单元的概念,变更通常隔离到单个服务组件,这样部署会快速简单。此外,使用这种模式构建的应用程序往往是非常松散耦合的,更加有助于更改的方便性。
Ease of deployment(易部署性)
评级:高
分析:由于远程服务的细粒度和独立性,微服务模式的部署特性非常高。服务通常作为单独的软件单元部署,从而能够在白天或晚上的任何时间执行“热部署”。总体部署风险也显著降低。在这种情况下,失败的部署能够更快地恢复,并且只对正在部署的服务操作有影响,不影响其他操作的继续操作。
Testability(易测性)
评级:高
分析:由于将业务功能分离和隔离到独立的应用程序中,测试可以确定范围,从而允许进行更有针对性的测试工作。针对特定服务组件的回归测试要比针对整个单片应用程序的回归测试容易得多,也更可行。此外,由于这种模式中的服务组件是松散耦合的,因此在开发过程中做出更改而破坏应用程序的另一部分的可能性要小得多,从而减轻了为一个小更改而测试整个应用程序的测试负担。
Performance(性能)
评级:低
分析:虽然你可以通过这种模式创建性能非常好的应用程序,但由于微服务架构模式的分布式特性,这种模式本身并不适合高性能应用程序。
Scalability(可扩展性)
评级:高
分析:因为应用程序被分割为单独部署的单元,所以每个服务组件都可以单独伸缩,从而允许对应用程序进行微调。例如,股票交易应用程序的管理区域可能不需要扩展,因为该功能的用户数量较少,但是交易配售服务组件可能需要扩展,因为大多数交易应用程序需要实现该功能的高吞吐量。
Ease of development(可开发性)
评级:高
分析:由于功能被隔离到独立的、截然不同的服务组件中,开发变得更容易,因为它的作用域更小、更隔离。开发人员对一个服务组件进行更改而影响其他服务组件的可能性大大降低,从而减少了开发人员或开发团队之间所需的协调。
大多数基于web业务的应用程序都遵循着同样的请求流程:来自浏览器的请求先到达web服务器,然后是应用服务器,最后是数据库服务器。虽然这种模式适用于用户量小的场景,但随着用户负载的增加,瓶颈开始出现,首先在web服务器层,然后是应用服务器层,最后是数据库服务器层。对于由用户负载增加而引起的瓶颈,通常的做法是扩充web服务器。这种是方法是相对简单和便宜的,而且有时候可以解决性能问题。然后,在多数高用户负载的情况下,扩充web服务器层只是将瓶颈向下转移到了应用服务器。扩充应用程序服务器会比扩充web服务器更复杂更贵,其实这又把瓶颈向下转移到了数据库服务器,而数据库服务器的扩充更困难更贵。即使你可以扩展数据库,你最终得到的是一个三角形的拓扑结构,三角形最宽的部分是web服务器(最容易扩展),最小的部分是数据库(最难扩展)。
在任何具有非常大并发用户负载的大容量应用程序中,数据库通常是限制可以并发处理多少事务的最终因素。虽然各种缓存技术和数据库扩展产品有助于解决这些问题,但事实上,向外扩展一个正常的应用程序以适应极端负载是一个非常困难的问题。
基于云的架构模式模式专门用于处理和解决可伸缩性和并发性问题。对于具有可变和不可预测并发用户量的应用程序来说,这也是一种有用的架构模式。从架构上解决极端和可变的可伸缩性问题通常比尝试向外扩展数据库或将缓存技术改造为不可伸缩的架构更好。
基于空间的模式(有时也称为云架构模式)最大限度地减少了限制应用程序伸缩性的因素。这种模式得名于元组空间的概念,即分布式共享内存的思想。高可伸缩性是通过去除中央数据库约束并使用复制的内存数据网格来实现的。应用程序数据保存在内存中,并在所有活动处理单元之间复制。处理单元可以随着用户负载的增加和减少而动态启动和关闭,从而解决可变可伸缩性问题。由于没有中央数据库,因此消除了数据库瓶颈,从而在应用程序中提供了近乎无限的可伸缩性。
符合这种模式的大多数应用程序都是标准网站,它们从浏览器接收请求并执行某种操作。竞价拍卖网站就是一个很好的例子。该网站通过浏览器请求不断接收来自互联网用户的出价。应用程序将接收特定项目的出价,用时间戳记录该出价,更新该项目的最新出价信息,并将信息发送回浏览器。
在这种架构模式中有两个主要组件: 处理单元和虚拟中间件。图5-1展示了基本的基于空间的架构模式及其主要架构组件。
Figure 5-1. Space-based architecture pattern
处理单元组件包含应用程序组件(或应用程序组件的部分)。这包括基于web的组件以及后端业务逻辑。处理单元的内容根据应用程序的类型而有所不同——较小的基于web的应用程序可能会部署到单个处理单元中,而较大的应用程序可能会根据应用程序的功能区域将应用程序功能拆分为多个处理单元。处理单元通常包含应用程序模块,以及内存中的数据网格和用于故障转移的可选异步持久存储。它还包含一个复制引擎,虚拟中间件使用该引擎将一个处理单元所做的数据更改复制到其他活动处理单元。
虚拟化中间件组件处理内部管理和通信。它包含控制数据同步和请求处理的各个方面的组件。虚拟化中间件包括消息传递网格、数据网格、处理网格和部署管理器。这些组件(将在下一节中详细描述)可以自定义编写,也可以作为第三方产品购买。
云架构模式的魔力在于虚拟中间件组件和包含在每个处理单元中的内存数据网格。图5-2展示了典型的处理单元架构,包括应用程序模块、内存数据网格、用于故障转移的可选异步持久性存储和数据复制引擎。
Figure 5-2. Processing-unit component
虚拟中间件本质上是架构的控制器,并且管理请求、会话、数据复制、分布式请求处理和进程单元部署。在虚拟中间件中有四个主要的组件:消息传递网格、数据网格、处理网格和部署管理器。
消息传递网格,如图5-3所示,管理输入请求和会话信息。当请求进入虚拟化中间件组件时,消息传递网格组件确定哪些活动的处理组件可用于接收请求,并将请求转发给其中的一个处理单元。消息传递网格的复杂性可以从简单的round-robin algorithm(轮询算法)到更复杂的next-available(下一个可用算法)(跟踪哪个处理单元正在处理哪个请求)。
Figure 5-3. Messaging-grid component
数据网格组件可能是该模式中最重要和最关键的组件。数据网格与每个处理单元中的数据复制引擎交互,以便在发生数据更新时管理处理单元之间的数据复制。由于消息传递网格可以将请求转发到任何可用的处理单元,因此每个处理单元在其内存数据网格中包含完全相同的数据是至关重要的。尽管图5-4展示了处理单元之间的同步数据复制,但实际上这是异步并行且非常快速地完成的,有时在几微秒内就完成了数据同步(百万分之一秒)。
Figure 5-4. Data-grid component
如图5-5所示,处理网格是虚拟中间件中的一个可选组件,当有多个处理单元时,它管理分布式请求处理,每个处理应用程序的一部分。如果传入的请求需要处理单元类型之间的协调(例如,订单处理单元和客户处理单元),则处理网格将在这两个处理单元之间协调和编排请求。
Figure 5-5. Processing-grid component
部署管理器组件根据负载条件管理处理单元的动态启动和关闭。该组件持续监视响应时间和用户负载,并在负载增加时启动新的处理单元,在负载减少时关闭处理单元。它是在应用程序中实现可变可伸缩性需求的关键组件。
云架构模式是一个复杂且昂贵的实现模式。对于具有可变负载的小型基于web的应用程序(例如,社交媒体网站,竞标和拍卖网站),它是一个很好的体系结构选择。然而,它并不适合传统的具有大量操作数据的基于大型关系数据库的应用程序。
尽管云架构模式不需要一个集中的数据存储,但通常包括一个数据存储来执行内存中的数据网格加载和异步持久化处理单元所做的数据更新。从非活动数据中获取事务性数据也是一种常见做法,以减少每个处理单元内内存数据网格的内存占用。
需要注意的是,虽然这种模式的另一个名称是基于云的体系结构,但处理单元(以及虚拟化中间件)不必驻留在基于云的托管服务或PaaS(平台即服务)上。它可以很容易地驻留在本地服务器上,这是我喜欢这个名字的原因之一“基于空间的架构。”
从产品实现的角度来看,您可以通过第三方产品,如GemFire、JavaSpaces、GigaSpaces、IBMObject Grid,nCache和Oracle Coherence。由于这种模式的实现在成本和性能(特别是数据复制时间)方面有很大差异,作为架构师,在选择任何产品之前,您应该首先确定您的具体目标和需求。
Overall agility(整体敏捷性)
评级:高
分析:整体敏捷性是快速响应不断变化的环境的能力。因为处理单元(应用程序的部署实例)可以快速地启动和关闭,应用程序可以很好地响应与用户负载增加或减少(环境变化)相关的更改。使用此模式创建的体系结构通常能很好地响应编码更改,这是由于该模式的小应用程序规模和动态性质。
Ease of deployment(易部署性)
评级:高
分析:尽管基于空间的架构通常不是解耦和分布式的,但它们是动态的,复杂的基于云的工具允许将应用程序轻松地“推出”到服务器,从而简化了部署。
Testability(易测性)
评级:低
分析:在测试环境中实现非常高的用户负载既昂贵又耗时,因此很难测试应用程序的可伸缩性。
Performance(性能)
评级:高
分析:高性能是通过此模式中内置的内存数据访问和缓存机制实现的。
Scalability(可扩展性)
评级:高
分析:高可伸缩性来自于对集中式数据库的依赖很少或没有依赖,因此从可伸缩性方程中基本上消除了这个限制瓶颈。
Ease of development(可开发性)
评级:低
分析:复杂的缓存和内存数据网格产品使得这种模式的开发相对复杂,主要是因为对用于创建这种类型架构的工具和产品缺乏熟悉。此外,在开发这些类型的体系结构时必须特别小心,以确保源代码中的任何内容都不会影响性能和可伸缩性。
图A-1总结了本报告中描述的每个架构模式的模式分析评分。这个摘要将帮助您确定哪种模式最适合您的情况。例如,如果您的架构主要关注的是可伸缩性,那么您可以通过这个图表看到事件驱动模式、微服务模式和基于空间的模式可能是很好的选择。类似地,如果你为应用程序选择分层架构模式,则需要关注图表中列出的部署、性能和可伸缩性方面可能是架构中的风险点。
Figure A-1. Pattern-analysis summary
虽然这张图表将有助于指导您选择正确的模式,但在选择架构模式时,还有更多需要考虑的问题。您必须分析环境的所有方面,包括基础设施支持、开发人员技能集、项目预算、项目截止日期和应用程序大小等。选择正确的体系结构模式是至关重要的,因为一旦体系结构就位,就很难进行更改(代价高昂)。