当前位置: 首页 > article >正文

《软件架构基础》读书笔记

1、软件架构第一定律:软件架构中的一切都是在做权衡(包括架构师如何在架构中创建组件,组件代表了一种通用的容器机制)

2、架构中没有对错,唯有取舍

3、模块的定义:可用于构建更复杂结构的一组标准化零件或独立单元中的一个,本书中使用模块化来描述代码的逻辑分组,该逻辑分组可以是面向对象语言中的一组类,也可以是结构化语言和函数式语言中的函数。开发者通常使用模块作为将相关代码分组在一起的一种方式。

4、分布式计算的8个谬误:

        4.1、网络是可靠的:尽管随着时间的推移,网络已经变得更加可靠,但事实是网络仍然普遍不可靠,这对于所有的分布式架构都非常重要,因为所有的分布式架构都依赖于网络来实现服务之间的通信。系统越依赖于网络(如微服务架构),其可靠性就越低。网络不可靠是分布式服务之间存在超时和断路机制的原因

        4.2、零延迟:当通过方法或者函数对另一个组件进行本地调用时,调用时间以纳秒或者微秒为单位,然而通过远程访问协议(如REST、消息队列或者RPC)进行相同的调用时,访问该服务时间则是以毫秒为单位的。任何分布式架构的延迟都不是零。使用任何分布式架构时,架构师必须知道平均延迟,这是确定分布式架构是否可行的唯一方法。了解平均延迟很重要,但更重要的是了解第95到99个百分点的延迟,大多数情况下,架构师可以从网络管理员处获得延迟值。

        4.3、带宽是无限的:单体架构中,带宽不是问题,因为单体架构中处理业务请求时所需的带宽很少或者不用。然而一旦系统在分布式架构中被分解为更小的部署单元(服务),这些服务之间的通信就会显著地占用带宽,导致网络变慢,从而影响延迟和可靠性。尤其是分布式架构中的特征耦合会消耗大量的带宽。确保在分布式架构中的服务或者系统之间传递最少的数据量是解决这一谬误的最佳方法。

        4.4、网络是安全的:在分布式架构中,安全性变得更具有挑战性,每个分布式部署单元的每个端点都必须得到保护以便未知或错误的请求不会到达该服务。当从单体架构迁移到分布式架构时,威胁和攻击的范围会增加很多。即使在进行服务间通信时,也必须保护每个端点,这是在同步、高度分布式的架构中性能往往比较慢的另一个原因。

        4.5网络拓扑结构从不改变:该谬误指的是整个网络的拓扑结构(包括网络中使用的所有路由器、集线器、交换机、防火墙、网络和设备),架构师假定这些整体网络拓扑结构是固定的不会发生变化的,但实际上它一直在改变。架构师必须运维人员和网络管理员时刻保持沟通,了解所有变化以便做出相应的调整,减少意外情况。

        4.6、只有一个网络管理员:架构师总是陷入这种谬误,认为他们只需要与一个管理员协作沟通,但是在一个大典型的大公司中有几十个网络管理员。这个谬误指出了分布式架构的复杂性,以及为了让所有东西正确工作而必须进行大量的协调。

        4.7、传输成本为零:架构师(不正确地)假设必要的基础设施已经就绪,足以进行简单的RESTFUL调用或者拆分单个应用程序,通常来说不是如此。分布式架构的成本明显高于单体架构,主要是由于对额外硬件、服务器、网关、防火墙、新子网、代理等需求的增加。

        4.8、网络是同构的:并不是所有的异构硬件都能很好的合作。

