编辑
2024-07-22
服务端
0
请注意,本文编写于 88 天前,最后修改于 72 天前,其中某些信息可能已经过时。

目录

软件架构是什么?
架构的不同视角
架构的演进
宏观角度
系统复杂度
关于架构选型
服务分层
传统的三层架构
整洁架构(Clean Architecture)
六边形架构(Hexagonal Architecture)
DDD
分布式系统
稳定性
限流
降级
熔断
性能
负载均衡
缓存
异步
自动化
评价指标

软件架构是什么?

软件架构到底是什么?每个人的理解可能不尽相同,下面举几个例子 维基百科的解释

软件架构是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计。

IEEE结构定义:架构=系统宏观结构+系统组件关系+系统设计原则

the fundamental organization of a system, embodied in its components, their relationships to each other and the environment, and the principles governing its design and evolution --ANSI/IEEE

在我的理解里,我们可以将架构理解为对一个系统内部各部分及其相互作用的高度概括。这不仅包括了物理和信息的属性及其相互之间的配对,也涵盖了组成部分之间以及它们与环境之间关系的界定。通过遵循特定的原则对系统进行细分,架构使得不同参与者能够同时在各自的领域内高效工作,从而证明了有组织的创造过程远胜于无序的努力。软件架构的核心目的在于管理和降低系统的复杂性,实现业务逻辑与技术实现之间的清晰分离和解耦。

架构的不同视角

你所认知的架构和别人所说的架构可能是两码事。

不同职位对架构的视角是不一样的。比如,对老板来说,他看到的是企业架构;对产品同学,他看到的是产品业务架构;对于销售同学,他看到的是市场渠道架构;对开发人员,他看到的是服务技术架构;对于运维人员,他看到的是运维架构;而对于技术支持,他看到的是网络和物理架构。

  • 企业架构

企业架构(EA)关心的是企业的结构和行为,尤其是业务角色和流程、业务数据的创建和使用。它被定义为“为分析、设计、规划与实现企业而精心设计的实践过程,使用一个全面的方法来保障企业战略被成功地开发和执行。比如将架构原则和实践贯彻到业务、信息、流程和技术调整之中。”

企业架构将企业看做一个大型复杂系统,企业架构框架则提供工具和方法,帮助架构师聚焦到企业设计任务,并产出有价值的架构描述文档。

  • 业务架构

业务架构就是一张用于理解企业的蓝图,其中描绘了战略目标和战术需求之间的对齐关系,这张蓝图是用管理结构、业务过程、业务信息这类术语绘制的。

业务架构描述的是商业的真实一面,是一个跨学科的场景,专注于定义和分析 “业务是什么”、“怎么形成的”、“怎么组织的”、“怎么实现价值的”。这有助于设计具备竞争力的结构和流程,发挥优势,识别潜在的商业机会,进而推进业务的目标或推动创新。

  • 技术架构(看图)

  • 运维架构(看图)

8

  • 网络和物理架构

物理架构涉及到软件组件在硬件资源上的部署和配置,包括但不限于特定的软硬件环境或云计算平台,比如亚马逊的AWS、微软的Azure和谷歌的GCP等。云服务提供了灵活的资源配置、自动扩展和高可用性等特性,让物理架构的设计和管理更加高效和灵活。物理架构需要确保软件能够在适当的硬件资源上运行,同时考虑到了系统的性能、可靠性、扩展性和安全性等关键因素。

在物理架构的设计过程中,需要考虑的元素包括数据中心的构建、网络的拓扑结构设计,以及网络分流器、代理服务器、Web服务器、应用服务器、报表服务器、集成服务器、存储服务器和主机等不同类型硬件的选择和配置。这些元素共同构成了支撑软件运行的基础设施框架。

下面举例一个网上看到的例子,是某银行系统的数据中心

架构的演进

宏观角度

