软件架构模式速览
解读架构模式
每当人们谈起架构要么会陷入一些纯理念(例如架构是什么?它来自哪里?又要到哪里去?我已陷入了深深地思考中…)或者buzzword(SOA、微服务)等空对空互侃的怪圈,要么会陷入过于细节的实操技巧(如高并发高流量技巧揭秘等)。软件架构模式则很好的起到连接理论与实际的作用,使得架构理念的讨论更佳具体而全面,让架构细节有了依据和载体。本文源自Mark Richards的《Software Architecture Patterns》一书,并对其精华进行了提炼,方便大家快速学习。
定义
架构模式是针对软件系统在特定上下文中经常出现问题的一种通用的、可重用的解决方案。架构模式类似于软件设计模式,但其范畴更为宽泛。架构模式关联软件工程各种问题,例如:计算机硬件性能限制,高可用及最小化商业风险。一些架构模式已经在软件框架中得到应用。
作用
架构模式帮助定义一个系统的基本特点和行为。对于架构师来说所设计的系统为实现商业目标,有必要了解每一种架构模式的特点、优点以及缺点,并能够根据实际情况灵活运用,做出合理的架构决策。
速览内容
本文将对常见的五种架构模式进行介绍,它们分别是:
- 分层架构模式(Layered Architecture Pattern)
- 事件驱动架构模式(Event-Driven Architecture Pattern)
- 微内核架构模式(Microkernel Architecture Pattern)
- 微服务架构模式(Microservices Architecture Pattern)
- 基于空间的架构模式(Space-Based Architecture Pattern)
分层架构(Layered Architecture Pattern)
又名N层架构模式,是应用最广泛的一种架构模式,大多数单体应用都是采用的这种架构模式,并且和其他架构模式进行组合应用的场景也不少见。
模式描述
分层模式是通过水平方向的层次来组织各个组件的,每一层都在系统中扮演特定角色,具有特定的职责。分层架构一般由四个层次组成:
- 表现层
- 业务层
- 持久层
- 数据库
当然分层架构模式的层数可以根据实际情况进行调整,有的应用是将持久层的逻辑绑定在业务层的组件当中,这时2层和3层会合并成一层包含持久逻辑(SQL)的业务层,而采用经典DDD领域驱动设计会将业务层分为服务层和业务逻辑层。
该模式通过分离关注点来降低整个应用程序的复杂度:
- 一个层中的组件只会处理本层的逻辑
- 层与层之间是通过接口来进行交互的
- 对于接口内部的实逻辑则通过信息隐藏来封闭
核心要点
开闭原则
在分层架构设计时,可根据实际需要将层与层之间的规则定义为封闭或开放的架构层次关系。
封闭的架构层次(Closed layers)
如下图所示,该架构中的每一层都是封闭的。请求需要一层层的传递。这里体现了层隔离(layers of isolation)的概念。
层隔离主要作用有两点:
- 封装变化(针对层自身来说)。
- 相互独立(针对层与层之间的关系来说)。 但有时候严格实施封闭层次会对实际应用造成一些不便,因此在实际层次设计中会设计一些开放层。
开放的架构层次(Open layers)
在下面的设计中,对持久化层内的组件访问提供了一个开放的服务层。这样从展示层的请求需要通过服务层才能访问持久层,但是业务层的内部逻辑可直接访问持久层。
示例
架构示例(MVC)
表现层 包含用户交互和用户体验、数据传输对象(Data Transfer Object)及视图模型(View Model)
业务层/服务层 接收请求并执行业务规则
数据访问层 操作各种类型的数据库
逻辑示例
黑色的箭头是从数据库中获得用户数据的请求流,红色箭头显用户数据的返回流的方向。
用户界面负责接受请求以及显示客户信息。它不管怎么得到数据的,或者说得到这些数据要用到哪些数据表。如果用户界面接到了一个查询客户信息的请求,它就会转发这个请求给用户委托(Customer Delegate)模块。这个模块能找到业务层里对应的模块处理对应数据(约束关系)。业务层里的customer object聚合了业务了请求需要的所有信息(在这个例子获取客户信息)。这个模块调用持久层中的customer dao来得到客户信息,调用order dao来得到订单信息。这些模块会执行SQL语句,然后返回相应的数据给业务层。当customer object收到数据以后,它就会聚合这些数据然后传递给customer delegate,然后传递这些数据到 customer screen展示在用户面前。
架构考量
分层架构适合大多数的应用场景,是架构设计的首选项。设计中需要注意下面两种情况:
- 污水池反模式(architecture sinkhole anti-pattern):这种反模式描述了请求经过分层, 但没做任何事或者只处理了很少的事。如果我们的请求经过所有分层而没有做任何事,这就是污水池反模式的征兆。可以通过20-80原则来判定。
- 单体应用(monolithic applications):分层架构可能会演变为单体应用。
模式分析
架构特性 | 评级 | 补充说明 |
---|---|---|
整体灵活性 | 低 | 单体应用灵活性差,层内组件耦合 |
易部署性 | 低 | 对于大型系统,局部变化导致整体部署 |
可测试性 | 高 | mock和stub,可对组件做隔绝测试 |
性能 | 低 | 多层访问 |
可伸缩性 | 低 | 耦合太紧以及整体风格(Monolith)、除非每层都能独立部署 |
易于开发度 | 高 | 易于理解和应用、组织层面 |
事件驱动架构(Event-Driven Architecture Pattern)
模式描述
事件驱动架构(Event Driven Architecture) 是一种主流的异步分发事件架构模式,用于创建高扩展的应用。事件驱动架构模式由高度解耦、单一目的的事件处理组件构成,这些组件负责异步接收和处理事件。
核心要点
事件驱动架构模式包含了两种主要的拓扑结构:中介(mediator)拓扑结构和代理(broker)拓扑结构。
中介(mediator)拓扑结构
适用于含有多个步骤,并需要在处理事件时能通过某种程度的协调将事件分层的场景。
架构要点
两种事件
- 初始事件(initial event):初始事件是中介所接收到的最原始的事件,没有经过其他组件的处理。
- 待处理事件(processing event):由事件中介生成,被事件处理器组件所接收。 注意:不能把待处理事件当做初始事件经过处理后得到的事件。
四种组件
- 事件队列(Event Queue):多个事件队列,对实现没有明确要求,可以使消息队列、web服务端等。
- 事件中介(Event Mediator):负责分配、协调初始事件中的各个步骤。
- 事件通道(Event Channel):消息主题(待处理的事件被多个事件处理器处理)或消息队列。
- 事件处理器(Event Processor):包含应用的业务逻辑,具有单一职责、自满足及高度解耦等特点。
示例
假设你在某家保险公司买了保险,成为了受保人,然后你打算搬家。在这种情况下,初始事件就是重定位事件,或者其他类似的事件。与重定位事件相关的处理步骤就像上图所示那样,处于事件中介之中。对每个初始事件的传入,事件中介都会创建一个待处理事件(例如:改变地址、重新计算保险报价等等),并将它发送给事件通道,等待发出响应的事件处理器处理待处理事件(例如:客户改变地址的操作流程、报价计算流程等等)。直到初始事件中的每 个需要处理的步骤完成了,这项处理才会继续(例如:把所有手续都完成之后,保险公司才会帮你改变地址)。事 件中介中,重新报价和更新理赔步骤上 的直线表 这些步骤可以并行处理。
代理(broker)拓扑结构
代理拓扑结构没有任何集中的事件中介,而是通过一个轻量的消息代理(例如:ActiveMQ、HornetQ等)将消息串成链状,分发至事件处理器进行处理。适用于:事件处理流简单、高效事件处理或无需集中编排的场景
架构要点
两种组件 两种组件:
- 代理
- 事件处理器
代理可被集中或相互关联在一起使用。代 理中还可以包含所有事件流中使用的事件通道。 存在于代理组件中的事件通道可以是消息队列、消息主题,或者是两者的组合。 整个处理流程比较像接力赛:当处理器处理完毕发出事件后,它的任务也就结束了。接下来则可能整个事件处理结束或者发给broker由下一个事件处理器接收处理。
示例
因为在代理拓扑结构中没有集中事件中介接收初始事件,那么事件将由客户处理组件直接接收,改 变客户的地址,并发出事件告知系统客户的地址被其改变了(例如:改变地址的事件)。在这个例 子中:有两个事件处理器会与改变地址的事件产生关联:报价处理和索赔处理。报价事件处理器将根据受保人的新地址重新计算保险的金额,并发出事件告知系统该受保人的保险金额被其改变。而索赔事件处理器将接受到相同的改变地址事件,不同的是,它将更新保险的赔偿金额,并发出一个更新索赔金额事件告知系统该受保人的赔偿金额被其改变。当这些新的事件被其他事件处理器接收、处理,使事件链一环扣一环地交由系统处理,直到事件链上的所有事件都被处理完,初始事件的处理才算完成。
架构考量
支持事务困难
- 需要事务协调器
- 难以维护事务性工作单元
组件契约(创建、维护、管理)
- 变化昂贵
- 基于标准数据格式
- 一开始就建立契约版本策略
异步&分布式
- 网络分区、调停者失败、重新连接逻辑等
- 远程进程可用性、缺乏响应能力
模式分析
架构特性 | 评级 | 补充说明 |
---|---|---|
整体灵活性 | 高 | 事件和事件处理器之间解耦,并 且可独立维护 |
易部署性 | 高 | 架构解耦,代理模式比中介模式更易于部署 |
可测试性 | 低 | 组件测试容易,由于异步特点端到端的测试是很难的 |
性能 | 高 | 解耦&异步操作 |
可伸缩性 | 高 | 组件之间解耦,组件可以独立扩展。 |
易于开发度 | 低 | 异步处理机制;协议创建流程;错误处理和重试机制 |
微内核架构模式(Microkernel Architecture Pattern)
微内核架构(又名插件架构plugin architecture)模式可以通过插件的形式添加额外的特性到核心系统中,因此提供了很好的扩展性,也使得新特性与核心系统解耦。
模式描述
微内核架构模式组成:
- 核心系统:通常包含最小的业务逻辑,并具备加载、卸载和运行应用所需的插件的机制。
- 插件模块:包含专业处理、额外特性的独立组件,自定义代码意味着增加或者扩展核心系统以达到产生附加的业务逻辑的能力。
核心要点
插件感知机制
插件感知机制一般是通过一组插件注册表来实现。插件模块的注册信息包括插件名字、数据契约(输入输出格式、契约格式)、远程访问细节等。
连接机制
连接机制微内核架构模式并未限定,取决于应用类型和特定的场景需求,包括OSGI(open service gateway initiative)、消息机制、web服务或者直接点对点的绑定(如:对象实例化,即依赖注入).
示例
图中的一堆文件夹代表了赔偿处理核心系统。它包含一些处理保险赔偿的基本业务逻辑。每一个插件模块则包含每个地区的具体赔偿规则。在这个例 中,插件模块通过自定义源代码实现或者分离规则引起实例。不管具体实现如何,关键就在于赔偿规则和处理都从核心系统中分离, 这些规则和处理过程都可以被动态地添加、移除,而这些改变对于核 系统和其他插件只有很小的影响或者根本不产生影响。
架构考量
- 解决某个特定模块架构问题时,可以与其他模式结合(例如:分层架构模式、事件处理器组件)
- 对渐进式设计和增量开发支持非常好
- 非常适合基于产品的应用
模式分析
架构特性 | 评级 | 补充说明 |
---|---|---|
整体灵活性 | 高 | 快速响应变化;插件可以独立开发并注册到核心系统 |
易部署性 | 高 | 热拔插;最大化减少部署时的停机时间 |
可测试性 | 高 | 独立测试;容易mock |
性能 | 高 | 取决插件数量;特性可定制、可编排 |
可伸缩性 | 低 | 大多数是基于产品的小型应用;插件特性级的伸缩 |
易于开发度 | 低 | 设计要求高;契约管理(例如:插件注册,契约版本、插件粒度,插件连接选型) |
微服务架构模式(Microservices Architecture Pattern)
采用一组服务的方式来构建一个应用,服务独立部署在不同的进程中,不同服务通过一些轻量级交互机制来通信,服务可独立扩展伸缩,每个服务定义了明确的边界,不同的服务甚至可以采用不同的编程语言来实现,由独立的团队来维护。
模式描述
微服务架构的每个组件都作为一个独立单元进行部署,让每个单元可以通过有效、简化的传输管道进 通信,同时它还有很强的扩展性,应用和组件之间高度解耦,使得部署更为简单。其关键概念有:
- 服务组件
- 分布式
- 从传统架构演进而来
演进路线
从单体应用演进
由持续交付促进形成。整体应用程序通常包含紧耦合的层次,难以部署和交付。
从面向服务架构演进
SOA架构非常强大,提供高抽象级别、异构连接、服务编排、对齐业务目标的特点。性价比低、复杂、昂贵,难于理解和实现,通常对于 大多数应用程序来说矫枉过正。
核心要点
基于REST API的拓扑
该拓扑结构是由细粒度的服务组件组成,每个服务组件是由一到两个模块构成来执行特定业务功能。这些服务组件由基于REST-based的接口访问。
基于REST应用拓扑
该拓扑结构是通过传统的基于web的或胖客户端业务应用来接收客户端请求,而不是通过一个简单的API层。这些服务组件往往会更大、粒度更粗、代表整个业务应用程序的一部分,而不是细粒度的、单一操作的服务。
集中式消息拓扑
该拓扑结构则使用一个轻量级的集中式消息代理(如,ActiveMQ, HornetQ等等)。与SOA的区别是该轻量级消息代理(Lightweight Message Broker)不执行任何编排、转换或复杂的路由;相反,它只是一个轻量级访问远程服务组件的传输工具。
其好处是有先进的排队机制、异步消息传递、监控、错误处理和更好的负载均衡和可扩展性。与集中式代理相关的单点故障和架构瓶颈问题已通过代理集群和代理联盟(将一个代理实例为分多个代理实例,把基于系统功能区域的吞吐量负载划分开处理)解决。
示例
架构考量
解决整体应用和面向服务应用程序的缺陷:
- 更健壮
- 可伸缩
- 持续发布
- 热部署和高可用
挑战:
- 服务的划分粒度
- 服务组件之间的契约(创建、维护和管理)
- 远程系统的可用性
- 远程访问认证和授权
包含业务逻辑和处理流程的服务组件(Service Component)
模式分析
架构特性 | 评级 | 补充说明 |
---|---|---|
整体灵活性 | 高 | 独立部署单元能够对变化作出快速响应;松耦合 |
易部署性 | 高 | 服务组件即是单独部署单元 |
可测试性 | 高 | 服务组件的测试可以独自完成 |
性能 | 低 | 分布式特性 |
可伸缩性 | 高 | 独立部署单元天然具备很好的伸缩性 |
易于开发度 | 高 | 每个服务组件可以各自独立实现 |
基于空间的架构(Space-Based Architecture Pattern)
基于空间的架构模型是专门为了解决伸缩性和并发问题而设计的。它对于用户数量不可预测且数量级经常变化的情况同样适用。在架构级别来解决这个伸缩性问题通常是比增加服务器数量或者提高缓存技术更好的解决办法。
模式描述
基于空间的模型旨在减少限制应用伸缩的因素。模型的名字来源于分布式共享内存中的tuple space(数组空间)概念。高伸缩性是通过去除中心数据库的限制,并使用从内存中复制的数据框架来获得的。保存在内存的应用数据被复制给所有运行的进程。进程可以动态的随着用户数量增减而启动或结束,以此来解决伸缩性问题。这样因为没有了中心数据库,数据库瓶颈就此解决,此后可以近乎无限制的扩展了。
核心要点
该架构主要包含两个主要模块:处理单元和虚拟化中间件。
处理单元
包含了web组件以及后台业务逻辑。典型的处理单元包含应用模块、位于内存中数据框架、为应用失败准备的异步持久化转移模块(可选)以及复制引擎。
虚拟化中间件
本质上是架构的控制器,负责保护自身以及通信。它包含用于数据同步和处理请求的模块。具有四个架构组件通信框架、数据框架、处理框架及部署管理器。
通信框架
管理输入的请求和会话信息。决定哪个处理单元可以接受请求,并指派给一个处理单元
数据框架
数据变化时和每个数据复制引擎交互,进行数据同步。数据同步是异步完成,微秒级别。
处理框架
可选组件,用于协调和编排多个处理单元之间的分布式请求处理
部署管理器
持续监控响应时间和用户负载 动态地开启或关闭处理单元
示例
GemFire、JavaSpaces、GigaSpaces、IBM Object Grid、nCache、Oracle Coherence
架构考量
适用于负载不稳定的web应用,如:社交媒体网站 不适用传统的大规模关系型数据库 可以建立数据仓库来分离非活跃数据和常用数据 不是必须部署在云上
模式分析
架构特性 | 评级 | 补充说明 |
---|---|---|
整体灵活性 | 高 | 处理单元动态开关 |
易部署性 | 高 | 使用云管理工具推送 |
可测试性 | 低 | 难以在测试环境模拟高用户负载 |
性能 | 高 | 数据存放在内存中加上缓存机制 |
可伸缩性 | 高 | 不依赖中心数据库 |
易于开发度 | 低 | 复杂的缓存和内存数据框架 |
架构对比
参考文献
《Software Architecture Patterns》—Mark Richards
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!