1.前言
架构风格描述包含了各种架构特征的组件,对于进阶架构设计的童鞋来说,面对各种复杂的需求,熟悉各种架构风格是非常必要的。如这些架构风格的特征,部署模型、数据策略等等。<br /> 架构风格主要可以分为单体与分布式架构两种,两者的区别是以**代码的部署与运行方式**作为主要区分点,其中单体架构是将所有的代码打包放在一个单元去部署和运行,而分布式架构是将代码放在多个部署单元上的,通过远程访问协议来连接。单体架构中比较典型的有分层架构、管道架构、微内核架构等,分布式架构典型的有基于服务的架构、事件驱动的架构、基于空间的架构、面向服务的架构、微服务架构等。
2.常见的架构风格
2.1 分层架构
分层架构属于典型的单体架构,比较常见,简单、高效、低成本。其中每个组件以逻辑水平分层的方式组织起来,每个分层在架构 中扮演了特定的角色,常见的有以下三种部署方式:
- 展示层、业务层、持久层组成一个部署单元,数据库层单独部署
- 展示层、业务层和持久层,数据层单独成部署单元
- 四层组成一个部署单元
其中每一层在架构中都有特定的角色和职责,比如展示层负责处理所有用户界面和浏览器通信逻辑,而业务层负责执行与请求关联的特定业务规则,数据层负责存储与事务。这样以后分层架构中的关注点分离使得架构中构建有效的角色和职责模型变得很容易,特定层中的组件只处理业务逻辑,这样对于该层的技术栈或组件升级也有很好的帮助。
分层架构中的每一层既可以是封闭的,也可以是开放的。封闭意味着一个请求自顶向下从一个层传递到另外一个层时,请求不能跳过任何一层,而是必须通过它下面的层才能到达下一层,例如来自展示层的请求必须首先经过业务层,然后到达持久层,最后到达数据库层。分层隔离意味着在架构的一个分层所做的更改不会影响其他分层中的组件。
对于小型应用、应用程序、网站,分层架构是一个很好的选择,对于有时间和成本都非常紧张的情况,它也是一个非常好的选择。分层架构是一种简单常见,成本较低的架构风格,可以提高小型应用程序的开发效率。但随着应用程序的发展,可维护性、敏捷性、可测试性和可部署性等会急剧降低,使用分层架构的大型应用程序和系统可能更适合采用其他模块化的架构风格。
2.2 管道架构
管道架构风格常见于数据处理相关的应用风格,其中管道架构中存在4种类型的过滤器:
- 生产者:进程的起点、仅供输出;
- 转换器:接收输入,选择性地对部分或全部数据执行转换,然后将其转发到输出管道;
- 测试器:接收输入,测试一个或多个条件,然后根据测试选择性生成输出;
- 消费者:管道流的终点。使用者有时会将管道处理的最终结果保存到数据库中,或者在用户界面现实最终结果.
管道架构模式常见于ETL工具(提取/转化/加载),从一个数据库或数据源到另一个数据源的数据流转和修改。如下图使用了管道架构来处理Kafaka的不同类型的数据,服务信息捕获器订阅Kafafa主题来接收服务信息,然后将捕获的数据发送给一个持续时间的测试过滤器,确定从Kafaka捕获的数据是否与服务请求的持续时间相关。服务指标捕获过滤器只关心如何连接到Kafaka主题并接收流数据,而持续时间过滤器只关心如何限定数据并选择性地将其转发到下一个管道。如果数据与服务请求的持续时间相关,则持续时间过滤器将数据传递给持续时间计算器。由于管道架构是单体部署,所以弹性和可伸缩性非常低。
基于管道架构本质上是单体的,不具备分布式架构所具有的复杂性,因此更简单易理解,在构建/维护上成本较低,通过分离各种过滤器和转换器之间的关注点可以实现架构模块化。该架构风格在整体容错性上不高,可能会因为一小部分出现,而导致整个应用程序单元受到影响。
2.3 微内核架构
微内核架构中主要由核心系统和插件组件两大部分组成,逻辑可分为独立的插件组件和基本核心系统,提供良好的扩展性、适应性,以及程序特性和自定义处理逻辑的隔离。
在我们经常使用VIM编辑器就是一个很好的例子,核心系统只是一个文本编辑器:打开文件->编辑文件->保存文件,开发者可以添加很多插件,比如导航栏、菜单、颜色等插件,让VIM更加酷炫。核心系统也可理解为通用流程,没有定制化处理, 这样就消除了核心系统的复杂度,通过插拔组件更好实现可扩展性和可维护性,也可增强可测试性。
插件组件可以通过http接口/消息等作为调用插件功能的方式,使用远程调用作为单个服务实现的插件组件,可以提供更好的整体组件解耦,同时提高可伸缩性和吞吐量,这样以后就将微内核单体架构转变为分布式架构,但这样也会使分系统部署更加复杂,插件也可以有自己使用的单独数据存储,更改数据库只会影响到核心系统,而不会影响插件组件。
微内核架构具有简单、总体成本低、可测试性、可部署性、可靠性较好的优点,特别是插件组件的隔离性,可以减少总体测试范围和部署的成本。但架构的可伸缩性、容错能力、可扩展性较低,这主要是因为微内核架构还是个单体应用架构。
2.4 基于服务的架构
基于服务的架构是微服务架构风格的混合,具有很好灵活性,被认为是最具有实用性的架构风格之一,尽管基于服务的架构是一种分布式架构,但它不具有其他分布式架构的复杂性和成本,使得它成为与业务相关的应用程序的选择。
基于服务的架构中的域服务都是粗粒度的,因此每个域服务的通常实用分层架构风格设计,由API层、业务逻辑层、持久层组成,另一种实用类似于模块化整体架构风格的子域对每个域服务进行域分区。<br />
无论服务如何设计,域服务都必须包含API层用来与用户交互,完成业务功能。域服务是粗粒度,多个组件可能是共享数据库,可以通过常规的ACID数据库事务通过提交和回滚来确保某个域服务中的完整性,但在微服务架构中,只能通过BASE分布式事务来实现最终一致性,因此在基于服务的架构中不支持与ACID事务相同级别的数据库完整性。<br />这种架构风格下,将应用程序拆分为可单独部署的域服务,可以实现更快的变更(敏捷性)、更好的测试覆盖率、更小的发布风险,带来更频繁的部署更新,这些特性使得企业可以及早抢占市场,并以相对较高的速度交付特性和修复缺陷。基于服务的架构,容错能力和整体应用程序可用性都很高,在这种架构风格下,通常其中的某个服务宕机了,不会影响到其他服务,可以实现工程实现上的可伸缩性和弹性,但与更细粒度的微服务相比,功能的重复度更高,所以机器使用率不高,整体成本效益也不高。
2.5 事件驱动的架构风格
事件驱动架构是一种流行的分布式异构架构风格,用于构建高度可伸缩和高性能的应用程序,适应性很强,可用于小型的应用程序,也可应用到大型、复杂的应用程序。事件驱动的架构由异步接手和处理事件的解耦事件处理组件组成,可以做独立的架构风格使用,也可以嵌入到其他架构风格。
在事件驱动架构中有两种实现方式中介和代理模式,其中代理模式比较常见,如经常使用到的RocketMQ/RabbitMQd等分布式消息中间件,其中有初始事件、事件代理、事件处理和待处理事件组件。
以订单处理流为例,如下图所示。这里的所有事件处理器都是高度解耦的且彼此独立的,代理拓扑可以理解为是一场接力赛,处理器处理完某事件后,就不再参与该特定事件的处理,并且可以对其他初始事件的或待处理事件作出响应,每个事件处理器可以独立地伸缩,如果在业务处理过程中某个处理器发生故障或者变慢后,会影响到整个业务流程执行。
事件中介解决了代理拓扑的一些缺点,如工作流控制、处理错误处理、可恢复性、事件任务重启、数据不一致性等问题,主要代表有Apache Camel、ESB等框架。
在事件驱动架构中经常发生数据丢失,可以使用多种技术保证事,消息持久化存储在物理数据存储中(例如文件系统或数据库),如果消息代理宕机,则将消息物理存储在磁盘中,同步发送在消息生产中执行阻塞等待,直到代理确认消息被持久化,有了这两种基本技术就不会丢失生产者和队列之间的消息。
基于事件驱动的架构是异步响应请求的,但有些场景下需要进行异步转“同步”的使用方式,让外部感觉这就是同步请求,实现同步请求-应答消息传递常见方式的是消息头中包含了关联ID,该技术原理如下所示:
- 事件生产者向请求队列发送一条消息并记录唯一的消息ID;
- 事件生产者使用过滤器在应答队列上执行阻塞等待,其中消息头中的关联ID等于原始消息ID(35),
- 事件接收者接收消息(ID 35)并处理请求;
- 事件接收者创建应答请求,并把消息头中的关联ID(CID)设置为原始消息ID(35);
- 事件生产者接收消息(ID 90),关联ID(CID)35与步骤2中相匹配.
基于事件驱动的架构是一种基于技术区分的架构,在多个事件处理器通过事件中介/代理,队列和主题绑定在一起,在高性能、可伸缩性、容错能力上表现很好,通过与高度并行处理结合实现架构的高性能,高可伸缩通过事件处理器的可控制的负载均衡实现,容错是通过高度解耦和异步的事件处理器实现的,这些处理器提供了事件流的最终一致性和最终处理,但该架构风格也具有可测试性较低的缺点,主要由于在架构中经常存在不确定性和动态事件流,而且在异步处理场景下调试也是很困难的。
2.6 面向服务的架构
面向服务的架构是一种分布式架构,没有确切的边界划分,是以企业级重用作为架构实现的最终目标,该架构风格会随着企业中的组织不同而不断变化。如下图就是一个典型的面向服务架构的拓扑结构说明图,有确切的分层,而且在架构中建立了服务分类,每个分层都有特定的职责,主要包括了业务服务/企业服务总线/企业服务/应用服务/基础设施服等。
- 业务服务: 这些服务的定义不包含代码, 只包含输入与输出,通常由业务用户定义,因此命名为业务服务;
- 企业服务: 围绕特定的业务域构建的原子行为,这些服务是粗粒度业务服务的组成部分,通常通过编制引擎集成绑定在一起;
- 应用程序服务: 是一次性、单一的服务,可能某个应用程序中需要地理位置,通常由应用程序服务的团队来解决问题;
- 基础设施服务:提供运维关注点,如监视、日志记录、身份验证和授权,与运维密切相关的共享设施服务团队负责.
编制引擎: 引擎中包括了事务协调和消息转换等功能,将业务服务实现组合在一起,这种架构通常绑定一个或几个关系数据库,而不是像微服务架构那样每个服务对应一个数据库,事务的行为在编制引擎中以声明的方式处理。
面向服务的架构的弹性和可扩展性还是不错的,但是由于整体是一个巨大的服务集合,改动一处可能会对多个服务产生影响,所以该架构的可部署性和可测试性都很低。
2.8 微服务架构
微服务架构是当前流行的分布式架构风格。其中每个微服务实体模型与数据库都完全独立解耦,每个服务在各自的进程中运行。
传统的面向服务的架构中会尽可能重用系统运维的功能如监控、日志记录和断路器等,在微服务架构中也是遵循相同的理念,为了最大化重用,发展出了如下图中的边车(sidecard)模式。常见的运维关注点作为单独的组件出现,可以由单个团队或基础设施团队拥有,当更新边车时,每个微服务都可以使用这个新的功能。
当每个服务都使用了一个边车组件后就可以构建出一个服务网络,从而实现架构的统一控制,以解决日志记录和监控等问题。公共边车组件连接在一起后,就会形成跨所有微服务的一致运维界面。这个服务平面提供了服务的一致接口,允许开发或运维人员对服务进行整体访问。<br /> 
微服务架构是一种以域为中心的架构,每个服务边界与领域相对应,同时是一种可伸缩性、高弹性和具备较好演化性的架构风格,但该架构严重依赖于自动化与运维的智能集成,在微服务架构中一个请求都是需要经过多次调用才能完成,会导致很高的性能开销,
3. 架构决策
以上介绍了当前主流常见的几种架构风格,通常在架构选择中还需要考虑总体的一些的因素如业务领域特定以及对应的一些特征,数据架构、组织因素、开发协作方式等等。
考虑事项 | 原因 |
---|---|
业务域 | 要了解业务流程/规则,特别是运营性架构特征的方面,要深入业务领域中,了解域的突出特点 |
架构特征 | 找出问题域相关的结构、运营等相关的架构特性 |
数据架构 | 数据库存储、模式和的其他数据协作方式 |
组织因素 | IssA供应商的成本,人员组织架构变更,项目产品的合并 |
软件开发过程、团队水平和运维关注点 | 架构中依赖了复杂的中间件会增大技术复杂度和运维成本 |
领域/架构同构 | 参考行业经验考虑问题域的架构适用性,比如微内核架构适合需要定制性的系统,高度可扩展的系统在大型单体设计中会遇到问题。 |
4. 小结
本篇是一些架构设计相关的知识总结,在繁杂的业务领域中。一个应用会使用其中一种或几种架构功能组成,选择合适的架构风格将会事半功倍。
========废弃材料================================
数据冲突
在复制过程中会由于复制延迟而导致数据冲突,当一个缓存实例(缓存A)更新数据, 并复制更新到另一个缓存实例(缓存B)而缓存B也有更新时,数据冲突就会产生。在这种情况下,缓存B的本地更新将被缓存A复制的过期数据而覆盖,而缓存A的数据又因为缓存B的更新时。
以一个电商的案例
当前库存显示毛衣有500件
服务A把毛衣的库存缓存更新为490件(10件已售出)
在复制过程中,服务B将毛衣库存缓冲更新为495件(5件已售出)
服务B的库存缓存被来自服务A的复制操作更新为490件
服务A的库存缓冲则被来自服务B的复制操作更新为495件
服务A和服务B的缓存此时都是错误且不同步的(库存正确应为485件)
分布式缓存VS分布式缓存
决策条件 | 复制缓存 | 分布式缓存 |
---|---|---|
优化 | 性能 | 一致性 |
缓存大小 | 小(<100MB) | 大(>500MB) |
数据类型 | 相对静止 | 高度动态 |
更新频率 | 相对低频 | 更新频率高 |
容错 | 高 | 低 |
2.6 基于空间的架构
大多数的Web业务应用程序普遍遵循相同的请求流程:来自浏览器的请求先访问Web服务器,然后是应用程序服务器,最后是数据库服务器,这种模式对于一小部分的用户非常有效,但随着用户负载的增加,瓶颈开始出现,首先在Web服务器,然后在应用程序服务层,最后在数据库服务器层。对于用户负载增加的产生的瓶颈,通常应对方案是横向扩展Web服务器。<br /><br />常见的是复制缓存与分布式缓存,
单体还是分布式?数据放在哪里?如何构建出合理的数据流,哪些数据是临时的,哪些数据需要持久化的,哪些数据可以缓存;<br />服务之间的通信风格是什么?同步通信非常简单,但也带来不可扩展性、不可靠性和其他问题,异步通信在性能和可扩展性上很好,但也会带来数据同步、死锁、调试等问题.