软件架构的发展是随着互联网技术的提升、用户规模的激增、网络通信容量的倍增而逐步发生的。下面所列的几个阶段都是如此。

  • 最初阶段:单台服务器就可以搞定一个网站了。
  • 应用服务和数据服务分离:单台服务器逐渐不能满足需求,这时候就需要将应用和数据分离。
  • 使用应用服务器集群:单一应用服务器能够处理的请求连接有限,在网站访问高峰时期,应用服务器会成为整个网站的瓶颈。通过集群化部署改善网站的并发处理能力,同时引入负载均衡技术。负载均衡器位于客户端和服务器集群之间,它的主要职责是接收来自用户的请求,并根据预定的策略将这些请求分配给后端服务器集群中的一台或多台服务器处理。这样不仅可以提升系统的处理能力,还能提高应用的可用性和容错能力。
  • 数据库读写分离:数据库因为负载压力过高而成为网站的瓶颈。目前主流的数据库都提供主从热备功能,通过配置两台数据库主从关系,可以将一台数据库的数据更新同步到另一台服务器上。网站利用数据库这一功能实现数据库读写分离,从而改善数据库负载压力。
  • 引入各种缓存:一次网络请求的全链路上可以使用到各种缓存技术。比如,静态文件的网络加速可以通过使用CDN和反向代理的缓存能力,二者的区别在于CDN部署在网络提供商的机房,而反向代理是部署在网站的中心机房,当用户请求到达中心机房后,首先访问反向代理,如果反向代理缓存着用户请求的资源,则直接返回给用户。
  • 分布式系统和微服务:大型网站为了应对日益复杂的业务场景,通过使用分而治之的手段将网站业务拆分成不同的产品线。具体到技术上,也会根据产品线划分将一个网站拆分成许多不同的应用,每个应用划分到不同的团队维护,独立部署维护。

下面再以一个电子商务网站为例,详细展示web应用的架构演变过程。

  • 1.0版本

这个时候一个web项目里包含了所有的模块,一个数据库里包含了所需要的所有表。

这时候网站访问量增加时,首先遇到瓶颈的是应用服务器连接数,比如,tomcat连接数不能无限增加,线程数上限受进程内存大小、CPU内核数等因素影响,当线程数到达一定数量,线程上下文的切换对性能的损耗会越来越严重,响应会变慢,通过增加web应用服务器方式的横向扩展对架构影响最小,架构升级成2.0版本。

  • 2.0版本

随着网站访问量继续增加,应用服务器数量持续增加,数据库成了瓶颈,而数据库的最主要的瓶颈体现在两方面:

  • 数据库的最大连接数是有限的,比如当前数据库的连接数设置8000,如果每个应用服务器与数据库的初始连接数设置40,那么200台web服务器是极限, 并且连接数太多后,数据库的读写压力增大,耗时增加
  • 当单表数量过大时,对该表的操作耗时会增加

这时,数据库可以采用主从方式进行读写分离的方案,并且引入缓存机制来抗读流量,同时需要考虑业务的垂直拆分,架构升级成3.0版本。

  • 3.0版本

这时候仍然是垂直架构,所有业务集中在一个项目里。项目维护、快速迭代问题会越来越严重,单个模块的开发都需要发布整个项目,项目稳定性也受到很大挑战,这是需要考虑业务的垂直拆分,需要将一些大的模块单独拆出来,架构升级成4.0版本。

  • 4.0版本

随着业务量增大,一些核心系统数据库单表数量达到几千万甚至亿级,这时候对该表的数据操作效率会大大降低,并且虽然有缓存来抗读的压力,但是对于大量的写操作和一些缓存miss的流量到达一定量时,单库的负荷也会到达极限,这时候需要将表拆分,一般直接采用分库分表,因为只做分表的话,单个库的连接瓶颈仍然无法解决。

同时,为了进一步提升用户体验,加速用户的网站访问速度,会使用CDN来缓存信息,用户会访问最近的CDN节点来提升访问速度。

随着流量的进一步增大,这时候系统仍然会有瓶颈出现,以订单系统为例,单个机房的机器是有限的,不能一直新增下去,并且基于容灾的考虑,一般采用同城双机房的方式,机房之间用专线链接,同城跨机房质检的延时在几毫秒,此时的架构图如下:

