聊架构设计的时候,我们在谈什么?

第一步:理解商业与组织上下文 (Understand Business & Organizational Context)

  • 利益相关方 (Stakeholders): 他们的核心诉求和期望是什么?
  • 用户视角 (User Perspective): 我们要为用户解决什么核心痛点?
  • 商业目标 (Business Goals): 这个项目要达成什么商业指标?(例如:降低成本、提升转化率)
  • 组织能力 (Organizational Capabilities):
    • 公司文化 (Company Culture): 我们的文化是拥抱变化还是追求稳定?
    • 团队现状 (Team Status): 团队的技术栈、技能水平和规模如何?

第二步:定义架构特性与约束 (Define Architectural Characteristics & Constraints)

这一步的目标是将第一步中模糊的需求,转化为具体、可度量的技术目标。

  • 识别架构特性 (Identify Architectural Characteristics / -ilities):
    • 从性能、可伸缩性、可用性、容错性、可维护性、安全性、成本等特性中,识别出本次设计最关键的 3-5 个。
    • 对它们进行排序。例如,对于一个后台管理系统,“可维护性”的优先级可能就高于“性能”。
  • 明确约束条件 (Define Constraints):
    • 有哪些不可逾越的红线?例如:预算上限、上线日期 (Time to Market)、必须使用公司内某技术平台、法律合规要求等。

第三步:探索方案与决策 (Explore Solutions & Make Decisions)

有了第二步清晰的目标和边界,我们现在可以带着这些标准去评估方案。

  • 探索可选方案 (Explore Options): 至少寻找 2-3 个备选方案。
  • 进行权衡分析 (Analyze Trade-offs): 基于第二步定义的架构特性优先级,系统地对比各方案的优劣。
  • 评估风险 (Assess Risks): 每个方案可能引入哪些短期或长期的技术、成本、人员风险?
  • 记录决策 (Document Decisions): 使用 ADR (Architecture Decision Record) 记录最终选择和放弃的原因。

第四步:设计实施路径与验证机制 (Design Implementation Path & Verification)

在真正开始大规模编码前,设计好如何走,以及如何验证我们走在正确的路上。

  • 实施计划 (Implementation Plan):
    • 是否需要技术原型 (PoC) 来验证关键难点?
    • 如何进行任务拆解和里程碑规划?
  • 构建适应度函数 (Build Fitness Functions):
    • 针对第二步定义的关键架构特性,设计具体的“检验尺”。
    • 例如:为保证“模块解耦”,设计一个静态代码检查规则,禁止模块间的非法调用。
  • 知识沉淀 (Knowledge Sedimentation): 准备好核心的架构图、设计文档等。

第五步:部署、观测与效果衡量 (Deploy, Observe & Measure Effectiveness)

将架构推向真实世界,并通过数据验证其价值。

  • 持续交付 (CI/CD): 作为将设计快速、可靠地部署到生产环境的手段。
  • 系统监控 (System Monitoring): 观测系统的健康状况(CPU、内存、延迟、错误率等)。
  • 业务指标验证 (Business Metrics Verification): (闭环关键) 验证是否达成了第一步定义的商业目标?例如,新架构上线后,用户转化率是否真的提升了?

第六步:复盘、沉淀与演进 (Retrospect, Internalize & Evolve)

  • 问题记录与根因分析 (Problem Record & Root Cause Analysis): 发生了什么?为什么会发生?
  • 流程与原则改进 (Process & Principle Improvement): 如何优化我们的设计流程、技术原则,避免未来再犯?
  • 人员与组织成长 (Personnel & Organizational Growth): 团队通过这次项目学到了什么?需要组织哪些培训?

Fundamentals of Software Architectrue 笔记梳理

1. 理解商业与组织上下文

利益相关方:他们的核心诉求和期望是什么?

用户视角:我们要为用户解决什么核心痛点?

商业目标:这个项目要达成什么商业指标?

组织能力:我们的文化是拥抱变化还是追求稳定?团队的技术栈、技能水平和规模如何?

1.1 谈判技巧

FOSA 指出,架构师必须理解并驾驭企业的政治环境。几乎每一个架构决策都会受到挑战,这可能来自产品负责人、项目经理、业务利益相关方(因为成本或时间增加),甚至是开发者(认为有更好的方法)。

因此,架构师需要具备卓越的谈判和引导技能 (Negotiation and Facilitation),以理解各方诉求,并在分歧出现时达成共识。