5、分层架构:也称为n层架构,其中的组件以逻辑水平分层的方式组织起来,每个分层在应用程序中扮演特定的角色。尽管对于分层的数量和类型没有特定的限制,但是大多数分层架构由四个标准层组成:展示层、业务层、持久层和数据库层。分层架构中的每一层都有特定的角色和职责,如表示层负责处理所有用户界面和浏览器同期通信逻辑,业务层负责执行与请求关联的特定业务规则。架构中的每一层未然需要完成的工作形成一个抽象,以满足特定的业务请求。分层架构中的关注点分离使得在架构中构建有效的角色和职责模型变得很容易,特定层中的组件在范围上受到限制,只处理与该层相关的逻辑。这允许开发者利用他们的特殊技术专场来关注领域的技术方面,但是这种好处的折中是缺乏整体的敏捷性(快速响应变化的能力)。分层架构是一种技术分区的架构(与领域分区的架构相反)。组件不是按领域分组的,而是按其在架构中的技术角色分组的,任何特定的业务领域都分布在架构的所有层中,因此很难对该领域进行更改。故领域驱动设计防反不适合分层架构。分层架构中的每一层既可以是封闭的也可以是开放的。封闭意味着一个请求自顶向下从一个层传递到另一个层时,请求不能跳过任何层, 而是必须通过它下面的层才能达到下下一层。分层隔离性意味着在架构的一个分层中所做的更改通常不会影响到其他分层中的组件,前提是这些层之间的契约保持不变。由于每一层都独立于其它层,因此各层对架构中其他层的内部工作方式很少了解或者完全不了解。为了支持分层隔离性,与主请求流相关的层必须是封闭的。分层隔离性还允许在不影响任何其他层的情况下替换架构中的任何层(同样,假定有定义良好的契约并使用业务委托模式)。封闭层促进了分层隔离性,有助于隔离架构中的更改,但是有时开放某些层是有意义的。利用开放层和封闭层有助于定义架构层和请求流之间的关系,它还为开发人员理解架构中的各种层访问限制提供了必要的信息和指导。对于分层架构,需要注意的一件事是架构污水池反模式。当请求作为简单的传递处理从一层移动到另一层而在每一层不执行业务逻辑时就会出现这种反模式。架构污水池反模式会导致不必要的对象实例化和处理,影响内存消耗和性能。确定架构污水池反模式是否出现的关键是分析属于这类请求的百分比。80-20规则通常是一个很好的实践,如只有20% 的请求是污水池,这是可以接受的。分层架构的弹性和可伸缩性非常低,这是因为单体部署和缺乏架构模块化,这也导致了分层架构不支持容错。如果分层架构得一小部分导致出现内存不足的情况,则整个应用程序单元就都会受到影响并崩溃,总体可用性受到平均恢复时间(MTTR)的影响。

6、管道架构风格:管道架构的拓扑结构由管线和过滤器组成,如下图所示:

管线和过滤器以特定的方式进行协调,管线通常通过点对点的方式在过滤器之间形成单向通信。管道架构将应用程序逻辑划分为不同类型的过滤器(生产者、测试器、转换器和客户),所以管道架构风格是一种基于技术分区的架构。

7、微内核架构风格:该架构也被称为插件架构,它由两个架构组件组成:核心系统和插件组成。应用程序逻辑分为独立的插件组件和基本核心系统,提供良好的扩展性、适应性以及应用程序特性和自定义处理逻辑的隔离。如下图所示:

核心系统是运行系统所需的最小功能集,其另一个定义是通过应用程序的happy path(通用处理流),几乎没有定制处理。消除核心系统的循环复杂度,并将其放在单独的插件组中,可以实现更好的可扩展性和可维护性,也可以增强可测试性。根据规模和复杂度的不同,核心系统可以实现为分层架构,也可以实现为模块化的整体结构。在某些情况下,可以将核心系统划分为单独部署的域服务,每个域服务包含特定于该域的特定插件组件。如下图所示:

插件组件是单机独立的组件,包含专门的处理逻辑、附加功能和用于增强或扩展核心系统的定制代码。此外,它们还可以用于隔离频繁变更的代码,从而在应用程序中创建更好的可维护性和可测试性。理想情况下,插件组件应该彼此独立,相互之间不存在依赖关系。插件组件和核心系统之间的通信通常是点对点的,这意味着连接插件系统和核心系统的“管线”通常是插件组件的入口类的方法调用或者函数调用。插件组件可以是基于编译的,也可以是基于运行时的。运行时插件可以在运行时添加或者删除,而无需重新部署系统或其他插件,并且他们通常通过框架进行管理。插件组件不一定必须域核心系统进行点对点通信,还有其他的选择,包括使用REST或消息传递作为调用插件功能的方法,每个插件都是独立的服务。核心系统需要知道哪些插件模块可用以及如何访问它们,一种常见的实现方法是通过插件注册中心。此注册中心包含关于每个插件模块的信息,包括其名称、数据契约和远程协议访问细节(具体取决于插件如何连接到该系统)。与分层架构类似,简单性和总体成本是微内核架构的主要优点,而可伸缩性、容错能力和可扩展性是其主要缺点,这些缺陷是由于微内核架构中典型的单体部署造成的。