由于数据库主库只能是在一个机房,所以仍然会有一半的数据库访问是跨机房的,虽然延时只有几毫秒,但是一个调用链里的数据库访问太多后,这个延时也会积少成多。其次这个架构还是没能解决数据库连接数瓶颈问题。

  • 随着应用服务器的增加,虽然是分库分表,但每增加一台应用服务器,都会与每个分库建立连接,比如数据库连接池默认连接数是40,而如果mysql数据库的最大连接数是8000的话,那么200台应用服务器就是极限
  • 当应用的量级太大后,单个城市的机器、电、带宽等资源无法满足业务的持续增长。这时就需要考虑SET化架构,也就是单元化架构,大体思路就是将一些核心系统拆成多个中心,每个中心成为一个单元,流量会按照一定的规则分配给每个单元,这样每个单元只负责处理自己的流量就可以了。每个单元要尽量自包含、高内聚。这是从整体层面将流量分而治之的思路。这是单元化后的机构简图如下:#
  • 5.0版本

单元化方案。如下图,流量从接入层按照路由规则(比如以用户ID来路由)路由到不同单元,每个单元内都是高内聚,包含了核心系统,数据层面的分片逻辑是与接入层路有逻辑一致,也解决了数据库连接的瓶颈问题,但是一些跨单元的调用是无法避免的,同时也有些无法拆分的业务需要放在中心单元,供所有其他单元调用。

系统复杂度

随着上面提到的软件架构的演进,加入更多的功能点,系统变得越来越复杂:各个模块(Module)间存在着各种微妙的依赖关系。系统的复杂性随着时间积累,对于程序员来说,修改系统时考虑周全所有的的相关因素变得越来越困难。这就会使软件开放进度变缓慢,并且引入 Bug,而导致会进一步延缓开发进度,增加开发成本。在任何一个系统的生命周期中,复杂性不可避免会增加;系统越大,需要更多的人开发,管理系统复杂性的工作就越困难。

系统的复杂性可能源于多个方面:

  • 持续的设计与迭代:软件开发是一个永无止境的过程,设计在整个系统生命周期中不断演进。开发者需要持续地面对设计上的挑战。
  • 系统间的复杂交互:当系统规模扩大,与其他系统的交互增多,复杂度随之上升。例如,一个简单的在线购物流程涉及订单、支付、物流等多个系统的协同工作,这种交互性质的复杂度是难以避免的。
  • 用户规模变大,应用访问量级指数增长:随着用户规模的增大,很多原有设计中的问题逐渐暴露出来,比如,面向高并发设计和面向低qps场景编程本质是两码事,前者对系统的性能要求、可扩展性、可用性、安全性、数据一致性、大数据处理都有不少的挑战
    • 性能要求:高性能要求(如低延迟、高吞吐量)需要优化设计和实现,增加了系统的复杂度。
    • 可扩展性:系统需要能够处理不断增长的用户和数据量,这需要考虑水平和垂直扩展的设计。
    • 可用性:高可用性要求(如99.99%的服务可用性)需要冗余、故障恢复和负载均衡等机制。
    • 安全性:保护系统免受各种安全威胁(如数据泄露、攻击)需要复杂的安全机制和策略。
    • 数据一致性:分布式系统中的数据一致性问题(如CAP定理)增加了复杂度。
    • 大数据处理:处理和存储大规模数据需要特殊的设计和技术(如分布式存储、并行计算)。
  • 不合理的业务封装:业务逻辑的封装如果不够合理,如采用过程式编程而非面向对象编程,或者分层架构设计不当,都会增加系统的复杂度。
  • 缺乏统一语言:在敏捷开发环境下,精细化分工可能限制了成员的全局视角,导致缺乏统一的沟通和理解框架。
  • 缺少规范和约束:团队合作中,如果缺少一致的编码规范和设计约束,将严重影响代码的一致性和可维护性,细微的疏忽可能导致大问题。

面对复杂性,我们需要采取动态和渐进的策略,不断地重新认识和优化系统,方法包括

  • 简化代码与明确意图:通过减少特殊情况的处理、保持变量命名的一致性等,可以有效降低系统复杂性。
  • 抽象与分治:对复杂问题进行抽象,然后采取分而治之的策略,可以使问题变得更加可管理。通过模块化设计和分层架构减少系统的复杂度。
  • 微重构:持续开发意味着持续重构,一个系统的初始设计几乎从来都不是最好的方案。
  • 自动化测试:通过自动化测试和持续集成减少变更引入的风险。
  • 文档和知识管理:完善的文档和知识管理有助于减少理解和维护的难度。
  • 代码质量:通过代码审查、静态分析和重构提高代码质量,减少技术债务。
  • 监控和日志:通过监控和日志系统及时发现和解决问题,确保系统的稳定性和可维护性。