FOSA 给出了几种谈判思路:

  1. 利用语法和流行语更好地理解情况。 软件架构师应注意业务利益相关者在沟通中使用的短语和流行语。例如,像“我们需要零停机时间”或“我昨天就需要这些功能”这样的表述,虽然可能不精确,但却能揭示出对可用性或上市时间等方面的真正关注。通过利用这些“废话语法”,架构师可以更好地理解对方真正的担忧和需求,从而在谈判中占据优势。
  2. 在进入谈判之前收集尽可能多的信息。 在谈判之前,架构师应尽可能多地收集相关信息。例如,如果业务利益相关者坚持“五个九”的可用性(99.999%),架构师应提前研究这意味着什么,并将其转化为实际的停机时间(例如,每年约 31.5 秒的计划外停机时间)。充分掌握事实和数据有助于进行基于现实的讨论。
  3. 当一切都失败时,说明成本和时间。 这是最后的谈判策略。尽管成本和时间(投入的工作量)是任何谈判中的关键因素,但应作为最后的手段使用。过早提及这些可能会使谈判陷入僵局,因为它们可能会被视为阻止或拒绝的借口。
  4. 利用“分而治之”的原则来限定需求。 这一策略借鉴了孙子兵法中的思想,即“其力合者,离之”。当面临不合理或范围过大的要求时(例如,整个系统都需要“五个九”的可用性),架构师可以通过提问来缩小范围,确定哪些特定部分或功能真正需要这种高水平的特性。这样做可以减少困难且昂贵需求的范围,从而简化谈判。
  5. 永远记住演示胜于讨论。 当与同事或开发人员在技术方法上存在分歧时,与其争论不休,不如通过实际的演示来证明你的观点。例如,如果你认为消息队列比 REST 更适合特定的服务间通信,可以在模拟生产环境中进行 A/B 测试,用数据和实际结果来说服对方。实际操作的证据通常比理论争论更有说服力。
  6. 在谈判中避免过于争辩或让事情变得过于个人化——冷静的领导力结合清晰简洁的推理总能赢得谈判。在讨论中,如果气氛变得过于激烈或个人化,最好的做法是暂停谈判,待双方冷静后再重新进行。作为领导者,保持冷静和专业的态度,并用清晰、简洁的逻辑进行推理,往往能够有效化解冲突,促使对方退让,最终达成共识。
  7. 在说服开发人员采纳架构决策或执行特定任务时,提供理由而不是“高高在上地发号施令”。 架构师不应凭借职位来命令开发人员,而应通过提供充分的理由来说明为什么需要某个架构决策或任务。例如,解释“所有数据库调用都需要通过业务层”是为了“更好地控制变更”,这比单纯命令“你必须通过业务层”更容易被接受。理解背后的原因能促使开发人员更积极地接受并实施决策。
  8. 如果开发人员不同意某个决策,让他们自己找到解决方案。 当开发人员对某个技术决策有异议时,与其直接反驳,不如挑战他们,让他们自己去探索并证明他们的替代方案。例如,如果开发人员坚持使用某个框架但你认为它不符合安全要求,可以让他们自行研究并展示如何解决安全问题。这不仅能促进开发人员的学习和思考,也能让架构师在最终解决方案上获得团队的认可和支持,形成双赢局面。

1.2 业务理解

架构决策必须提供业务价值。如果一个架构决策没有业务价值,它可能就不是一个好的决策,需要重新考虑。

FOSA 强调,架构决策的商业合理性至关重要。常见的商业合理性包括:成本 (Cost)、上市时间 (Time to Market)、用户满意度 (User Satisfaction) 和战略定位 (Strategic Positioning)。在与业务利益相关方谈判时,要重点关注他们最看重的指标。

这里面的一大难点就是:业务方与开发方使用的不是同一种"语言"。双方对同一件事情的关注点是不一样的,所以表述出来的述求,也是不同的。所以架构师的职责就是需要将业务领域的关注点和架构特性进行对应。

比如:

Domain Concern Architecture characteristics
Mergers and acquisitions 合并与收购 互操作性 interoperability
可扩展性 scalability
适配性 adaptability
可扩展性 extensibility
Time to market 上市时间 灵活性 agility
可测试性 testability
可部署性 deployability
User satisfaction 用户满意度 性能 performance
可用性 availability
容错性 fault tolerance
可测试性 testability
可部署性 deployability
灵活性 agility
安全性 security
Competitive advantage 竞争优势 灵活性 agility
可测试性 testability
可部署性 deployability
可扩展性 scalability
可用性 availability
容错性 fault tolerance
Time and budget 时间和预算 简单性 simplicity
可行性 feasibility

另外, 随着业务的发展,关注点也是在不断发生变化的,这个时候,架构所侧重的架构特性也是随之改变。

2. 定义架构特性与约束

识别架构特性:从性能、可伸缩性、可用性、容错性、可维护性、安全性、成本等特性中,识别出本次设计最关键的 3-5 个。

明确约束条件:有哪些不可逾越的红线?

2.1 架构特性定义

架构师的核心职责之一就是识别和定义系统的架构特性 (Architecture Characteristics)。这些特性定义了系统的成功标准,并且通常与系统的功能性 (Functionality) 正交。

一个属性要成为架构特性(Architecture Characteristics),需至少满足 3 个条件:

  1. 指定非领域设计考量:架构特性关注的是应用程序"如何"实现需求以及做出某些选择"为何"的原因,而不是应用程序"应该做什么"的业务需求。例如,性能水平通常不会出现在需求文档中,但却是重要的架构特性。
  2. 影响设计的某个结构方面:如果一个架构特性需要特殊结构考虑才能成功,那么它就会上升到架构特性的层面。例如,一般的安全性对于几乎所有项目都是必需的,但当需要设计特定的模块、组件或服务来隔离关键安全问题时,安全才成为一个架构特性。
  3. 对应用程序的成功至关重要:应用程序可以支持大量的架构特性,但并非所有都应该被支持。支持每个架构特性都会增加设计的复杂性,因此,架构师的关键任务是选择最少的、对应用程序成功至关重要或重要的架构特性,而不是尽可能多的。

2.2 架构特性类型

  • 显性架构特性:是在需求规范中明确列出的,作为必要设计的一部分。它们通常直接出现在需求文档或其他具体说明中。
  • 隐性架构特性:很少出现在需求文档中,但它们对于项目的成功是必需的。架构师必须利用他们对问题领域的知识,在分析阶段发现这些特征。

可进一步细分为:操作特性、结构特性和交叉特性。

操作性架构特性涵盖了系统的运行能力,例如性能、可伸缩性、弹性、可用性和可靠性等。这些特性通常与运营和 DevOps 关注点高度重叠。