6、基于服务的架构风格:基于服务的架构是微服务架构风格的混合,由于其架构的灵活性,被认为是最实用的架构风格之一。尽管基于服务的架构是一种分布式架构,但它不具有其他分布式架构(如微服务架构或者事件驱动架构)的复杂性和成本。基于服务的架构的基本拓扑遵循分布式宏分层结构。该结构由单独部署的用户界面、单独部署的远程粗粒度服务和单体数据库组成,如下图所示:

这种架构风格中的服务通常是粗粒度的“应用程序的一部分”(通常称为域服务),它们是独立且单独部署的。服务通常以与任何单体应用程序相同的方式部署,因此不需要容器化。服务通常共享单个数据库。大多数情况下,基于服务的架构中每个域服务只有一个实例。然而基于可伸缩性,容错能力和吞吐量需求,一个域服务中可以存在多个实例。服务的多个实例通常需要用户界面和域服务之间具有某种负载均衡功能,以便将用户界面定向到健康且可用的服务实例。服务由用户界面通过远程访问协议进行远程访问。从用户界面访问服务通常使用REST,也可以使用消息传递、远程过程地哦啊用RPC甚至SOAP。基于服务的架构通常使用中心化共享的数据库,这允许服务像传统的单体分层架构一样使用SQL查询和连接。也可以在用户界面层之下添加一个API层,该层由用户界面和服务之间的反向代理或网关组成,如下图所示:

由于基于服务的架构中域服务通常是粗粒度的,因此每个与服务器通常使用分层架构风格设计,由API外观层、业务层和持久化层组成。另一种流行的设计方法是使用类似于模块化整体架构风格的子域对每个域服务进行域区分。如下图所示:

无论服务如何设计,域服务都必须包含某种API访问外观,用户界面与之交互,已执行某种业务功能。API访问外观通常负责编制来自用户界面的业务请求。因为域服务式粗粒度的,所以常规ACID数据库事务通过数据库提交和回滚操作来确保单个域服务中的数据库完整性。高度分布式架构(如微服务)通常具有细粒度的服务并且使用BASE分布式事务实现最终一致性,因此基于服务的架构中不支持与ACID事务相同级别的数据库完整性。粗粒度的域服务可以保证更好的数据完整性和一致性。由于部署了更多的代码,使用基于服务的架构出现问题(包括支付处理)的风险更大,而使用微服务时,每个服务都有一个单独的职责,因此在变更时破坏其他功能的可能性更小

7、事件驱动的架构风格:事件驱动的架构风格是一种流行的分布式异步架构风格,用于构建高度可伸缩和高性能的应用程序。事件驱动的架构由异步接收和处理事件的事件处理组件组成,它可以作为独立的架构风格使用,也可以嵌入其他架构风格(如事件驱动的微服务架构)。基于事件的价模型对特定情况做出响应,并根据该事件才去行动。在事件驱动的架构中主要有两种主要拓扑:中介拓扑和代理拓扑,中介拓扑可以实现对事件流程的工作流进行控制,代理拓扑可以实现高度响应和动态控制事件的处理。二者的不同之处在于代理拓扑没有中心事件中介。

        7.1、代理拓扑结构:在代理拓扑中,消息流以类似链的方式通过轻量级消息代理(如RabbitMQ等)分布在事件处理器组件之间。在代理拓扑中有四个主要的架构组件:初始事件、事件代理、事件处理器和待处理事件。初始事件用于启动整个事件流,它被发送到事件代理中的事件通道进行处理。由于代理拓扑结构中没有中介组件来管理和控制时间,因此单个事件处理器从事件代理接收初始事件并开始处理它。接收初始事件的事件处理器执行与该事件的处理相关联的特定任务,然后通过创建处理事件来异步通知系统的其他部分来通知系统其他部分它做了哪些处理。之后将新创建的处理事件一步发送到事件代理,以便在需要的时候进行进一步处理。其他事件处理器侦听待处理事件,以便对新创建的事件做出响应,然后再通过创建一个新的待处理事件发布它们(事件处理器)所做的操作。这个过程会继续下去,直到没有事件处理器对最终的事件感兴趣为止。整个流程如下图所示:

事件代理组件通常是联合(即基于多个域的集群的实例),其中每个联合代理包含该特定领域的事件流中使用的所有事件通道。由于代理拓扑的解耦异步“即发即弃”广播特性,代理拓扑中使用的主题通常采用发布——订阅消息传递模型。对每个事件处理器而言,在代理拓扑中广播它对系统其他部分所做的操作始终是一种很好的实践,不管其他事件处理器是否关心该操作具体是什么。如果处理该事件需要额外的功能,那么此实践提供了架构可扩展性。在代理拓扑结构中,一旦事件处理器处理完某个事件,它就不再参与该特定事件的处理并且可以对其他初始事件或者待处理事件做出相应。此外每个事件处理器都可以彼此独立地伸缩,以应对该事件处理过程中的各种负载条件或备份。虽然代理拓扑具有性能、响应性和可伸缩性等优势,但其劣势也比较明显:首先它无法控制与初始事件关联的整个工作流。其次,它也不擅长错误处理,因为没有中介监视或者控制业务事务,如果发生故障,系统中没有模块能够感知到崩溃。如果没有自动化或者手动干预手段,业务流程就会卡住,无法继续流转。代理拓扑也不支持业务重启(可恢复性)。由于初始事件的初始过程中异步地执行了其他操作,因此不可能重新提交初始事件。代理拓扑结构中的组件不知道初始事件的装填甚至不知道原始业务请求的状态,因此拓扑结构中没有角色负责重启业务事务(初始事件),也不知道初始事件是从何处停止的。

7.2、中介拓扑结构:中介拓扑结构解决了代理拓扑的一些缺点。中介拓扑的核心是时间中介,它管理和控制初始事件的工作流,初始事件由多个事件处理器协调。中介拓扑结构的组成构件如下:初始事件、事件队列、事件中介、事件通道和事件处理器。与代理拓扑结构一样,初始事件用于启动整个事件过程,与代理拓扑结构不同的是,初始事件被发送到初始事件队列后,该队列由事件中介接受。事件中介只知道处理事件所涉及的步骤,因此生成相应的处理事件,这些事件以点对点消息传递方式发送到专用事件通道(通常是队列)。然后,事件处理器侦听专用的事件通道,处理事件,并通常向中介返回已完成工作的响应。与代理拓扑结构不同,中介拓扑中的事件处理器不会通知它们对系统其他部分做了什么。中介拓扑结构图如下图所示:

事件驱动的架构风格相比较于其他架构风格外提供了一个特性,即它完全依赖于异步通信来进行“即发即弃”处理(不需要响应)和请求/应答处理(需要来自事件使用者的响应)。异步通信可以增强系统的总体响应能力。但是异步通信的主要问题是错误处理。虽然响应能力得到了显著提高,但很难处理错误,从而增加了事件驱动的系统的复杂性

响应式架构的工作流时间模式可以处理异步工作流中与错误相关的问题,此模式是一种同时具有弹性和响应性的架构模式。换句话说,系统可以在不影响性能的情况下灵活地处理错误。工作流事件模式通过使用工作流委托来实现委托、控制和修复,如下图所示:

事件生成者通过消息通道异步地将数据传递给使用者。如果事件使用者在处理数据时遇到错误,它会立即将该错误委托给工作流处理器,并继续处理事件队列中的下一个消息。通过这种方式,总体响应性能不会受到影响,因为下一条消息将立即被处理。如果事件使用者试图花时间找出错误,那么它就无法读取队列中的下一条消息,这样不仅会影响下一条消息的响应性,还有影响队列中等待处理的所有其他消息的响应性。一旦工作流处理器收到一个错误,它就会试图找出消息的错误之处。工作流处理器都以编程方式方式(不需要人工干预)对原始数据进行更改以尝试修复它,然后将其发送回原始队列。事件使用者将此消息视为一个新消息并尝试再次处理它。如果工作流处理器无法确定消息出了什么问题,则将消息发送到另一个队列,然后在通常被称为“仪表板”的应用程序中接受该消息。仪表盘中的消息需要人工手动修复,修复完成之后重新提交到原始队列。使用工作流事件模式,错误消息在重新提交时会按照新的顺序被处理。在处理异步通信时,数据丢失始终是一个存在的问题,在事件驱动的架构中经常发生数据丢失——一条消息被丢弃或者永远无法到达最终目的地。可以通过持久消息队列以及同步发送消息来解决消息丢失的问题,如下图所示:

事件驱动架构的另一个特征是能够在不知道消息接收方是谁以及接收方如何处理消息的情况下广播事件,如下图所示,生产者发布消息时,相同的消息被多个订阅者接收。