总的来说,系统复杂性的管理是一个持续的过程,要求开发者不断地学习和适应,来有效地控制和减少复杂性。

关于架构选型

关于架构选型还说一句,选择合适的架构是基于当前业务需求的一项关键决策。在确保业务需求得到满足的同时,架构设计需要保持适度的扩展性,避免不必要的过度设计。每一次架构的更新和升级,都旨在解决系统存在的瓶颈问题,以促进系统性能的提升和业务的顺畅运行。

不可否认的是,架构设计的确是一个比较复杂的任务。因为要设计一个合理的架构,需要拥有如下能力:

  • 需要拥有较深的业务理解:因为每一个架构的存在,都是为了解决特定的业务问题。
  • 需要拥有较高的视野:理想设计可以尝试跳出原有的架构设计,否则在设计的过程中,会发现原有的不好的架构已经能解决问题。会让原有的设计思维框住设计思维。
  • 行业内架构专业知识:套用现在的架构设计。多借鉴其他的设计,站在巨人的肩膀上做微创新。

最后,没有最好的架构,只有最合适的架构。

服务分层

在当前公司内部基建完善,宏观架构稳定的前提下,我们更多要关注的是每个服务内部的分层情况。 服务分层的关键是是边界划分,软件架构设计本身就是一门划分边界的艺术。

分层架构是软件设计中一种常见的模式,其主要目的是将软件划分为不同的层,每一层负责处理特定的任务或关注点。这种分离使得系统更加模块化,提高了代码的可维护性、可扩展性和可测试性。虽然不同的分层架构模式可能因时代背景和设计哲学的不同而有所区别,它们的共同点在于都试图通过分离关注点来简化软件系统的复杂性。以下是一些常见的分层架构模式:

传统的三层架构

这是最基本也是最广为人知、群众喜闻乐见的分层模式,通常包括:

  • 表示层(或用户界面层):负责与用户交互,展示数据和接收用户输入。
  • 业务逻辑层:包含处理用户请求的核心功能和业务规则。
  • 数据访问层:负责与数据库或其他持久层存储进行交互,进行数据的CRUD(创建、读取、更新、删除)操作。

整洁架构(Clean Architecture)

由Robert C. Martin提出,旨在实现关注点分离,提高系统的独立性。整洁架构将系统分为多个圆环层(洋葱模型),每个层只能与相邻的内层进行通信。

葱架构强调核心代码的独立性,类似于整洁架构,但具有自己的特点。它由几个同心圆层组成,最内层是领域模型,外层依次是领域服务、应用服务和基础设施层。所有的外部请求都通过基础设施层进入,然后向内传递至核心领域逻辑。

六边形架构(Hexagonal Architecture)

六边形架构也称为端口和适配器架构,由Alistair Cockburn提出。它将应用分为内六边形和外六边形两层,这两层的职能划分如下

  • 加粗线框内的六边形实现应用的核心业务逻辑。
  • 外六边形完成外部应用、驱动和基础资源等交互和访问,对前端应用以API主动适配的方式提供服务,对基础资源以依赖导致被动适配的方式实现资源访问。

六边形架构各层的依赖关系与洋葱架构一样,都是由外向内依赖。 六边形架构的核心理念是:应用是通过端口与外部进行交互。它强调应用程序核心逻辑的独立性,使其与外部世界隔离。核心逻辑通过端口与外部世界交互,外部的交互则通过适配器来实现。这种架构使得应用程序可以独立于外部设备、数据库、Web接口等运行。

DDD