特性 说明
Availability 系统需要保持可用的时间长度;例如,如果需要 24/7 可用,则需要采取措施确保系统始终可用。它指的是软件可操作和可访问的程度。
Continuity 灾难恢复能力。
Performance 衡量应用程序请求和响应周期所需的时间。它包括压力测试、高峰分析、功能使用频率分析、所需容量和响应时间。它也可以是更具体的度量,例如首屏渲染时间,即网页首次可见的时间。
Recoverability 业务连续性要求(例如,发生灾难时,系统需要多快才能重新上线?)这将影响备份策略和对复制硬件的要求。它也指软件从故障中恢复的能力,通过恢复任何受影响的数据并重新建立系统的所需状态。
Reliability/Safety 评估系统是否需要具备故障安全能力,或者其任务关键性是否影响生命。如果系统发生故障,是否会给公司带来巨额损失。它指系统在指定条件下和指定时间内运行的程度。
Robustness 在互联网连接中断、断电或硬件故障时,处理错误和边界条件的能力。
Scalability 系统随着用户或请求数量的增加而执行和运行的能力。这意味着处理大量并发用户而不会出现严重的性能下降。

结构性架构特性关注代码结构。在许多情况下,架构师对代码质量问题负有独立或共同的责任,例如良好的模块化、组件间的受控耦合、可读性强的代码以及其他内部质量评估。

特性 说明
Configurability 最终用户通过可用界面轻松更改软件配置方面的能力。
Extensibility 系统的可扩展性。
Installability 系统在所有必要平台上安装的便捷性。它指软件在指定环境中安装和/或卸载的程度。
Leverageability/Reuse 跨多个产品利用通用组件的能力。它指开发人员在多个系统或构建其他资产中重复使用资产的程度。
Maintainability 开发人员修改、纠正或使其适应环境和/或需求变化的有效性和效率程度。
Portability 系统是否需要在多个平台上运行。它指开发人员将系统、产品或组件从一个硬件、软件或其他操作或使用环境转移到另一个环境的程度。
Supportability 应用程序所需的技术支持级别。系统中调试错误所需的日志记录及其他设施的级别。
Upgradeability 从该应用程序/解决方案的旧版本轻松/快速升级到新版本的能力。

交叉架构特性指的是那些难以归类或超出传统类别,但却形成重要设计约束和考虑的特性。

特性 说明
Accessibility 确保所有用户(包括色盲或听力障碍等残障用户)能够访问系统。它指使软件可供具有最广泛特征和能力的人使用。
Archivability 数据是否需要在一段时间后归档或删除。
Authentication 确保用户是其所声称的身份的安全要求。
Authorization 确保用户只能访问应用程序内特定功能(按用例、子系统、网页、业务规则、字段级别等)的安全要求。
Legal 系统在哪些法律约束下运行(数据保护、萨班斯-奥克斯利法案、GDPR 等)?公司需要哪些保留权利?关于应用程序构建或部署方式的任何规定。
Privacy 隐藏内部公司员工交易信息的能力(加密交易,甚至数据库管理员和网络架构师都无法查看)。
Security 数据是否需要在数据库中加密?内部系统之间网络通信是否需要加密?远程用户访问需要何种类型的认证?它指软件保护信息和数据的程度,以便人员或其他产品或系统具有与其授权类型和级别相称的数据访问程度。
Supportability 应用程序所需的技术支持级别。系统中调试错误所需的日志记录及其他设施的级别。
Usability/Achievability 用户使用应用程序/解决方案实现目标所需的培训水平。它指用户可以有效、高效、满意地使用系统达到预期目的。

2.3 架构特性选择

架构特性不是越多越好:

  • 增加系统设计的复杂性:每增加一个架构特性,都会使整个系统设计变得更加复杂。支持过多的架构特性会导致在架构师和开发人员开始解决核心业务问题之前,系统就变得越来越复杂。
  • 分散对核心问题的关注:架构特性定义了系统的成功标准,通常与系统的功能性正交,关注的是“如何”实现需求以及“为什么”做出某些选择。然而,如果过度追求特性数量,可能会导致偏离原始的业务问题,即开发软件的最初动机。
  • 每个特性都涉及权衡:软件架构中的每一个方面都存在权衡,有优点也有缺点。例如,在拍卖系统中,选择使用主题(topic)进行通信可能带来架构可扩展性的优势和服务的解耦,但会引入数据访问和数据安全方面的潜在问题,并且不支持异构契约。而使用队列(queue)则允许每个消费者拥有自己的契约,但不具备可扩展性,并且会增加服务间的耦合。架构师需要分析这些权衡,并根据业务驱动因素和环境选择最重要的特性。
  • 过度规范的危害:架构师过度规范架构特性是常见的陷阱,其破坏性不亚于规范不足,因为它会使系统设计过于复杂。历史案例“瓦萨号”战舰的失败就是一个例证,它是因为过度追求建造最宏伟的战舰(即过度规范架构特性)而最终导致沉没。
  • 陷入“意外复杂性”陷阱:架构师有时会为解决方案、图表和文档添加不必要的复杂性。正如一位作者所言,“开发者被复杂性吸引,就像飞蛾扑火一样——结果往往相同”。这种“意外复杂性”是由于人为地使问题复杂化,而不是问题本身固有的复杂性。通过识别子领域类型并根据其业务逻辑的复杂性选择合适的实现模式(例如,事务脚本和活动记录适用于简单业务逻辑,而领域模型和事件溯源领域模型适用于复杂的核心子领域),可以避免引入不必要的复杂性。
  • 设计应由业务驱动:领域驱动设计(DDD)的核心思想在于让业务领域驱动软件设计决策。这意味着设计决策应该基于业务领域的需求和战略,而非盲目地堆砌所有可能的架构特性。