8、微服务架构:微服务架构深受领域驱动设计DDD的启发。DDD中有一个特别的概念:有界上下文,它激发了产生微服务的灵感。有界上下文代表了一种解耦风格,当开发人员定义了一个包含许多实体和行为的域时,这些实体和行为是在代码和数据库模式等构件中确定的。在传统的单体架构中,开发人员可以共享其中的许多概念,构建可冲用的类和链接的数据库。在有界上下文中,内部部分(如代码和数据模式)耦合在一起进行工作,但是它们不与有界上下文之外的任何事物耦合。这允许每个上下文之定义其需要的内容而不是容纳其他组件。虽然重用是有用的,但是重用的负面影响是耦合。如果架构师的目标是需要高度解耦,那么他们更喜欢复制而不是重用。微服务的主要目标就是高度解耦,即对有界上下文的逻辑概念进行物理建模。将每个服务分离到其自己的进程中可以解决共享带来的所有问题,但性能通常是微服务的分布式特性的副作用。网络调用比方法调用花费的时间长得多,而且每个端点的安全性验证都增加了额外的处理时间,这就要求架构师在设计系统时应该仔细考虑粒度的影响。微服务的驱动哲学是有界上下文:每个服务建模一个域或者工作流,因此每个服务都包括应用程序运行中所需要的一切,包括类、自他子组件和数据库模式。微服务将域分区架构的概念发挥到了极致,每个服务表示一个域或者子域,在许多方面,微服务是领域驱动设计中逻辑概念的物理体现。微服务中服务边界的目的是识别域或工作流,下面是一些架构师可以用来找到合适边界的指导方针:

        1)目的:最明显的边界依赖于架构风格的灵感,即域。理想情况下,每个微服务应该具有非常高的功能内聚性,代表应用程序贡献一种重要的行为;

        2)事务:有界上下文是业务工作流,在事务中需要协作的实体通常为架构师提供良好的服务边界。因为事务会在分布式架构中引起问题,如果架构师可以设计它们的系统来避免事务,那么就可以产生更好的设计。

        3)编排:如果架构师构建了一组服务,这些服务提供了优秀的域隔离,但是需要大量的通信才能发挥作用,那么可以考虑将这些服务打包到一个更大的服务中以避免通信开销。

由有界上下文概念驱动的微服务另一个需求时数据隔离,微服务试图避免所有类型的耦合,包括用作集成点的共享模式和数据库。


http://www.kler.cn/news/293691.html

相关文章:

  • 【SQL】常见语句合集
  • 何时使用枚举处理前后端的数据传输
  • 深度学习(十一)-PaddlePaddle
  • Java语言程序设计基础篇_编程练习题**17.20 (二进制编辑器)
  • 手撕HashMap源码
  • SVN提交失败Can‘t create directory ‘E:\SVN\Tool\db\transactions\27-v.txn‘:
  • 【Oracle APEX开发小技巧 7】解决初始化数据在动态操作-变更中被识别跳出弹窗的问题
  • 【超详细】windows Docker安装
  • GDB:加载符号表
  • ubuntu22.04 qemu 安装windows on arm虚拟机
  • uniapp+vue3实现小程序和h5解压线上压缩包以及如何访问解压后的视频地址
  • 69-java 接口中可以有构造函数吗
  • 使用 VisionTransformer(VIT) FineTune 训练驾驶员行为状态识别模型
  • setTimeout设置为0和nexttick 谁先执行谁后执行
  • OXC:光交叉连接(optical cross-connect)-介绍
  • 计算机网络-VRRP基础概念
  • 第十七题:电话号码的字母组合
  • 上海市计算机学会竞赛平台2024年8月月赛丙组等差数列的素性
  • 数字图像处理基础:图像处理概念、步骤、方式介绍
  • 【区块链 + 人才服务】FISCO BCOS 高校实训和管理平台 | FISCO BCOS应用案例
  • 【Linux】自定义协议与序列化和反序列化
  • 热力图科普:数据可视化的利器
  • 68-java字符流和字节流
  • 【一嗨租车-注册安全分析报告-滑动验证加载不正常导致安全隐患】
  • DWG如何转换成PDF?总结了四种转换
  • excel比较两列差异性和一致性,统计之后降序排列
  • SQL 数据查询
  • flask-login 生成 cookie,session
  • 从基础到前沿:基于Python的自然语言处理系列介绍
  • 正点原子阿尔法开发板linux驱动开发遇到cc1: error: code model kernel does not support PIC mode