领域驱动设计(Domain-Driven Design,简称DDD)的核心战略在于明确区分问题域和解决方案空间(即应用架构),并通过业务逻辑的显性化来简化复杂的业务算法。DDD通过创建领域对象和采用统一语言,将抽象和复杂的业务逻辑转换为清晰的领域概念。这种方法不仅提高了代码的可读性,还降低了团队成员之间的沟通成本。

  • 统一语言是DDD中的一个关键概念,它要求开发者和业务用户使用相同的术语来描述业务概念,确保对特定概念的理解是一致的。这种做法有助于构建清晰的业务模型,形成统一的业务语义。在团队内部的所有交流、代码编写、图表绘制、文档编写甚至日常对话中,都应当使用这种统一的语言。例如,对于“账号”、“转账”、“透支策略”等关键领域概念,如果命名与日常讨论和产品需求文档(PRD)中的描述保持一致,将极大地提升代码的可读性和降低理解成本。
  • DDD鼓励从领域的角度思考问题,而非仅仅从技术模块的角度。这意味着将隐式的业务逻辑抽象化,用通用语言进行命名和编码,从而使其成为显性的概念。通过这种方式,可以避免重要的业务概念在代码中被淹没。
  • 此外,DDD还强调根据实际业务需求合理划分模型,使模型之间的依赖结构和边界更加清晰。这有助于避免混乱的依赖关系,提高代码的可读性和可维护性。每个模型都只关注自己的本职工作,避免不必要的交叉调用,进而实现单一职责。

通过DDD的建模方法,可以更好地表达现实世界中的复杂业务,随着时间的推移,系统对实际业务的理解将不断深入,从而更清晰地通过代码描述业务逻辑。模型的内聚性增强了系统的模块化,提高了代码的可重用性。与传统的三层架构模式相比,DDD能够避免功能在各个服务中的重复散布,从而实现更高效的代码组织和业务逻辑表达。

  • 用户接口层:显示信息给用户,如对外的model、模型的转换。一般包括用户接口、Web 服务等,只处理用户显示和用户请求,不应包含领域或业务逻辑。用户接口层很重要,在于前后端调用的适配,Facade接口就起很好的作用,包括DO和DTO对象的组装和转换等。
  • 应用层:主要包含线程调度,应用服务,与模型进行与实体无关的业务逻辑。理论上不应有业务规则或逻辑,而主要是面向用例和流程相关的操作。应用层位于领域层之上,因为领域层包含多个聚合,所以它可协调多个聚合服务和领域对象完成服务编排和组合,协作完成业务。
  • 领域层:业务概念、规则、领域模型。主要包含聚合、聚合根、实体、值对象、领域服务等领域模型中的领域对象。过事件风暴(用例分析,场景分析,用户旅程分析)得到实体和值对象,然后找出聚合根,按照高内聚低耦合的设计原则,找出跟聚合根紧密关联的实体和值对象,即形成聚合,并画出聚合内的实体和值对象的引用依赖关系,最后把业务把关联紧密的聚合画在同一个限界上线文中,即完成了领域建模。
    • 聚合:高内聚低耦合,是领域模型中最底层的边界,可以作为拆分微服务的最小单位,但是不建议单独对应一个微服务,除非是对性能有极致要求的场景,一个微服务可以包含多个聚合,聚合之间的边界是逻辑最天然的边界,有了这个逻辑边界,就可以在微服务拆分的时候作为拆分和组合依据,微服务架构演进也就不是难事了。
    • 聚合根:果把聚合比作组织,聚合根则是组织的负责人。聚合根是实体,具备唯一标识,有独立的生命周期,一个聚合只有一个聚合根,聚合根在聚合之内采用引用依赖的方式对实体和值对象进行组织和协调,聚合根和聚合根之间通过唯一id进行聚合之间的协同;
    • 实体:具备id标识,可以通过id进行相等性比较,实体在聚合内唯一,但是状态可变,它依附于聚合根,它的生命周期由聚合根管理,实体一般都会持久化,跟数据持久化对象存在多种对应关系(一对一,一对多,多对一,1对0),实体可以引用聚合中的聚合根,实体,值对象;
    • 值对象:无id,不可变,无生命周期,用完即失效,值对象之间通过属性值判断相等性,他的核心是值,是一组概念完整的属性集合,用于描述实体的特征和状态,值对象尽量只引用值对象;
  • 基础层:是一个交互层次,为其它各层提供通用的技术基础服务,包含三方工具、驱动、MQ、API网关、文件、缓存、DB、基础服务等;最常用的还是提供DB持久化。