因此,与领域利益相关者合作时,架构师应努力使最终的架构特性列表尽可能短,因为每个特性都会增加总体系统设计的复杂性。

3. 探索方案与决策

探索可选方案 :至少寻找 2-3 个备选方案。

进行权衡分析:基于第二步定义的架构特性优先级,系统地对比各方案的优劣。

评估风险:每个方案可能引入哪些短期或长期的技术、成本、人员风险?

记录决策:使用 ADR (Architecture Decision Record) 记录最终选择和放弃的原因。

3.1 架构风格

3.1.1 分层架构 Layered Architecture

3.1.1 分层架构

分层架构的核心驱动力关注点分离(Separation of Concerns)。它将一个复杂的系统按照不同的职责或技术关注点,垂直地划分成若干个水平的“层(Layer)”。

这些层之间存在一个至关重要的约束:依赖关系是单向的。通常来说,上层可以依赖下层,但下层绝对不能依赖上层。例如,表现层可以调用业务逻辑层,但业务逻辑层不应该知道任何关于表现层的具体实现细节。

优点:

  • 简单性(Simplicity)和低成本(Cost):分层架构模式非常成熟,广为人知,开发团队的学习成本极低。对于中小型项目、预算有限的初创公司或内部管理系统,它是一个"足够好"的、性价比极高的起点。
  • 可维护性(Maintainability):如前所述,只要遵循了隔离层原则,系统的维护和迭代会非常清晰。对于那些业务逻辑相对稳定、变更不频繁的系统,这是一个巨大的优势。
  • 整体可部署性(Deployability):分层架构天然倾向于构建单体应用(Monolith)。整个应用被打包成一个单元(例如一个 WAR 包或一个可执行文件)进行部署。这极大地简化了部署和运维的复杂度,尤其是在项目早期或运维能力有限的团队中。

缺点:

  • 技术分区而非领域分区:分层架构是一种技术分区架构。这意味着它的组件是根据其在架构中的技术角色(如表示层、业务层、持久层),而不是根据业务领域(如客户、订单)进行分组的。这会导致任何特定的业务领域(例如“客户”领域)的逻辑都会分散在架构的所有层中。同时,当需要对特定业务领域的需求进行更改时,由于其逻辑分散在多个技术层中,开发人员必须在所有相关层中进行修改,这降低了开发的敏捷性。
  • 部署风险高:在分层架构中,即使是对少量代码的更改(例如,一个类文件中简单的三行更改),也需要重新部署整个部署单元。这种部署往往会捆绑数十个其他更改,从而显著增加了部署风险,且部署频率受到限制。
  • 测试范围大且不完整:由于整个应用程序是作为一个大型单体单元部署的,开发人员通常不会为简单的三行更改花费数小时执行完整的回归测试套件。这导致测试覆盖范围不完整,并且难以确保更改不会影响看似不相关的部分。

3.1.2 管道架构 Pipeline Architecture

3.1.2 管道架构

管道架构,又称为管道与过滤器架构(Pipes and Filters Architecture),是一种用于处理数据流的强大模式。它的核心思想非常直观,就像一条工厂的流水线:原材料从一端进入,经过一系列独立工站的加工、处理、检验,最终在另一端形成成品。

要理解管道架构,首先要理解它的两个基本构件:

  • 过滤器 (Filter):它是一个独立的、可执行的处理单元,负责接收数据、执行单一任务(例如转换格式、过滤内容、扩充信息),然后将处理后的数据传递出去。关键在于,每个过滤器都是自包含(Self-Contained)无状态(Stateless)的,它不关心上一个过滤器是谁,也不关心下一个过滤器是谁。
  • 管道 (Pipe):代表流水线上的"传送带"。它是一个单向的数据通道,负责将一个过滤器处理完的数据传递给下一个过滤器。

过滤器一般又分为 4 种:

  • 生产者 (Producer / Source):作为整条管道的起点。它不接收来自管道的数据,而是负责创建数据,并将这些初始数据泵入管道。
  • 转换器 (Transformer):它从上游管道接收数据,对其进行某种形式的修改或转换,然后将结果发送到下游管道。
  • 测试器 (Tester):它接收数据,并根据一个或多个条件对数据进行检验。如果数据满足条件,就将其传递到下游管道;如果不满足,则数据流在此处被中断(或被导向另一条错误处理管道)。
  • 消费者 (Consumer / Sink):作为整条管道的终点。它从上游管道接收最终处理好的数据,并将其消费掉,通常不会再将数据传递出去。

优点:

  • 成本低且简单:作为一种单体架构,管道架构不具备分布式架构风格所带来的复杂性,因此它简单易懂,并且构建和维护成本相对较低。
  • 高模块化:通过不同过滤器类型之间关注点的分离,实现了架构的模块化。任何过滤器都可以修改或替换而不影响其他过滤器。
  • 部署性和可测试性较好:由于其模块化程度较高,部署性和可测试性略优于分层架构,但仍受单体应用固有的部署仪式、风险和测试完整性等因素的影响。

缺点:

  • 单体特性带来的限制:尽管在模块化方面有所改进,但它仍然是一种单体应用。这意味着部署的仪式感、风险、部署频率以及测试的完整性都会受到单体特性的影响。例如,对任何更改都需要测试和部署整个单体应用。
  • 弹性低:由于其单体部署和缺乏架构模块化,管道架构的弹性评级非常低(一星)。尽管可以在单体内部实现某些功能的伸缩,但这通常需要复杂的设计技术,而管道架构并不擅长此道。
  • 可伸缩性差:与弹性类似,由于是单体架构且缺乏模块化,可伸缩性也评级很低。应用程序的伸缩能力受限于单一系统量子。
  • 性能一般:管道架构不适合高性能系统,因为它缺乏并行处理能力、存在闭合分层(closed layering)以及可能出现"架构下沉"(sinkhole anti-pattern)问题。

