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

第一步:理解商业与组织上下文 (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 笔记梳理

本章笔者将打散 FOSA 书中的各个知识点,并将它们贯穿在我们上面提到的整个架构设计闭环中,同时会添加一些书中没有的内容进行补充扩展。

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.2.1 单体 vs 分布式

当团队规模有限、需求节奏温和,而且必须尽快交付可用版本时,单体依旧是上市速度最快且认知成本最低的形态:所有模块共用同一进程,Debug、部署、回滚都异常直接。

然而,随着业务子域越来越多、发布节奏愈发碎片化,巨石应用往往演变成"所有人都必须一起上线或一起停机"的瓶颈。此时把系统拆成若干服务,允许各自独立发布,能显著缓解排期冲突;同时也可以针对流量热点的子域单独扩容,而非整包扩容。

带来的复杂度在于网络调用、链路追踪、容错和 DevOps 自动化,一旦这些配套不到位,分布式的优势就会被运维复杂度和认知成本抵消。换言之,拆分前要先确认组织是否具备持续交付、自动化监控、故障演练等能力,否则分布式只会把"技术债"换成"组织债"。

3.2.2 数据存储

如果系统只处理核心交易并且对强一致性要求极高,一体化的关系数据库依旧能提供最成熟、最易掌控的事务保障。随着并发数和存储量攀升,分库分表成为横向扩展的常规做法,但需要额外的分布式事务模式或 Saga 来保证业务完整性。

如果读写模式呈现极强的峰谷或结构多变,就非常适合引入键值、文档、列式乃至时序、图数据库等多模型共存策略。这样做的关键在于为每一类数据访问场景挑选最经济的存储形式,同时在数据治理层面清晰定义数据主权、法务合规和生命周期。

3.2.3 同步 vs 异步

一般原则:优先使用同步通信,必要时使用异步通信。

同步调用(如 REST 或 gRPC)带来的是即时反馈和易于调试的调用链,适用于用户交互需要立刻响应的场合。然而它也拉高了两个服务在时间维度上的耦合:只要任意环节超时或故障,整个链路都会受影响。

异步消息则通过中间件把调用方与被调用方解耦,让系统可以削峰填谷并获得天然的弹性缓冲区;代价是业务体验不再“即时”,而且需要额外处理幂等、重复消费、消息顺序、死信等问题。通常情况下,读取或修改单一资源这一类“命令/查询”仍倾向同步;任务排队、事件通知、工作流编排与数据集成则更适合异步。若核心场景必须保证强一致性,仍可采用同步事务或锁;而能够容忍短暂的不一致时,则转而采用事件驱动的最终一致模式。

3.3 风险评估

架构风险评估矩阵

风险的影响面(Impact):这个维度主要评估一旦风险发生,会对系统、业务、用户产生多大程度的负面影响。

  • 低影响(Low): 影响范围小,可控性强。例如,某个非核心模块的性能略有下降,只影响少量用户,且有明确的降级方案。或者,故障恢复时间短,对整体业务影响微乎其微。
  • 中影响(Medium): 影响范围较大,但可控。例如,系统某个核心功能出现短暂不可用,影响部分用户,但可以通过人工干预或备用方案快速恢复。业务运营会受到一定影响,但不会造成灾难性的损失。
  • 高影响(High): 影响范围广,失控性强。例如,系统核心服务大面积宕机,导致业务全面停止。或者,数据出现严重损坏,造成不可挽回的损失。

风险出现的可能性(Likelihood):这个维度主要评估风险发生的概率。

  • 低可能性(Low): 发生概率很小。例如,系统依赖的某个成熟、稳定的第三方服务,过去几年从未出现过故障。或者,经过充分的测试和验证,某个技术方案的潜在问题已经被基本排除。
  • 中可能性(Medium): 发生概率一般。例如,某个新技术或新组件,虽然经过了小规模测试,但在大规模生产环境下的表现还未得到充分验证。或者,架构依赖的某个外部系统,其 SLA(服务等级协议)历史记录显示偶尔会出现短暂的抖动。
  • 高可能性(High): 发生概率很高。例如,在高峰期对数据库进行无主键大批量更新操作,必然会导致锁表和性能问题。或者,系统设计存在明显的单点故障,一旦该节点出现问题,整个系统就会瘫痪。

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

3.4 架构决策

3.4.1 Anti-Pattern1: Covering Your Assets

害怕承担责任,总是希望有更高级别的人来拍板。决策过程变得极其缓慢,甚至为了规避风险而选择最保守、最平庸的技术方案,而不是最合适的方案。

应对方案:

  • Fact(事实): 聚焦于客观事实和数据。在做技术选型或架构决策时,不要只凭感觉或经验,而是要基于事实,如性能测试报告、技术预研结果、业界最佳实践、开源社区活跃度等。当所有人都基于事实说话时,决策的对错就更容易被评估和追溯,而非个人责任。
  • Options(可选方案): 明确列出所有可行的备选方案,并分析它们的优缺点、成本、风险和收益。当一个决策有多个清晰的选项时,团队可以共同讨论和权衡,而不是只盯着一个保守方案不放。

实践建议:

  • 建立决策评审机制: 明确谁是最终的决策者(DRI - Directly Responsible Individual),并设立评审环节。评审会上,每个人都应基于数据和事实来论证自己的观点。
  • 鼓励小步快跑和 PoC: 对于有争议的技术方案,可以先用小规模的 PoC(概念验证)项目来验证其可行性。用实际结果说话,而不是让大家停留在理论争辩。

3.4.2 Anti-Pattern2: Groundhog Day

团队成员在每次会议上都重复同样的讨论,无法达成共识。由于没有明确的决策记录或决策依据,导致下一次讨论又回到原点。

应对方案:

  • Subject(主题): 在每次讨论前,都必须有一个明确的、聚焦的 Subject。这次会议要讨论什么?目标是什么?是决定数据库选型?还是讨论消息队列的方案?有了明确的主题,才能避免讨论跑偏。
  • Decision(决策): 讨论结束后,必须得出一个明确的 Decision。决策是什么?为什么做出这个决策?这个决策有哪些局限性?明确记录下来,并让所有人都知晓。

实践建议:

  • 会议纪要: 每次关键的架构讨论后,都必须有正式的会议纪要。纪要中要包含:讨论主题、所有备选方案、最终决策、决策依据以及未被采纳方案的理由
  • 设立时间限制: 在讨论时,可以为每个议题设定一个时间限制。如果超过时间仍无法达成一致,可以先暂停,让大家会后去搜集更多数据,再进行下一轮讨论。

3.4.3 Anti-Pattern3: Email-Driven Architecture

重要的架构决策都散落在团队成员的邮件、聊天记录或者 Wiki 的各个角落,没有一个集中的、可检索的知识库。当新成员加入或需要回顾历史决策时,很难找到完整的信息。

应对方案:

  • Subject(主题) 和 Decision(决策): 这两个元素是解决这个问题的核心。架构决策不应该只是一个口头或邮件的结论,而是一个完整的 ADR(Architecture Decision Record)。ADR 本身就是一个以主题和决策为核心的文档。

实践建议:

  • 建立 ADR 制度: 强烈建议引入 ADR 机制。
  • 使用统一的知识管理平台: 将所有 ADR 存放在一个统一的、可检索的知识管理平台(如飞书文档, Wiki 或 Git)。这样,团队成员可以轻松地查阅历史决策,新成员也能快速理解系统的演进过程。

3.4.4 架构决策记录 ADR

架构决策记录 ADR

TITLE(标题)

  • 解释: 标题应该简短、清晰地描述这个 ADR 的核心决策是什么。
  • 示例: "使用 RabbitMQ 替代 Kafka 作为消息队列" 或 "将数据库从 MySQL 切换到 PostgreSQL"。
  • 作用: 让读者一眼就能明白这份文档的主题。一个好的标题本身就包含了 Subject

STATUS(状态)

  • 解释: ADR 的生命周期状态。通常包括以下几种:
    • Proposed(提案中): 决策还在讨论阶段,尚未被团队接受。
    • Accepted(已接受): 决策已经通过,可以开始实施。
    • Superseded(已废弃): 这个决策已经被新的 ADR 替代。这对于追踪架构演变历史非常重要。这样要链接到新的 ADR,方便追溯!
  • 作用: 帮助团队成员了解该决策的当前状态,避免对过时或仍在讨论中的方案产生误解。

CONTEXT(背景)

  • 解释: 为什么要做出这个决策?它试图解决什么问题?这里应该详细描述问题的来龙去脉、约束条件以及技术或业务驱动因素。
  • 示例: "我们现有的系统在处理高并发订单时,MySQL 数据库的写入性能出现了瓶颈,导致订单处理延迟。"
  • 作用: 提供决策的 Fact(事实),让读者理解决策背后的原因,而不是孤立地看待决策本身。

DECISION(决策)

  • 解释: 明确描述最终的决策是什么,并给出相应的理由。这个部分是整个 ADR 的核心。
  • 示例: "我们决定将订单处理服务从同步调用改为异步消息队列。备选方案是采用 Kafka,但我们最终选择了 RabbitMQ,原因是 RabbitMQ 具有更完善的路由机制和更稳定的交付保障,更适合我们对消息可靠性的高要求。"
  • 作用: 记录决策的 DecisionOptions。它清晰地表明我们做了什么选择,以及为什么没有选择其他方案。

CONSEQUENCES(影响)

  • 解释: 这个决策会带来什么后果?包括积极的和消极的。
  • 示例:
    • 积极影响: "订单处理性能将得到显著提升,系统的可扩展性增强。"
    • 消极影响: "引入 RabbitMQ 会增加运维复杂性,团队需要学习新的技术栈。需要额外投入人力进行开发和部署。"
  • 作用: 帮助团队全面评估决策的利弊,提前预见潜在的风险和挑战。这与我们之前讨论的风险评估中的「风险的影响面」有异曲同工之妙。

COMPLIANCE(遵循)

  • 解释: 如何确保团队会遵循这个决策?这个部分更多是关于实践和治理。
  • 示例: "新开发的订单服务必须通过 RabbitMQ 进行异步通信。代码评审时,需要检查是否遵守此规范。运维团队需要负责 RabbitMQ 集群的部署和监控。"
  • 作用: 将抽象的决策转化为具体的行动和规范,确保决策能够真正落地。

NOTES(备注)

  • 解释: 用于记录一些额外的元数据,例如:文档的作者、创建日期、链接到相关的 Jira 工单或会议记录等。
  • 作用: 便于管理和追溯文档。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
**ADR #001 - 使用 RabbitMQ 替代 Kafka 作为消息队列**
---
**TITLE(标题)**

将消息队列从 Kafka 切换至 RabbitMQ

**STATUS(状态)**

Accepted(已接受)

**CONTEXT(背景)**

我们现有的订单服务在业务高峰期时,订单创建和扣减库存的同步处理流程出现了严重的性能瓶颈。MySQL 数据库的写入操作成为单点瓶颈,导致订单处理延迟增加,甚至出现超时。为了解决这一问题,我们决定引入消息队列,将订单创建的后续流程(如库存扣减、积分发放)改为异步处理。

在技术选型阶段,团队提出了两个主要的备选方案:Kafka 和 RabbitMQ。我们希望找到一个能满足以下需求的消息队列:

1. **高可靠性:** 消息不能丢失,即使在消费者故障或重启时。
2. **消息时效性:** 消息需要被及时处理,不接受长时间的延迟。
3. **灵活的路由:** 能够根据不同的业务场景,将消息发送到不同的消费者。
4. **易于运维:** 团队需要能快速上手,运维成本不能过高。

**DECISION(决策)**

我们决定采用 **RabbitMQ** 作为新的消息队列,用于实现订单处理流程的异步化。

**核心理由:**

* **消息路由的灵活性:** RabbitMQ 提供了多种 exchange 类型(如 direct, fanout, topic),可以实现非常灵活的消息路由。这使得我们可以轻松地根据不同的订单类型或业务事件(例如,秒杀订单、普通订单)将消息发送到不同的消费者队列,满足未来的业务扩展需求。
* **消息的可靠性:** RabbitMQ 提供了成熟的持久化机制(Durable Queues)和消息确认机制(Publisher Confirms),能确保即使在 RabbitMQ 本身或消费者故障时,消息也不会丢失。这对订单处理这种核心业务至关重要。
* **团队学习曲线:** 团队成员在内部技术分享中对 RabbitMQ 的概念(exchange, queue, binding)有了一定的了解,学习成本相对可控。

**备选方案的局限性:**

* **Kafka:** Kafka 的核心设计思想是基于日志和分区,其路由能力相对较弱,主要通过 topic 和分区来实现消息分发。虽然可以通过消费者组来实现负载均衡,但在某些复杂路由场景下,需要额外的开发工作来适配。同时,Kafka 在保证单条消息的精确可靠投递方面,实现起来比 RabbitMQ 复杂一些,而这正是我们当前业务最关注的点。

**CONSEQUENCES(影响)**

* **积极影响:**
* 显著提升订单处理的并发能力和吞吐量,缓解数据库写入瓶颈。
* 提升系统的可扩展性,未来可以方便地增加更多异步消费者服务。
* 系统的响应时间将大大缩短,提升用户体验。

* **消极影响:**
* 引入 RabbitMQ 会增加系统的运维复杂性,需要额外的监控和维护工作。
* 团队需要投入时间学习和掌握 RabbitMQ 的相关知识,尤其是如何处理消费者故障、消息死信等问题。
* 系统架构复杂度增加,需要重新设计和实现订单服务与消息队列的集成部分。

**COMPLIANCE(遵循)**

* 所有与订单相关的异步化处理流程,必须通过 RabbitMQ 进行通信。
* 新的服务代码必须严格遵循消息持久化和确认机制,以确保消息不丢失。
* 运维团队负责 RabbitMQ 集群的部署、监控和维护,并确保其高可用性。
* 在代码评审时,需要确保新引入的异步化服务遵循此 ADR 的设计规范。

**NOTES(备注)**

* **作者:** Gemini AI
* **创建日期:** 2025-07-26
* **关联工单:** PROJECT-1234 - 订单服务高并发性能优化
* **相关会议记录:** 架构评审会议 [2025-07-25]

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

实施计划:是否需要技术原型 (PoC) 来验证关键难点?如何进行任务拆解和里程碑规划?

构建适用度函数:针对第二步定义的关键架构特性,设计具体的检验尺。

知识沉淀:准备好核心的架构图、设计文档等。

4.1 实施计划

技术原型 (PoC) 来验证关键难点:架构师应频繁进行概念验证 (PoC),以验证架构决策的可行性,并深入了解实施细节。PoC有助于比较不同解决方案,并评估性能、可伸缩性等架构特性。建议架构师在进行PoC时编写生产质量的代码,这是架构师可以用于保持编码手感的有效手段,同时一次性的 PoC 代码往往会成为团队的参考架构。

任务拆解和里程碑规划:组件识别和架构设计是一个迭代过程,通过反馈不断优化。敏捷方法论支持迭代开发和快速反馈,有助于架构师在实践中调整决策。架构师还需要平衡架构工作和实际编码,通过委派核心路径代码,避免成为团队瓶颈。

4.2 适应度函数

适应度函数是架构治理的核心工具。它是一种客观的函数,用于衡量代码复杂度和架构特性,并自动化验证开发团队是否遵循了架构决策和设计原则。适应度函数应集成到 CI/CD 流程中,在代码集成时自动检查合规性,从而避免问题积累。

  • 检测循环依赖:可编写适应度函数来检测并防止组件之间的循环依赖,因为这会损害模块化(例如,使用 JDepend 工具)。这有助于维护架构中“重要但不紧急”的实践。
  • 分层架构合规性:利用ArchUnit(Java)或NetArchTest(.NET)等工具,可以确保分层架构中各层之间的访问限制被遵守。例如,限制表现层不能直接访问数据库,而必须通过业务层和持久层。
  • 验证距主序列距离:通过适应度函数验证代码抽象性与不稳定性之间的平衡。
  • 自动化编码标准合规性:例如,检查特定类是否包含必需的注解。

4.3 知识沉淀

  • ADR:将每一次关键决策及其动机、权衡、后果记录下来,形成可检索的决策日志。
  • C4 架构图:在每个里程碑输出更新后的系统上下文、容器、组件图,配合 ADR 链接。

4.4 管理松紧度

架构师需要根据团队实际情况采用恰到好处的管理松紧度,才能发挥团队的最大潜力。

采取哪种管理松紧度,可以从几个方面进行考量:

  • team familiarity:团队内部的熟悉程度,越不熟悉,越需要更多投入。
  • team size:团队大小,团队越大, 越需要投入。
  • overall experience:团队经验,新人越多,越需要投入。
  • project complexity:项目越复杂,越需要投入。
  • project duration:项目周期,周期越长,越需要投入。

按照这 5 个方面,极限 tight 是 20 分,极限 loose 是 -20 分,进行综合评价,看看自己是应该扮演什么角色。

管理松紧度计算表盘

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

持续交付:作为将设计快速、可靠地部署到生产环境的手段。

系统监控:观测系统的健康状况(CPU、内存、延迟、错误率等)。

业务指标验证:验证是否达成了第一步定义的商业目标?例如,新架构上线后,用户转化率是否真的提升了?

5.1 持续交付与部署自动化

持续交付 (CI/CD) 是架构落地的关键环节。FOSA 强调,现代软件架构的成功离不开 DevOps 革命和对操作关注点的自动化。持续交付不仅仅是技术实践,更是组织文化的体现。

核心要素:

  • 自动化构建与测试:每次代码提交都触发自动化的构建、单元测试、集成测试流程,确保代码质量。

  • 环境一致性:通过容器化技术(如 Docker)和基础设施即代码(IaC)确保开发、测试、生产环境的一致性。

  • 渐进式部署:采用蓝绿部署、金丝雀发布等策略,降低部署风险,实现零停机时间。

  • 快速回滚机制:当新版本出现问题时,能够快速回滚到上一个稳定版本。

架构师职责:

  • 设计适合团队规模的 CI/CD 流水线

  • 确保架构决策能够通过自动化流程得到验证

  • 平衡部署频率与系统稳定性

5.2 系统监控与可观测性

可观测性 (Observability) 是现代分布式系统的生命线。FOSA 指出,在分布式架构中,一个请求可能会流经数十个甚至上百个服务,要诊断一个问题,需要建立复杂的可观测性体系。

三大支柱:

  1. 指标 (Metrics):量化系统性能的关键指标

    • RED 方法:Rate(请求率)、Error(错误率)、Duration(延迟)

    • USE 方法:Utilization(利用率)、Saturation(饱和度)、Errors(错误)

    • 业务指标:用户转化率、订单成功率、收入增长率

  2. 日志 (Logs):记录系统运行时的详细信息

    • 结构化日志:使用 JSON 格式,便于机器解析

    • 日志聚合:集中收集、存储和分析日志

    • 日志级别:根据重要性设置不同的日志级别

  3. 追踪 (Tracing):追踪请求在分布式系统中的完整路径

    • 分布式追踪:为每个请求分配唯一 ID,追踪其在整个调用链中的路径

    • 链路追踪:记录服务间的调用关系和耗时

    • 性能分析:识别系统瓶颈和性能热点

监控策略:

  • 分层监控:从基础设施层到应用层,建立完整的监控体系

  • 告警策略:设置合理的告警阈值,避免告警疲劳

  • 可视化仪表板:为不同角色提供定制化的监控视图

5.3 业务指标验证与闭环反馈

业务指标验证是架构设计的闭环关键。FOSA 强调,技术架构的最终目标是服务于业务价值,因此必须验证是否达成了第一步定义的商业目标。

验证流程:

  1. 建立基线:在架构变更前,记录关键业务指标的当前状态
  2. 设定目标:基于第一步的商业目标,设定具体的量化指标
  3. 持续监控:在架构部署后,持续跟踪业务指标的变化
  4. 效果评估:定期评估架构变更对业务指标的实际影响

常见业务指标:

  • 用户相关:日活跃用户数、用户留存率、用户满意度

  • 业务相关:订单转化率、客单价、复购率

  • 技术相关:系统可用性、响应时间、错误率

A/B 测试策略:

  • 在架构变更时,可以考虑 A/B 测试来验证效果

  • 对比新旧架构在相同条件下的业务表现

  • 基于数据做出是否全面推广的决策

5.4 性能监控与容量规划

性能监控是架构健康度的重要指标。FOSA 指出,架构师需要持续监控系统的性能表现,并基于趋势进行容量规划。

关键性能指标:

  • 响应时间:P50、P95、P99 延迟

  • 吞吐量:每秒处理的请求数

  • 资源利用率:CPU、内存、磁盘、网络使用率

  • 错误率:4xx、5xx 错误的比例

容量规划方法:

  • 趋势分析:基于历史数据预测未来需求

  • 压力测试:通过模拟高负载验证系统极限

  • 弹性规划:设计自动扩缩容机制应对流量波动

6. 复盘、沉淀与演进

问题记录与根因分析:发生了什么?为什么会发生?

流程与原则改进:如何优化我们的设计流程、技术原则,避免未来再犯?

人员与组织成长:团队通过这次项目学到了什么?需要组织哪些培训?

6.1 问题记录与根因分析

根因分析 (Root Cause Analysis, RCA) 是架构演进的基础。FOSA 强调,架构师需要建立系统性的问题记录和分析机制,避免同样的问题重复发生。

分析框架:

  1. 5W1H 分析法

    • What:发生了什么问题?

    • When:什么时候发生的?

    • Where:在哪个组件/服务中发生的?

    • Who:谁发现了这个问题?

    • Why:为什么会发生?

    • How:如何避免再次发生?

  2. 鱼骨图分析

    • 人员因素:技能不足、沟通不畅

    • 流程因素:流程缺陷、决策不当

    • 技术因素:架构设计问题、技术选型错误

    • 环境因素:基础设施问题、外部依赖故障

  3. 时间线分析

    • 按时间顺序记录事件发展过程

    • 识别关键决策点和转折点

    • 分析因果关系链

问题分类:

  • 架构设计问题:组件划分不当、接口设计不合理

  • 技术选型问题:技术栈不匹配、性能瓶颈

  • 流程管理问题:决策流程不清晰、沟通机制缺失

  • 人员技能问题:团队技能不足、知识传递不畅

6.2 流程与原则改进

持续改进是架构师的核心职责。FOSA 指出,架构师需要基于实践经验,不断优化设计流程和技术原则。

流程改进方法:

  1. 回顾会议 (Retrospective)

    • 定期组织团队回顾会议

    • 识别流程中的痛点和改进机会

    • 制定具体的改进行动计划

  2. 架构评审机制

    • 建立正式的架构评审流程

    • 邀请相关方参与评审

    • 记录评审决策和后续行动

  3. 决策记录 (ADR) 更新

    • 定期回顾和更新 ADR

    • 记录决策的后续影响和教训

    • 为未来类似决策提供参考

原则演进:

  • 技术原则:基于实践经验更新技术选型原则

  • 设计原则:优化组件划分和接口设计原则

  • 流程原则:改进决策流程和沟通机制

  • 质量原则:更新代码质量和测试策略

6.3 持续学习与团队领导

组织学习是架构成功的关键。FOSA 强调,架构师不仅要关注技术架构,更要关注团队和组织的成长。优秀的架构师通过培养团队能力和建立学习型组织,实现技术债务的持续偿还和架构能力的持续提升。

6.3.1 20 分钟法则

架构师需要持续学习以保持技术广度。FOSA 指出,技术发展日新月异,架构师必须建立系统化的学习机制,避免技术视野的固化。

20分钟法则:建议每天至少投入20分钟学习新知识或深入特定主题,以系统化地拓展技术广度。这种持续的小剂量学习比偶尔的集中学习更有效,能够保持技术敏锐度。

学习策略:

  • 技术深度与广度平衡:在保持一个技术领域的深度基础上,系统性地拓展技术广度

  • 问题驱动学习:将实际工作中遇到的问题作为学习的起点

  • 理论与实践结合:通过概念验证(PoC)验证新技术的适用性

  • 跨领域学习:不仅学习技术,还要了解业务、管理、心理学等相关领域

6.3.2 个人技术雷达

建立"个人雷达"可以帮助架构师系统化地评估和追踪新兴技术和实践,类似于 ThoughtWorks 的技术雷达。

雷达分类:

  • 采用 (Adopt):经过验证的技术,可以安全地在生产环境中使用

  • 试用 (Trial):有前景的技术,可以在非关键项目中尝试

  • 评估 (Assess):值得关注的技术,需要进一步研究和评估

  • 保持 (Hold):暂时不推荐使用的技术,但保持关注

雷达维护:

  • 定期更新:每季度更新一次技术雷达

  • 团队共享:与团队分享技术雷达,促进集体学习

  • 决策参考:将技术雷达作为技术选型的重要参考

6.3.3 知识分享

架构师应通过以身作则而非仅仅凭借头衔来领导团队。他们可以通过主持"午餐分享会" (brown-bag lunches) 来分享技术知识和经验,从而提升在团队中的领导力和影响力。

6.3.4 团队健康监控与预警

当出现以下 3 个问题时,意味着团队已经开始进入不健康状态了,作为架构师,需要及时发现和解决团队协作中的问题。

Process Loss(过程损失):随着人数的增加,团队效率却在降低。

  • 表现:团队规模扩大后,沟通成本激增,决策效率下降

  • 原因:信息传递链条过长,协调成本超过协作收益

  • 解决方案:

    • 建立清晰的信息传递机制

    • 采用敏捷方法,保持小团队结构

    • 定期评估团队规模与效率的关系

Pluralistic Ignorance(多元无知):当团队成员因为觉得自己没掌握某些信息的时候,对提出的方案不好提出拒绝,而只能在表面进行同意。

  • 表现:会议上大家都点头同意,但会后执行时遇到各种问题

  • 原因:团队成员缺乏安全感,不敢提出质疑

  • 解决方案:

    • 营造安全的讨论环境,鼓励质疑和提问

    • 建立"魔鬼代言人"机制,专门负责提出反对意见

    • 定期进行匿名反馈收集

Diffusion of Responsibility(责任扩散):职责混乱,大家不知道谁应该为哪些东西负责任。

  • 表现:任务推诿,问题无人负责,决策无人执行

  • 原因:角色定义不清晰,责任边界模糊

  • 解决方案:

    • 建立明确的 RACI 矩阵(Responsible, Accountable, Consulted, Informed)

    • 定期回顾和更新团队职责分工

    • 建立问责机制,确保每个决策都有明确的责任人

总结

架构设计一个系统性的六步工程过程,从商业理解到组织成长形成闭环。它强调"为什么"比"怎么做"更重要,要求架构师在理解利益相关方诉求和用户痛点的基础上,将模糊需求转化为可度量的技术目标,通过多方案权衡分析选择"最不差"而非"最佳"的架构方案,并建立持续交付、监控验证和复盘演进的机制,最终实现技术债务的持续偿还和团队能力的持续提升。整个方法论的核心是权衡取舍的艺术,以及架构师在技术决策中始终提供技术和业务双重理由的能力。