分布式系统

使用分布式系统核心要解决的问题有两点。一是提高整体架构的吞吐量,服务更多的并发和流量,二是为了提高系统的稳定性,让系统的可用性更高。

下面这四项技术,即应用整体监控、资源和服务调度、状态和数据调度及流量调度,它们是构建分布式系统最最核心的东西。

稳定性

在高并发场景,实现高可用的三板斧是缓存,限流和降级。实施这些策略需要精细的设计和周到的计划,包括但不限于对系统架构的深入了解、各服务之间依赖关系的明确、以及对故障发生时各种情景的预案。通过这种综合性的防御策略,可以有效地保护系统免受突发高并发事件的严重影响,从而维持服务的稳定性和可靠性。

限流

在任何服务系统中,存在着一个最大承载量,一旦请求量超出这一界限,系统可能无法响应,甚至会发生故障。为了避免这种情况,引入了服务降级和流量控制机制。流量控制,简而言之,是一种在遇到高并发请求或突发流量峰值时,为了维持系统的运行稳定和服务的可持续提供,而采取的一种策略,该策略通过牺牲一部分请求处理或延迟部分请求的处理来实现。这种策略的核心目的是通过有效管理资源分配和请求处理速率,防止系统过载。除了技术层面的措施,有效的流量控制还需要考虑到业务优先级,确保关键服务的高可用性。在设计限流策略时,需要综合考虑系统架构、业务需求和用户体验等多方面因素,以达到既保障系统稳定性,又尽可能满足用户需求的目的。

降级

降级操作是在检测到系统关键组件故障或是响应时间过长时,暂时关闭一些非核心业务,保证系统能够继续提供最主要的服务功能。

熔断

熔断机制像是一个自我保护机制,当系统检测到某个服务异常(如失败率过高)时,会自动切断该服务的调用,防止错误进一步扩散。

性能

负载均衡

负载均衡是一种技术策略,旨在通过将工作负荷(如数据处理任务和网络请求)均匀分配到多个处理单元(比如多台服务器或不同的系统组件)上,以优化资源利用、提高系统整体性能、增强系统的可用性并促进系统的横向扩展能力。这种方法不仅可以提升处理效率,还能避免因单个节点过载而导致的性能瓶颈或系统崩溃,从而确保服务的持续可用和响应速度。

在实现负载均衡时,可以采用多种技术和策略,如DNS轮询、IP哈希分配、最少连接数分配等,每种方法都有其特定的适用场景和优势。例如,DNS轮询简单易实施,适合于请求独立且服务器性能相近的场景;而IP哈希分配则可以保证来自同一客户端的请求被分配到同一个服务器,适合需要会话保持的应用。

此外,现代的负载均衡器不仅仅在于分配负载,它们还具备高级功能,如自动健康检查、SSL终端处理、攻击防护等,这些功能进一步增强了系统的稳定性和安全性。通过动态地管理和调整资源分配,负载均衡器能够确保系统即使在面对高并发请求、节点故障或其他不稳定因素时,也能保持高效和稳定的运行。

负载均衡的实施,对于构建高性能、高可用和易于扩展的系统至关重要。它不仅可以提高系统的处理能力和响应速度,还能提升系统的容错能力和灵活性,是实现服务稳定运行和支持业务增长的关键技术之一。

缓存

缓存可以提高系统的响应速度和

异步

一般通过消息队列实现削峰的能力,可以增加系统的吞吐量。

自动化

应用层的自动化运维需要基础层的调度支持,也就是云计算 IaaS 层的计算、存储、网络等资源调度、隔离和管理。有了 DevOps 后,我们就可以对服务进行自动伸缩、故障迁移、配置管理、状态管理等一系列的自动化运维技术了。分布式系统可以更为快速地更新服务,但是对于服务的测试和部署都会是挑战。所以,还需要 DevOps 的全流程,其中包括环境构建、持续集成、持续部署等。

评价指标

目前,团队内部从以下四个方面评价团队的技术。

  • 研发效率
  • 工程质量
  • 成本
  • 性能

本文作者:sora

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!