3.1.3 微核架构 Microkernel Architecture

3.1.3 微核架构

微核架构,也被称为插件化架构(Plug-in Architecture),是一种能够提供极高扩展性、灵活性和演化能力的系统设计模式。它的核心思想是将系统功能划分为两部分:一个最小化的、稳定的核心系统(Core System)和一个由独立插件组件(Plug-in Components)构成的可扩展生态。

  • 核心系统 (Core System):这是架构的"微核"。它的职责被严格限制在最小且必要的范围内,通常只包含:
    1. 系统运行所必需的通用业务逻辑(例如,一个 IDE 的文件管理和基础编辑器)。
    2. 一个至关重要的插件管理机制,包括插件的注册、发现、生命周期管理等。这是连接核心与插件的桥梁。
  • 插件组件 (Plug-in Components):这些是独立的、可插拔的模块,用于实现扩展功能或特定业务逻辑。每个插件都通过一个由核心系统定义的标准契约(Standard Contract)来与核心交互。这个契约通常是一个接口或一组 API。

优点:

  • 高模块化与扩展性:微内核架构通过插件组件实现了高度模块化和扩展性。应用程序逻辑被划分为核心系统和独立的插件组件,从而提供了可扩展性、适应性以及应用程序特性和自定义处理逻辑的隔离。任何插件都可以修改或替换而不影响其他组件,例如,添加一个新的电子设备评估逻辑只需添加一个新的插件组件并更新注册表。
  • 成本较低且相对简单:作为一种单体架构,微内核架构避免了分布式架构风格所带来的复杂性,因此它简单易懂,并且构建和维护成本相对较低。
  • 部署性和可测试性较好:由于其模块化程度较高,功能可以隔离到独立的插件组件中。如果做得好,这可以减少整体测试范围并降低部署风险,尤其是在运行时部署插件组件的情况下。因此,可部署性和可测试性略优于分层架构。
  • 领域与架构的同构性:微内核架构可以同时进行领域分区和技术分区。对于需要针对每个位置或客户端进行不同配置的问题,或者那些强调用户定制和功能扩展性的产品(例如 Jira 或Eclipse IDE),这种架构风格非常适用。

缺点:

  • 单体特性带来的限制:尽管在模块化方面有所改进,但它仍然是一种单体应用。这意味着部署的仪式感、风险、部署频率以及测试的完整性都会受到单体特性的影响。
  • 弹性低:由于其单体部署和缺乏架构模块化,微内核架构的弹性评级非常低(一星)。尽管可以在单体内部实现某些功能的伸缩,但这通常需要复杂的设计技术。
  • 可伸缩性差:与弹性类似,由于是单体架构且缺乏模块化,可伸缩性也评级很低(一星)。所有请求都必须通过核心系统才能到达独立的插件组件

3.1.4 基于服务的架构 Service-Based Architecture

3.1.4 基于服务的架构 SBA

如果说单体(Monolith)和微服务(Microservices)是两个广为人知的端点,那么基于服务的架构(Service-Based Architecture, SBA)就是它们之间那个常常被忽略,却又极具现实意义的"务实中间派"。它既非庞大到笨拙,也非精细到繁杂,为许多成长中的系统提供了一条平滑的演进路径。

SBA 的本质是一种将一个大型的单体应用,分解为少数几个、逻辑独立的、可独立部署的"服务" 的架构风格。SBA 的服务数量通常不多,一般在 4 到 12 个之间。它不像微服务那样追求极致的拆分(可能会有几十上百个服务),而是将应用按照核心的业务领域(Domain)进行划分。

与微服务不同的是 SBA 的典型实现是,所有服务共享同一个数据库。这种设计的初衷是为了在享受独立部署带来的好处的同时,最大限度地降低数据层面的复杂性。共享数据库可以:

  • 简化开发:开发者无需处理复杂的分布式事务和跨服务数据同步问题。
  • 保证数据一致性:传统的 ACID 事务可以在数据库层面轻松实现。
  • 降低技术门槛:团队无需掌握复杂的分布式数据管理技术。

随着业务发展,共享数据库的弊端会逐渐显现。在以下情况下,拆分数据库就成了合理的选择:

  1. 服务资源争用 (Service Contention):某个服务(如高流量的商品浏览服务)对数据库产生巨大压力,影响了其他关键服务(如订单服务)的性能。
  2. 数据隔离与安全 (Data Isolation and Security):某个服务处理的数据高度敏感(如支付服务中的金融信息),需要从主数据库中物理隔离出来,以满足合规性或安全要求。
  3. 技术栈不匹配 (Technology Mismatch):某个服务有特殊的数据存储需求。例如,搜索服务最适合使用 Elasticsearch,而核心业务数据则存储在关系型数据库中。

当这些情况发生时,SBA 允许你"渐进式"地将某个服务连同其数据一起剥离出去,赋予它独立的数据库。

优点:

  • 可部署性 (Deployability):这是最大的优势之一。每个服务都可以独立部署,使得发布更加频繁、风险更低。
  • 模块化 (Modularity):通过按领域划分服务,实现了清晰的业务模块边界。
  • 可维护性 (Maintainability):每个服务的代码库规模远小于整个单体,更易于理解、修改和维护。
  • 容错性 (Fault Tolerance):一个服务的崩溃不会导致整个应用程序宕机(尽管共享数据库可能成为共同的故障点)。
  • 保留ACID事务:这是其相对于其他细粒度分布式架构(如微服务)的一大优势。由于领域服务是粗粒度的,事务通常限制在一个服务内部,可以利用传统的 ACID 事务来保证数据完整性和一致性

缺点:

  • 弹性低:尽管可以在单体内部实现某些功能的伸缩,但由于其单体部署和缺乏架构模块化,弹性评级仍然较低。
  • 可伸缩性受限:虽然可以扩展,但由于服务粒度较粗,与微服务等细粒度服务相比,在机器资源方面效率不高,成本效益也较低。
  • 部署风险:虽然比传统单体应用有所改进,但由于部署的代码量仍然较大,其部署风险仍然高于微服务架构。

3.1.5 事件驱动架构 Event-Driven Architecture

3.1.5 事件驱动架构

在传统的请求驱动模型中,系统接收请求后会确定性地、同步地将请求路由到各个请求处理器来处理数据。而事件驱动模型则不同,它对特定情况做出反应,并根据该事件采取行动

EDA 的力量源泉来自于异步通信,它有以下优点:

  1. 极高的系统韧性与可用性 (Resiliency and Availability):在同步调用中,如果服务 B 宕机,服务 A 的调用会立刻失败,导致整个链路中断。但在异步模式下,服务 A 将事件发送给一个中间人(消息代理),然后就可继续自己的工作。即使服务 B 此时宕机,事件也会被安全地存放在代理中,待 B 恢复后再进行处理。这使得系统能够优雅地处理局部故障,整体可用性大大提高。
  2. 卓越的可伸缩性与弹性 (Scalability and Elasticity):生产者和消费者被完全解耦,可以独立进行伸缩。如果事件产生的速度突然加快,我们只需要增加消费者实例的数量即可,而无需对生产者做任何改动。这种按需、独立伸缩的能力是构建高弹性系统的关键。

典型的 EDA 有 2 种拓扑,分别为代理模式(broker)和中介者模式(mediator),二者最大的区别在于后者具有一个统一的协调者,这会对异常处理、全局统筹有很好的管控手段,当同时也牺牲了系统的解耦程度、灵活度和性能。

在 EDA 中,有几个典型的问题需要关注:

  • 异常处理:可采用 workflow event pattern 工作流事件模式。事件处理后,如果失败了,就告知 workflow processworkflow processor 识别错误,如果能自动处理,就自动处理,并丢回原始队列中,重新执行。如果不能处理,就放到 dashbord 上,人工检查、校正或重试。

    workflow event pattern 工作流事件模式
  • 数据丢失:发送事件到 channel 的路上、channel 转发事件到处理器的路上和处理器处理完持久化到 db 的路上都有可能发生数据的丢失。可以通过同步发送、持久化队列、ACK 机制和事务型 DB 来解决这个问题。

    防止 EDA 数据丢失的思路
  • 返回响应:如果希望在事件驱动架构中实现请求-响应的能力,可以消息的两个元数据字段:回复地址 (Reply-To)关联标识 (Correlation ID) 来通过回传通道返回响应数据。

    EDA 返回响应数据的处理思路

优点:

  • 可伸缩性与弹性 (Scalability & Elasticity):独立伸缩组件的能力是其核心优势。
  • 可扩展性 (Extensibility):系统极易扩展。当需要增加新功能时,只需开发一个新的服务来订阅感兴趣的现有事件即可,完全无需改动已有服务。
  • 响应性 (Responsiveness):对于需要快速响应用户的系统,可以将耗时任务异步化。例如,用户提交视频后,系统立即返回"上传成功,正在处理中",然后通过事件驱动后台的转码、审核等一系列复杂流程。

缺点:

  • 简单性 (Simplicity):EDA 显著增加了系统的复杂性。你需要管理消息代理,处理异步编程的挑战(如调试、错误处理),并应对最终一致性带来的心智负担。
  • 事务性 (Transactional):实现跨多个服务的原子性操作(即分布式事务)变得异常困难。虽然可以通过 Saga 等模式来模拟长事务,但其实现复杂,且只能保证最终一致性而非强一致性。
  • 工作流的可观测性 (Observability of Workflow):尤其是在代理拓扑中,业务流程被分散到各个独立的处理器中,没有一个集中的地方可以让你直观地看到一个完整的业务流程是如何执行的,这给监控和排错带来了巨大挑战。

3.1.6 空间架构 Space-Based Architecture

3.1.6 空间架构

传统三层 Web 拓扑在用户量剧增时呈倒三角:Web 层易横向扩容,数据库层最难扩容,最终成为性能上限。为削弱数据库瓶颈,业界先用本地缓存,再出现集中式分布式缓存,但网络跳转仍是热点。把数据直接放到每个处理节点的 复制型内存网格 并实时同步,才真正让数据库从"同步路径"上消失,空间架构由此成形。

空间架构的名称来源于元组空间(Tuple Space)多个并行处理器通过共享内存进行通信。SBA 的核心理念便是将应用数据保存在内存中(in-memory),并在所有活跃的处理单元(Processing Units)复制,从而移除中心数据库作为同步约束,实现近乎无限的伸缩性。

空间架构由以下几个部分组成:

  • 处理单元 Processing Unit:

    • 处理单元包含了应用逻辑(包括基于 Web 的组件和后端业务逻辑)。

    • 它还包含一个内存数据网格复制引擎,通常由 Hazelcast、Apache Ignite 或 Oracle Coherence 等产品实现。

    • 处理单元可以包含小型、单一用途的服务,类似于微服务

  • 虚拟化中间件 Virtualized Middleware:虚拟化中间件负责处理架构中的基础设施问题,控制数据同步和请求处理。它由以下四个关键组件组成:

    • 消息网格(Messaging Grid):它负责将请求转发到任何可用的处理单元。

    • 数据网格(Data Grid):它是 SBA 中最重要和关键的组件,通常在处理单元内部以复制缓存的形式实现。它确保每个处理单元都包含完全相同的数据,数据复制是异步且快速的。

    • 处理网格(Processing Grid):这是一个可选组件,用于管理协调请求处理,当一个业务请求涉及多个处理单元时,它会协调这些处理单元之间的请求。

    • 部署管理器(Deployment Manager):该组件根据负载条件管理处理单元实例的动态启动和关闭,对于实现应用的弹性伸缩至关重要。

  • 数据泵 Data Pumps:数据泵是将数据发送到另一个处理器,然后该处理器更新数据库的方式。它们总是异步的,提供内存缓存与数据库之间的最终一致性(Eventual Consistency)。消息机制是数据泵的常用实现方式,因为它支持异步通信、保证消息传递和维护消息顺序。

  • 数据写入器 Data Writers:数据写入器(Data Writers)负责接收来自数据泵的消息,并用消息中包含的信息更新数据库。它们可以是服务、应用或数据中心(如 Ab Initio)。写入器的粒度可以根据数据泵和处理单元的范围而变化,例如,领域驱动的数据写入器可以处理特定领域(如客户)内的所有更新。

  • 数据读取器 Data Readers:负责从数据库读取数据,并通过反向数据泵将其发送到处理单元。服务需要通过数据读取器访问数据的情况有三种:

    1. 所有相同命名缓存的处理单元实例都崩溃时。
    2. 所有相同命名缓存的处理单元需要重新部署时。
    3. 需要检索复制缓存中不包含的归档数据时。

空间架构最大的一个问题就是数据冲突,不同的 processing unit 处理同一个业务逻辑相关的数据时,由于数据同步存在时序问题,所以很容易出现数据不一致的情况。

可以从以下几个因素进行冲突概率的评估:

  • N:处理相同缓存的 processing unit 的数量
  • UR:缓存更新频率
  • S:缓存大小
  • RL:缓存复制的延迟

CollisitionRate = N × (UR2/S) × RL

如果估算出来的冲突概率无法接受,或者需要缓存在内存中的业务数据过多而超过单机负载时,也可以使用分布式缓存来替代复制缓存。

优点:

  • 弹性(Elasticity):处理单元可以根据负载动态启停,实现高度弹性。
  • 伸缩性(Scalability):通过内存数据缓存和移除数据库约束,支持处理数百万并发用户。
  • 性能(Performance):移除了数据库瓶颈,提供了极高的性能。

缺点:

  • 简洁性(Simplicity):SBA 是一种非常复杂的架构风格,因为它涉及到缓存、最终一致性以及众多动态组件。
  • 可测试性(Testability):由于需要模拟极高的伸缩性和弹性负载,测试复杂且成本高昂,许多高负载测试甚至需要在生产环境中进行,带来巨大风险。
  • 成本(Cost):由于缓存产品许可费和高资源利用率,SBA 通常相对昂贵。

3.1.7 面向服务架构 Orchestration-Driven Service-Oriented Architecture

3.1.7 面向服务架构

编排驱动的面向服务架构(Orchestration-Driven Service-Oriented Architecture,简称 SOA)是一种在特定时代背景下演变而来的软件架构风格。它在 20 世纪 90 年代末企业快速扩张、需要更复杂的 IT 系统来适应增长的背景下出现。

  • 资源稀缺性:在开源操作系统尚未被认为足够可靠用于严肃工作之前,操作系统和商业数据库服务器的许可费用昂贵且按机器收费。这导致架构师们被要求尽可能地实现重用,以优化成本。
  • 企业级重用:SOA 的一个主要目标是实现服务层面的重用,即逐步构建可随时间增量重用的业务行为。大型公司厌倦了重复编写软件,因此采取了逐步解决这个问题的策略。
  • 技术分层:这种架构风格也将技术分层理念推向了极致。其驱动哲学围绕着企业级的重用展开。

这个架构在历史进程中是一个反面教材,它是核心思想就俩字:复用

失败的最核心原因:过度重视技术,以技术为导向进行模块划分和复用尝试,而业务是不断演进变化的,最终技术与业务之间的隔阂无法弥补,功亏一篑。

其他原因还有:

  • 过度追求复用导致的高度耦合
  • 编排引擎成为巨大的耦合点和瓶颈
  • 技术分区带来的业务流程碎片化

3.1.8 微服务架构 Microservice Architecture

3.1.8 微服务架构

微服务架构的核心在于高度解耦。它倾向于复制而非耦合。这意味着,如果架构师的目标是高度解耦,那么他们会选择复制而不是重用。微服务通过物理上建模限界上下文(Bounded Context)的逻辑概念来实现高度解耦。

限界上下文(Bounded Context)是微服务设计理念的核心驱动力。这是一个愿与领域驱动设计(DDD)的概念。限界上下文代表了一种解耦风格。在限界上下文内,与特定领域相关的所有内部组件(如代码和数据库模式)都是紧密耦合的,但它们与外部限界上下文的任何内容(如其他数据库或类定义)是解耦的。

这种隔离使得每个服务可以独立演进,定义其自身所需的一切,而不必适应其他部分的约束。它避免了传统单体架构中常见的共享类和数据库作为集成点导致的紧密耦合问题

所以微服务也是一个典型的领域分区架构,并且它倾向于将领域分区推到极致。

在划分微服务粒度时,以下三个方面是需要重点考虑的:

  1. 目的(Purpose):微服务的首要目的应该是捕获一个领域或工作流。理想情况下,每个微服务都应该具有极高的功能内聚性,为整个应用程序贡献一个重要的行为。这意味着,服务应该专注于一个单一的、明确的业务功能。
  2. 事务(Transactions):限界上下文是业务工作流,通常需要在事务中协作的实体可以为服务边界提供良好的指示。由于分布式事务在分布式架构中会带来复杂性,架构师应尽量设计系统以避免跨服务的事务。如果需要跨服务事务,这可能表明服务粒度过细。事务边界通常是服务粒度的常见指标。
  3. 通信(Communication):如果一组服务为了完成功能而需要大量通信,那么将这些服务捆绑成一个更大的服务可能有助于避免过度的通信开销。换句话说,如果服务变得过于“多话”(chatty),频繁地相互调用,那么它们的边界可能需要重新评估,以减少不必要的全局复杂性

此外,业界也有一些其他的常用的判断方法:

  1. 变更频率:把一起变更/部署的东西放在一个服务,频率不同的拆开。
  2. 耦合指标:如果拆分后跨服务调用暴增,说明拆太细;反之,如果内部复杂度过高且团队协作困难,可能太粗。
  3. 认知负荷:一个团队能完全理解并独立维护的范围通常就是一个合理服务边界。

在微服务架构中,有几个典型的问题需要关注:

  • 基础设施复用:虽然微服务倾向于复制而非耦合,不过这更多是在业务层面,对于运维层面的基础设施,包括但不限于:监控(Monitoring)日志记录(Logging)断路器(Circuit Breakers)服务发现(Service Discovery),微服务是主张进行统一建设和复用的。

  • 服务协作方式:一般有编舞和编排 2 种协作方式:

    • 编舞(Choreography):是指多个服务相互之间直接通信,而没有中央协调器。服务(如同舞者)根据彼此发出的事件或信息自主响应和行动。
    • 编排(Orchestration):是指通过一个单独的协调器服务来管理和控制工作流中多个服务的协调。协调器(如同乐队指挥)负责指导每个服务的执行顺序,并处理整个业务流程的状态和错误。在微服务中,架构师可以创建局部化的协调器服务来处理复杂的业务流程。

    微服务两者都支持。 不过编舞方式更符合微服务的高度解耦哲学,因为它不依赖于中央协调器,而是通过解耦的事件来实现通信,使用起来更简便。当然,在复杂的业务流程中,编舞环境下的错误处理和协调会变得更加复杂。如果业务流程本质上是耦合的,此时编排可能更为适合。

  • 数据一致性:微服务主张尽可能避免分布式事务的问题,如果多个服务经常需要处理分布式事务问题,那最好将它们合而为一,直接在一个 ACID 事务中完成。在万不得已的时候,也可以采用如 saga 和最终一致性、人工补偿等方式来缓解数据一致性问题。

优点:

  • 高度解耦与小部署单元:微服务架构极力推崇高度解耦。每个服务都是极小的部署单元,且具备高度的独立性。这种解耦使得团队可以独立地开发、测试和部署服务,大大减少了对其他服务的依赖,从而提高了敏捷性。
  • DevOps 革命与自动化:微服务架构的成功离不开 DevOps 革命和对操作关注点的自动化。自动化部署、自动化测试等现代工程实践是微服务存在的基础,它们极大地提高了部署频率、降低了部署风险,并保证了测试的完整性。
  • 更快的变更响应速度:由于服务范围小且高度解耦,当业务需求发生变化时,团队只需修改受影响的少量服务,而不是整个大型单体。这种增量式的演进能力使得组织能够更快地响应市场变化,提高时间到市场(time-to-market)的速度
  • 单一职责与清晰边界:每个微服务都专注于一个单一的业务功能或领域。这种清晰的职责边界使得开发人员更容易理解、测试和维护代码,因为他们不必处理与服务无关的复杂性

缺点:

  • 网络调用开销(Network Call Overhead):微服务是分布式架构。这意味着服务之间(乃至用户界面与服务之间)的通信需要通过网络进行。网络调用比本地方法调用耗时更长。当一个业务请求需要链式调用多个微服务时,累积的网络延迟会显著影响整体响应时间。
  • 安全验证开销(Security Verification Overhead):在微服务架构中,由于每个服务都是独立的部署单元,因此每个服务端点都需要进行安全验证。这增加了额外的处理时间。这种“在每个入口处进行安全检查”的模式进一步降低了同步、高度分布式架构(如微服务)的性能。
  • 高复杂性(Complexity):作为一种分布式架构,微服务固有的缺点在于运行时连接各个部分所带来的复杂性,为了解决由此带来了一系列问题,需要学习、使用甚至开发一系列的组件,会给团队带来更大的心智负担和运维难度。
  • 数据一致性(Data Consistency):如上所述,但无法避免分布式事务时,为了处理数据一致性问题,会引入很大的非业务复杂性。

3.2 架构选择

软件架构第一原理:一切都是权衡

软件架构第二原理:为什么比如何更重要

在选择架构时,最典型的 3 个问题:

  1. 单体还是分布式架构?
  2. 数据存在哪里?
  3. 异步还是同步通信?

3.3 风险评估

从 2 个维度进行打分:

  1. 风险的影响面
  2. 风险出现的可能性
架构风险评估矩阵

在分析时,不要企图一次性对所有的架构特性进行分析,拆开了,逐一击破,避免一次性关注点太多,从而不知所向。

3.4 架构决策

4. 设计实施路径与验证机制

5. 部署、观测与效果衡量

6. 复盘、沉淀与演进