4软件架构实践(第4版)

架构战术

实现质量属性的手段

性能战术可用性战术安全战术可修改性战术

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第4章 架构战术 (Architectural Tactics)

一、导读

1.1 本章学习目标

在本章结束时,你将能够:

  • 理解架构战术的本质,区分战术与模式、风格的差异
  • 掌握性能战术,包括资源需求、资源管理和资源仲裁
  • 掌握可用性战术,包括错误检测、错误恢复和错误预防
  • 掌握安全性战术,包括抵抗攻击、检测攻击和从攻击中恢复
  • 掌握可修改性战术,包括局部化变更、防止连锁反应和延迟绑定时间
  • 应用战术组合,根据质量属性场景选择合适的战术组合
  • 分析战术权衡,理解战术之间的相互影响
  • 建立战术思维,在设计中主动运用战术解决质量问题

1.2 为什么本章如此重要

如果说质量属性定义了系统"应该怎么样",那么架构战术就是实现这些质量属性的具体手段。战术是架构设计的工具箱,它提供了经过验证的、可复用的解决方案。

在实际工作中,我们经常面临这样的困境:知道系统需要高性能,但不知道如何实现;知道系统需要高可用,但不清楚该采取什么措施。架构战术正是为了解决这个问题而存在的。

战术的重要性体现在以下几个方面:

首先,战术提供了具体的行动指南。与抽象的质量属性不同,战术是具体的、可操作的设计决策。例如,"使用缓存"是一个战术,"引入冗余"是一个战术,"异步处理"是一个战术。这些战术可以直接指导代码实现。

其次,战术是经过验证的最佳实践。每个战术都来自于实际项目的经验总结,它们已经被证明在特定情境下能够有效改善特定的质量属性。使用战术可以避免重复发明轮子,减少试错成本。

第三,战术是架构模式的构建块。复杂的架构模式(如微服务、事件驱动)是由多个战术组合而成的。理解战术有助于深入理解模式,也有助于根据具体需求定制模式。

第四,战术帮助进行架构权衡。不同的战术可能对同一质量属性产生相反的影响,或者对不同质量属性产生冲突的影响。理解战术的作用机制,有助于做出明智的权衡决策。

最后,战术促进了架构知识的传承。通过学习和应用战术,初级开发者可以快速掌握架构设计的核心技巧,资深架构师可以系统地传授经验。

本章将系统介绍各类质量属性对应的架构战术,包括它们的原理、适用场景、实施方法和潜在代价。掌握这些战术,将使你的架构设计更加精准和有效。

1.3 核心问题引导

在阅读本章时,请思考以下问题:

架构战术与设计模式有什么区别?它们之间是什么关系?

为什么同一个质量属性可以有多种战术?如何选择最合适的战术?

性能战术中的"资源管理"和"资源仲裁"有什么区别?

可用性战术如何与错误处理的三个阶段的理论对应?

安全性战术中的"纵深防御"具体包含哪些层次?

可修改性战术中的"延迟绑定时间"有哪些实现方式?

当多个质量属性需要同时优化时,如何避免战术之间的冲突?

战术的选择是否依赖于技术栈?还是说战术是技术无关的?


二、核心概念详解

4.1 架构战术的定义

严格定义:

架构战术(Architectural Tactics)是实现特定质量属性的基本设计决策。它是介于高层架构风格和底层代码实现之间的中间层抽象,提供了具体的、可操作的手段来改善系统的质量属性。

通俗地说,如果架构风格是"战略"(如选择微服务还是单体),那么架构战术就是"战术"(如使用缓存提高性能、引入冗余提高可用性)。战术是具体的行动,可以直接指导和影响代码实现。

战术 vs 模式 vs 风格

这三个概念经常被混淆,它们之间有明确的层次关系:

概念粒度抽象程度示例
架构风格最高最抽象微服务、分层、事件驱动
架构模式中等中等MVC、发布-订阅、断路器
架构战术最低最具体缓存、冗余、异步处理
  • 架构风格:描述系统的整体结构特征,如"这是一个微服务架构"
  • 架构模式:描述解决特定问题的通用方案,如"使用发布-订阅模式解耦组件"
  • 架构战术:描述实现特定质量属性的具体手段,如"使用 Redis 缓存热点数据以提高性能"

关系:

  • 多个战术可以组合成一个模式
  • 多个模式可以构成一个风格
  • 战术是模式和风格的构建块

战术的特征

一个好的架构战术应具备以下特征:

针对性:明确针对某个或某几个质量属性

可操作性:提供具体的实施指导

可验证性:效果可以被测量和验证

可复用性:可以在不同项目中应用

有代价:任何战术都有取舍,不是免费的

战术的分类

根据目标质量属性,战术可以分为:

  • 性能战术:改善响应时间、吞吐量、资源利用率
  • 可用性战术:提高系统正常运行时间比例
  • 安全性战术:保护系统免受攻击
  • 可修改性战术:降低变更成本和风险
  • 可测试性战术:提高测试效率和质量
  • 其他战术:针对其他质量属性

4.2 性能战术

性能战术的目标是改善系统对请求的响应能力。根据 SEI(软件工程研究所)的分类,性能战术分为三类:资源需求、资源管理和资源仲裁。

4.2.1 资源需求战术

这类战术旨在减少系统处理请求所需的资源。

1. 减少计算开销

  • 算法优化:选择更高效的算法,如用哈希表代替线性搜索
  • 数据结构优化:选择更适合的数据结构,如用数组代替链表
  • 编译优化:使用编译器优化选项,生成更高效的机器码

直觉解释:通俗地说,就是用更聪明的方法做同样的事,减少不必要的计算。

2. 减少数据量

  • 数据压缩:传输前压缩数据,减少网络带宽占用
  • 数据精简:只传输必要的数据字段,避免过度获取
  • 二进制格式:使用 Protobuf、MessagePack 等高效序列化格式

直觉解释:少传数据自然就快。就像寄快递,东西越少越轻,送达越快。

3. 减少访问延迟

  • 预取(Prefetching):预测用户可能需要的数据,提前加载
  • 预计算(Precomputation):提前计算可能的结果,避免实时计算
  • 批处理(Batching):将多个小请求合并为一个大请求,减少往返次数

直觉解释:与其等用户要了再去拿,不如提前准备好。就像餐厅提前备好常用食材。

4.2.2 资源管理战术

这类战术旨在更有效地管理系统已有的资源。

1. 缓存(Caching)

  • 原理:将频繁访问的数据存储在快速存储中
  • 层级:浏览器缓存 → CDN → 应用缓存 → 数据库缓存
  • 策略:LRU(最近最少使用)、LFU(最不经常使用)、TTL(过期时间)

直觉解释:把常用的东西放在手边,不用每次都去仓库取。

注意事项:

  • 缓存一致性:缓存数据与源数据可能不一致
  • 缓存失效:何时更新或删除缓存
  • 缓存穿透:大量请求直接打到后端

2. 资源池(Resource Pooling)

  • 原理:预分配和复用昂贵资源
  • 示例:数据库连接池、线程池、对象池
  • 优势:避免频繁创建和销毁的开销

直觉解释:工具用完放回去,下次直接用,不用每次都去买新的。

3. 负载均衡(Load Balancing)

  • 原理:将请求分散到多个服务器
  • 策略:轮询、加权轮询、最少连接、一致性哈希
  • 优势:避免单点过载,提高整体吞吐量

直觉解释:不要让一个人干所有活,分给多个人一起做。

4.2.3 资源仲裁战术

这类战术旨在当多个组件竞争有限资源时,决定谁先获得资源。

1. 优先级调度(Priority Scheduling)

  • 原理:给请求分配优先级,高优先级先处理
  • 应用:实时系统、VIP 用户、关键业务
  • 风险:低优先级请求可能长期得不到服务(饥饿)

直觉解释:急诊病人优先看,普通病人排队等。

2. 队列管理(Queue Management)

  • 原理:控制请求进入系统的速率
  • 策略:FIFO(先进先出)、LIFO、优先级队列
  • 限流:当队列满时拒绝新请求

直觉解释:餐厅满了就取号排队,不要让更多人挤进来。

3. 并发控制(Concurrency Control)

  • 原理:管理多个线程/进程对共享资源的访问
  • 机制:锁、信号量、读写锁、乐观锁
  • 目标:防止竞态条件,保证数据一致性

直觉解释:多人共用一个卫生间,需要排队或使用规则。

4.3 可用性战术

可用性战术的目标是提高系统在需要时能够正常提供服务的能力。根据错误处理的生命周期,可用性战术分为三类:错误检测、错误恢复和错误预防。

4.3.1 错误检测战术

在错误发生或被触发时及时发现。

1. 心跳(Heartbeat/Ping-Echo)

  • 原理:定期发送探测消息,确认组件存活
  • 频率:根据系统要求设定,通常几秒到几十秒
  • 超时:超过阈值未收到响应则判定为故障

直觉解释:每隔一会儿问一句"你还在吗?",没回应就是出问题了。

2. 异常检测(Exception Detection)

  • 原理:捕获运行时异常和错误
  • 层级:应用层异常、系统层异常、硬件层异常
  • 处理:记录日志、发送告警、触发恢复

直觉解释:程序出错时抓住错误,不要让它悄悄溜走。

3. 监控和告警(Monitoring and Alerting)

  • 指标:CPU、内存、磁盘、网络、应用指标
  • 阈值:设定正常范围,超出则告警
  • 渠道:邮件、短信、电话、即时通讯

直觉解释:安装监控摄像头,发现异常立即报警。

4. 健康检查(Health Check)

  • 原理:暴露端点供外部检查系统状态
  • 内容:依赖服务连通性、资源使用情况、业务指标
  • 用途:负载均衡器据此决定是否转发流量

直觉解释:体检报告,告诉别人你是否健康。

4.3.2 错误恢复战术

检测到错误后,采取措施恢复服务。

1. 冗余(Redundancy)

  • 原理:关键组件有多个副本
  • 类型:

- 主动-主动(Active-Active):所有副本同时服务

- 主动-被动(Active-Passive):主副本服务,备副本待命

  • 切换:故障时自动切换到备用副本

直觉解释:重要的东西多备几份,一份坏了还有别的。

2. 重试(Retry)

  • 原理:临时故障时重新执行操作
  • 策略:立即重试、延迟重试、指数退避
  • 限制:最大重试次数,避免无限重试

直觉解释:电话打不通就多打几次,可能是暂时占线。

3. 降级(Degradation)

  • 原理:非核心功能不可用时,核心功能仍能工作
  • 类型:

- 功能降级:关闭非核心功能

- 数据降级:使用旧数据或默认值

- 体验降级:简化界面或流程

直觉解释:飞机引擎坏了一个,还能靠另一个安全降落。

4. 回滚(Rollback)

  • 原理:新版本出现问题时恢复到旧版本
  • 前提:保留历史版本,数据向后兼容
  • 自动化:检测到失败自动触发回滚

直觉解释:新衣服不合适,退回去换旧的穿。

5. 状态重置(State Reset)

  • 原理:将组件恢复到已知的良好状态
  • 方式:重启、清理缓存、重建连接
  • 适用:状态污染导致的故障

直觉解释:电脑卡了就重启,大部分问题能解决。

4.3.3 错误预防战术

在错误发生之前采取措施防止其发生。

1. 从服务中移除(Removal from Service)

  • 原理:定期将组件下线进行检查和维护
  • 目的:在故障发生前发现潜在问题
  • 方式:滚动升级、蓝绿部署

直觉解释:汽车定期保养,防止半路抛锚。

2. 事务(Transaction)

  • 原理:确保操作的原子性和一致性
  • 特性:ACID(原子性、一致性、隔离性、持久性)
  • 适用:金融交易、数据修改

直觉解释:要么全做完,要么全不做,不要做一半。

3. 进程监视器(Process Monitor)

  • 原理:独立进程监视主进程,发现异常时重启
  • 示例:Supervisor、systemd、Kubernetes
  • 优势:即使主进程崩溃也能恢复

直觉解释:找个保镖盯着你,你晕倒了把你叫醒。

4.4 安全性战术

安全性战术的目标是保护系统免受恶意攻击。根据攻击生命周期,安全性战术分为三类:抵抗攻击、检测攻击和从攻击中恢复。

4.4.1 抵抗攻击战术

防止攻击成功。

1. 身份认证(Authentication)

  • 原理:验证用户身份
  • 方式:密码、双因素认证、生物识别、OAuth
  • 强化:密码策略、账户锁定、验证码

直觉解释:进门先出示身份证,证明你是谁。

2. 授权(Authorization)

  • 原理:确定用户有权执行的操作
  • 模型:RBAC(基于角色)、ABAC(基于属性)、ACL(访问控制列表)
  • 原则:最小权限,只授予必要的权限

直觉解释:进了门也不能随便乱跑,只能去你有权限的地方。

3. 加密(Encryption)

  • 原理:将数据转换为不可读的形式
  • 类型:

- 传输加密:TLS/SSL,保护数据传输

- 存储加密:磁盘加密、数据库加密,保护静态数据

  • 算法:对称加密(AES)、非对称加密(RSA)

直觉解释:把信写成密码,只有收件人能看懂。

4. 输入验证(Input Validation)

  • 原理:检查和过滤用户输入
  • 防护:SQL 注入、XSS、命令注入
  • 原则:白名单优于黑名单

直觉解释:门卫检查每个人带的东西,危险品不让进。

5. 最小暴露(Least Exposure)

  • 原理:减少系统的攻击面
  • 措施:关闭不必要的端口和服务、隐藏版本信息、限制 API 暴露
  • 原则:默认拒绝,除非明确允许

直觉解释:门窗越少,小偷越难进来。

4.4.2 检测攻击战术

发现正在进行的攻击。

1. 入侵检测(Intrusion Detection)

  • 原理:监控系统活动,识别可疑行为
  • 类型:

- 基于签名:匹配已知攻击模式

- 基于异常:检测偏离正常行为的模式

  • 工具:IDS、WAF、SIEM

直觉解释:安装防盗报警器,有人闯入就响。

2. 审计跟踪(Audit Trail)

  • 原理:记录所有关键操作
  • 内容:谁、在什么时候、做了什么、结果如何
  • 用途:事后追溯、合规审计

直觉解释:装监控摄像头,出了事可以查录像。

3. 蜜罐(Honeypot)

  • 原理:设置诱饵系统吸引攻击者
  • 目的:研究攻击手法、早期预警、分散注意力
  • 注意:蜜罐应与生产系统隔离

直觉解释:放个假保险箱,小偷一碰就知道有人行窃。

4.4.3 从攻击中恢复战术

攻击发生后恢复系统和数据。

1. 备份和恢复(Backup and Recovery)

  • 原理:定期备份数据,故障时恢复
  • 策略:全量备份、增量备份、差异备份
  • 测试:定期演练恢复流程

直觉解释:重要文件多 copy 几份,丢了一份还有别的。

2. 密钥管理(Key Management)

  • 原理:安全地生成、存储、轮换加密密钥
  • 措施:硬件安全模块(HSM)、密钥轮换、密钥分割
  • 恢复:密钥泄露时能够快速更换

直觉解释:保险箱钥匙要妥善保管,丢了能配新的。

3. 账户恢复(Account Recovery)

  • 原理:帮助用户在账户受损时恢复访问
  • 措施:密码重置、多因素恢复、人工审核
  • 安全:防止恢复流程被滥用

直觉解释:钥匙丢了有备用方案开门。

4.5 可修改性战术

可修改性战术的目标是降低系统变更的成本和风险。根据变更的影响范围,可修改性战术分为三类:局部化变更、防止连锁反应和延迟绑定时间。

4.5.1 局部化变更战术

使变更只影响少量模块。

1. 高内聚(High Cohesion)

  • 原理:相关的功能放在同一个模块中
  • 好处:修改一个功能只需改一个模块
  • 衡量:模块内元素的相关程度

直觉解释:把相关的东西放在一起,找起来方便,改起来也方便。

2. 信息隐藏(Information Hiding)

  • 原理:隐藏实现细节,只暴露接口
  • 好处:内部实现变化不影响使用者
  • 机制:私有成员、接口抽象、封装

直觉解释:你只需要知道怎么用电视遥控器,不需要知道里面怎么工作的。

3. 单一职责(Single Responsibility)

  • 原理:每个模块只负责一件事
  • 好处:变更原因单一,影响范围可控
  • 应用:类、模块、服务层面

直觉解释:专人专事,改会计的工作不会影响保安。

4.5.2 防止连锁反应战术

防止一个模块的变更引发其他模块的变更。

1. 松耦合(Loose Coupling)

  • 原理:减少模块之间的依赖
  • 机制:接口抽象、依赖注入、事件驱动
  • 好处:修改一个模块不影响其他模块

直觉解释:乐高积木,换一块不影响其他块。

2. 中介模式(Intermediary Pattern)

  • 原理:通过中间层解耦直接依赖
  • 示例:API 网关、消息队列、服务总线
  • 好处:双方只需与中介交互,互不关心

直觉解释:两个人通过翻译交流,各自语言变化不影响对方。

3. 限制通信路径(Restrict Communication Paths)

  • 原理:规定模块间只能按指定路径通信
  • 机制:分层架构、依赖规则
  • 好处:变更传播路径可控

直觉解释:公司里汇报有固定流程,不能越级。

4.5.3 延迟绑定时间战术

推迟决策时间,增加灵活性。

1. 配置化(Configuration)

  • 原理:将可变参数外部化
  • 方式:配置文件、环境变量、特性开关
  • 好处:修改配置无需重新编译

直觉解释:音量旋钮可以随时调,不用拆开机箱改电路。

2. 插件化(Plugin Architecture)

  • 原理:通过插件扩展功能
  • 机制:接口定义、动态加载、热插拔
  • 好处:新增功能无需修改核心代码

直觉解释:手机装 App 就能增加功能,不用换手机。

3. 多态(Polymorphism)

  • 原理:通过继承和接口实现不同行为
  • 好处:新增实现无需修改调用方
  • 应用:策略模式、工厂模式

直觉解释:插座是标准接口,插什么电器都可以。

4. 运行时注册(Runtime Registration)

  • 原理:组件在运行时动态注册自己
  • 示例:服务发现、事件监听器注册
  • 好处:新增组件无需重启系统

直觉解释:新员工入职登记一下就能开始工作,不用全公司重新组织。

4.6 其他质量属性的战术

4.6.1 可测试性战术

1. 记录/回放(Record/Replay)

  • 记录输入和输出,用于回归测试

2. 内置监控(Built-in Monitor)

  • 系统内置测试接口和诊断功能

3. 特殊测试接口(Special Test Interfaces)

  • 暴露仅供测试使用的接口

4.6.2 可部署性战术

1. 容器化(Containerization)

  • 使用 Docker 等容器技术打包应用

2. 基础设施即代码(Infrastructure as Code)

  • 用代码定义和管理基础设施

3. 特性开关(Feature Toggles)

  • 通过开关控制功能启用

三、程序员的视角

3.1 在架构设计中的应用

场景一:性能优化

当系统出现性能问题时,系统地应用性能战术:

问题:订单查询慢

分析:
1. 资源需求:查询是否做了不必要的计算?→ 优化 SQL,减少 JOIN
2. 资源管理:是否可以缓存结果?→ 引入 Redis 缓存
3. 资源仲裁:是否有其他请求占用了资源?→ 调整优先级,分离读写

实施:
- 第一步:添加数据库索引(资源需求)
- 第二步:引入二级缓存(资源管理)
- 第三步:读写分离(资源仲裁)

场景二:提高可用性

设计高可用系统时,组合使用可用性战术:

目标:99.9% 可用性

战术组合:
1. 错误检测:健康检查 + 心跳
2. 错误恢复:冗余部署 + 自动故障转移
3. 错误预防:滚动升级 + 灰度发布

实施:
- 部署至少 2 个实例(冗余)
- 配置负载均衡器健康检查(检测)
- 设置自动故障转移(恢复)
- 使用蓝绿部署策略(预防)

场景三:安全加固

保护系统安全时,采用纵深防御策略:

战术层次:
1. 边界:WAF + 身份认证(抵抗)
2. 网络:VPC + 安全组(抵抗)
3. 应用:输入验证 + 授权(抵抗)
4. 数据:加密 + 审计(检测 + 恢复)

实施:
- 每层独立防护,一层失效还有其他层
- 定期渗透测试,检验防护效果
- 建立安全事件响应流程

3.2 常见陷阱与误区

陷阱一:战术滥用

表现:不加分析地应用战术,导致系统过度复杂。

例如:

  • 明明 QPS 只有 10,却引入了复杂的分布式缓存
  • 内部工具系统却设计了多活容灾架构

避免方法:

  • 先量化需求,再选择战术
  • 遵循 YAGNI 原则,不要过早优化
  • 评估战术的成本和收益

陷阱二:战术孤立

表现:单独应用某个战术,忽视与其他战术的配合。

例如:

  • 只加了缓存,但没有考虑缓存失效和一致性
  • 只做了冗余,但没有配置故障检测和自动切换

避免方法:

  • 战术应成组应用,形成完整方案
  • 考虑战术的前置条件和后续影响
  • 设计端到端的解决方案

陷阱三:战术冲突

表现:应用的战术之间相互矛盾,抵消效果。

例如:

  • 为了提高性能引入缓存,但为了安全性频繁刷新缓存
  • 为了提高可用性增加冗余,但为了降低成本限制资源

避免方法:

  • 识别战术之间的依赖和冲突
  • 明确优先级,做出权衡
  • 记录决策理由,便于后续调整

陷阱四:忽视战术代价

表现:只看到战术的好处,忽视其带来的复杂性、成本或副作用。

例如:

  • 微服务提高了可修改性,但增加了运维复杂度
  • 加密提高了安全性,但降低了性能

避免方法:

  • 每个战术都有代价,事先评估
  • 与利益相关者沟通代价
  • 寻找平衡点,不过度追求单一质量

3.3 与其他层级的联系

与架构模式的联系

战术是模式的构建块:

战术:缓存 + 异步处理 + 事件通知
  ↓ 组合
模式:CQRS(命令查询责任分离)
  ↓ 组合
风格:微服务架构

理解战术有助于深入理解模式的设计意图。

与设计模式的联系

某些战术可以用设计模式实现:

战术:延迟绑定时间
  ↓ 实现
设计模式:策略模式、工厂模式、观察者模式

战术:防止连锁反应
  ↓ 实现
设计模式:中介者模式、适配器模式、外观模式

与代码实现的联系

战术最终要落实到代码:

战术:缓存
  ↓ 实现
代码:Redis 客户端 + 缓存注解 + 失效策略

战术:重试
  ↓ 实现
代码:Retry 模板 + 指数退避算法 + 熔断器

四、代码实战

4.1 示例一:性能战术的综合应用

下面我们用 Java 展示多种性能战术的组合应用。

java
/**
 * 性能战术综合示例
 * 展示缓存、资源池、批处理和异步处理的应用
 */

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

public class PerformanceTactics {

    // ==================== 战术一:缓存 ====================
    
    /**
     * 带 TTL 和 LRU 淘汰的缓存实现
     */
    static class Cache<K, V> {
        private final int maxSize;
        private final long ttlMs;
        private final Map<K, CacheEntry<V>> map;
        private final Deque<K> accessOrder;
        
        static class CacheEntry<V> {
            final V value;
            final long expiryTime;
            
            CacheEntry(V value, long ttlMs) {
                this.value = value;
                this.expiryTime = System.currentTimeMillis() + ttlMs;
            }
            
            boolean isExpired() {
                return System.currentTimeMillis() > expiryTime;
            }
        }
        
        public Cache(int maxSize, long ttlMs) {
            this.maxSize = maxSize;
            this.ttlMs = ttlMs;
            this.map = new HashMap<>();
            this.accessOrder = new ArrayDeque<>();
        }
        
        public synchronized V get(K key) {
            CacheEntry<V> entry = map.get(key);
            if (entry == null || entry.isExpired()) {
                if (entry != null) {
                    map.remove(key);
                    accessOrder.remove(key);
                }
                return null;
            }
            // 更新访问顺序
            accessOrder.remove(key);
            accessOrder.addFirst(key);
            return entry.value;
        }
        
        public synchronized void put(K key, V value) {
            if (map.containsKey(key)) {
                accessOrder.remove(key);
            } else if (map.size() >= maxSize) {
                // LRU 淘汰
                K oldest = accessOrder.removeLast();
                map.remove(oldest);
            }
            map.put(key, new CacheEntry<>(value, ttlMs));
            accessOrder.addFirst(key);
        }
        
        public int size() {
            return map.size();
        }
    }
    
    // ==================== 战术二:资源池 ====================
    
    /**
     * 通用对象池实现
     */
    static class ObjectPool<T> {
        private final BlockingQueue<T> pool;
        private final Supplier<T> factory;
        private final Consumer<T> resetter;
        
        public ObjectPool(int size, Supplier<T> factory, Consumer<T> resetter) {
            this.factory = factory;
            this.resetter = resetter;
            this.pool = new ArrayBlockingQueue<>(size);
            
            // 预创建对象
            for (int i = 0; i < size; i++) {
                pool.add(factory.get());
            }
        }
        
        public T borrow(long timeout, TimeUnit unit) throws InterruptedException {
            T obj = pool.poll(timeout, unit);
            if (obj == null) {
                throw new RuntimeException("Pool exhausted");
            }
            return obj;
        }
        
        public void returnObject(T obj) {
            resetter.accept(obj);
            pool.offer(obj);
        }
        
        public int available() {
            return pool.size();
        }
    }
    
    // ==================== 战术三:批处理 ====================
    
    /**
     * 批量处理器
     * 累积多个请求后批量处理,减少 IO 次数
     */
    static class BatchProcessor<T, R> {
        private final int batchSize;
        private final long flushIntervalMs;
        private final Function<List<T>, List<R>> batchFunction;
        private final List<T> buffer;
        private final ExecutorService executor;
        private long lastFlushTime;
        
        public BatchProcessor(int batchSize, long flushIntervalMs, 
                             Function<List<T>, List<R>> batchFunction) {
            this.batchSize = batchSize;
            this.flushIntervalMs = flushIntervalMs;
            this.batchFunction = batchFunction;
            this.buffer = Collections.synchronizedList(new ArrayList<>());
            this.executor = Executors.newSingleThreadExecutor();
            this.lastFlushTime = System.currentTimeMillis();
        }
        
        public CompletableFuture<List<R>> submit(T item) {
            buffer.add(item);
            
            CompletableFuture<List<R>> future = new CompletableFuture<>();
            
            // 检查是否需要刷新
            if (buffer.size() >= batchSize || 
                System.currentTimeMillis() - lastFlushTime > flushIntervalMs) {
                flush(future);
            }
            
            return future;
        }
        
        private synchronized void flush(CompletableFuture<List<R>> future) {
            if (buffer.isEmpty()) {
                future.complete(Collections.emptyList());
                return;
            }
            
            List<T> items = new ArrayList<>(buffer);
            buffer.clear();
            lastFlushTime = System.currentTimeMillis();
            
            executor.submit(() -> {
                try {
                    List<R> results = batchFunction.apply(items);
                    future.complete(results);
                } catch (Exception e) {
                    future.completeExceptionally(e);
                }
            });
        }
        
        public void shutdown() {
            executor.shutdown();
        }
    }
    
    // ==================== 战术四:异步处理 ====================
    
    /**
     * 异步任务处理器
     */
    static class AsyncTaskHandler {
        private final ExecutorService executor;
        private final CompletionService<String> completionService;
        
        public AsyncTaskHandler(int threadCount) {
            this.executor = Executors.newFixedThreadPool(threadCount);
            this.completionService = new ExecutorCompletionService<>(executor);
        }
        
        public void submitTask(String taskName, Callable<String> task) {
            completionService.submit(() -> {
                long start = System.currentTimeMillis();
                try {
                    String result = task.call();
                    long elapsed = System.currentTimeMillis() - start;
                    return taskName + " completed in " + elapsed + "ms: " + result;
                } catch (Exception e) {
                    long elapsed = System.currentTimeMillis() - start;
                    return taskName + " failed after " + elapsed + "ms: " + e.getMessage();
                }
            });
        }
        
        public String takeResult() throws InterruptedException {
            return completionService.take().get();
        }
        
        public void shutdown() {
            executor.shutdown();
        }
    }
    
    // ==================== 测试 ====================
    
    public static void main(String[] args) throws Exception {
        System.out.println("===== 性能战术演示 =====\n");
        
        // 测试缓存
        testCache();
        
        // 测试对象池
        testObjectPool();
        
        // 测试批处理
        testBatchProcessing();
        
        // 测试异步处理
        testAsyncProcessing();
    }
    
    private static void testCache() {
        System.out.println("--- 缓存战术 ---");
        Cache<String, String> cache = new Cache<>(3, 5000); // 最多 3 个,TTL 5 秒
        
        cache.put("user:1", "Alice");
        cache.put("user:2", "Bob");
        cache.put("user:3", "Charlie");
        
        System.out.println("Get user:1: " + cache.get("user:1")); // 命中
        System.out.println("Get user:2: " + cache.get("user:2")); // 命中
        
        cache.put("user:4", "David"); // 触发 LRU 淘汰,user:1 被移除
        
        System.out.println("Get user:1: " + cache.get("user:1")); // 未命中(已被淘汰)
        System.out.println("Cache size: " + cache.size());
        System.out.println();
    }
    
    private static void testObjectPool() throws Exception {
        System.out.println("--- 资源池战术 ---");
        
        // 创建数据库连接池(模拟)
        ObjectPool<String> connectionPool = new ObjectPool<>(
                3,
                () -> "Connection-" + ThreadLocalRandom.current().nextInt(1000),
                conn -> {} // 重置逻辑(模拟)
        );
        
        System.out.println("Available connections: " + connectionPool.available());
        
        // 借用连接
        String conn1 = connectionPool.borrow(1, TimeUnit.SECONDS);
        String conn2 = connectionPool.borrow(1, TimeUnit.SECONDS);
        
        System.out.println("Borrowed: " + conn1 + ", " + conn2);
        System.out.println("Available connections: " + connectionPool.available());
        
        // 归还连接
        connectionPool.returnObject(conn1);
        connectionPool.returnObject(conn2);
        
        System.out.println("After return, available: " + connectionPool.available());
        System.out.println();
    }
    
    private static void testBatchProcessing() throws Exception {
        System.out.println("--- 批处理战术 ---");
        
        // 批量查询用户
        BatchProcessor<String, String> processor = new BatchProcessor<>(
                5, // 批量大小
                1000, // 最大等待时间 1 秒
                items -> {
                    System.out.println("Batch processing " + items.size() + " items: " + items);
                    // 模拟批量数据库查询
                    return items.stream()
                            .map(id -> "User_" + id)
                            .collect(Collectors.toList());
                }
        );
        
        // 提交多个请求
        for (int i = 1; i <= 5; i++) {
            CompletableFuture<List<String>> future = processor.submit("user:" + i);
            future.thenAccept(results -> 
                System.out.println("Results: " + results)
            );
        }
        
        // 等待处理完成
        Thread.sleep(1500);
        processor.shutdown();
        System.out.println();
    }
    
    private static void testAsyncProcessing() throws Exception {
        System.out.println("--- 异步处理战术 ---");
        
        AsyncTaskHandler handler = new AsyncTaskHandler(3);
        
        // 提交多个任务
        handler.submitTask("Task-A", () -> {
            Thread.sleep(500);
            return "Result-A";
        });
        
        handler.submitTask("Task-B", () -> {
            Thread.sleep(300);
            return "Result-B";
        });
        
        handler.submitTask("Task-C", () -> {
            Thread.sleep(700);
            return "Result-C";
        });
        
        // 收集结果(按完成顺序)
        for (int i = 0; i < 3; i++) {
            String result = handler.takeResult();
            System.out.println(result);
        }
        
        handler.shutdown();
    }
}

这个示例展示了四种性能战术:

缓存:减少重复计算的开销

资源池:复用昂贵资源

批处理:减少 IO 往返次数

异步处理:提高响应速度和吞吐量

4.2 示例二:可用性战术的实现

下面我们用 Python 展示可用性战术的实现。

python
"""
可用性战术示例
展示错误检测、恢复和预防的实现
"""

import time
import random
import threading
from typing import Callable, Optional
from enum import Enum


class CircuitState(Enum):
    CLOSED = "closed"
    OPEN = "open"
    HALF_OPEN = "half_open"


# ==================== 战术一:熔断器(错误恢复) ====================

class CircuitBreaker:
    """
    熔断器模式
    当依赖服务频繁失败时,暂时停止调用,给服务恢复时间
    """
    
    def __init__(self, failure_threshold: int = 5, recovery_timeout: float = 30.0):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.last_failure_time: Optional[float] = None
        self._lock = threading.Lock()
    
    def execute(self, func: Callable, *args, **kwargs):
        with self._lock:
            if self.state == CircuitState.OPEN:
                if time.time() - self.last_failure_time > self.recovery_timeout:
                    self.state = CircuitState.HALF_OPEN
                    print("Circuit breaker: HALF_OPEN (probing)")
                else:
                    raise CircuitBreakerOpenError("Circuit is open")
        
        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise
    
    def _on_success(self):
        with self._lock:
            if self.state == CircuitState.HALF_OPEN:
                self.state = CircuitState.CLOSED
                print("Circuit breaker: CLOSED (recovered)")
            self.failure_count = 0
    
    def _on_failure(self):
        with self._lock:
            self.failure_count += 1
            self.last_failure_time = time.time()
            if self.failure_count >= self.failure_threshold:
                self.state = CircuitState.OPEN
                print(f"Circuit breaker: OPEN ({self.failure_count} failures)")


class CircuitBreakerOpenError(Exception):
    pass


# ==================== 战术二:重试带退避(错误恢复) ====================

def retry_with_backoff(
    func: Callable,
    max_retries: int = 3,
    base_delay: float = 1.0,
    max_delay: float = 60.0,
    exponential_base: float = 2.0
):
    """
    带指数退避的重试机制
    临时故障时自动重试,等待时间逐次增加
    """
    last_exception = None
    
    for attempt in range(max_retries + 1):
        try:
            return func()
        except Exception as e:
            last_exception = e
            if attempt < max_retries:
                delay = min(base_delay * (exponential_base ** attempt), max_delay)
                jitter = random.uniform(0.5, 1.5)
                delay *= jitter
                print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay:.2f}s...")
                time.sleep(delay)
            else:
                print(f"All {max_retries + 1} attempts failed")
    
    raise last_exception


# ==================== 战术三:健康检查(错误检测) ====================

class HealthChecker:
    """
    健康检查器
    定期检查组件状态,发现故障
    """
    
    def __init__(self, check_interval: float = 5.0):
        self.check_interval = check_interval
        self.is_healthy = True
        self.last_check_time: Optional[float] = None
        self._running = False
        self._thread: Optional[threading.Thread] = None
    
    def register_check(self, name: str, check_func: Callable[[], bool]):
        """注册健康检查函数"""
        if not hasattr(self, '_checks'):
            self._checks = {}
        self._checks[name] = check_func
    
    def start(self):
        """启动定期检查"""
        self._running = True
        self._thread = threading.Thread(target=self._check_loop, daemon=True)
        self._thread.start()
        print("Health checker started")
    
    def stop(self):
        """停止检查"""
        self._running = False
        if self._thread:
            self._thread.join()
        print("Health checker stopped")
    
    def _check_loop(self):
        while self._running:
            self._run_checks()
            time.sleep(self.check_interval)
    
    def _run_checks(self):
        if not hasattr(self, '_checks'):
            return
        
        all_healthy = True
        for name, check_func in self._checks.items():
            try:
                healthy = check_func()
                if not healthy:
                    print(f"Health check failed: {name}")
                    all_healthy = False
            except Exception as e:
                print(f"Health check error: {name} - {e}")
                all_healthy = False
        
        self.is_healthy = all_healthy
        self.last_check_time = time.time()
    
    def get_status(self) -> dict:
        return {
            "is_healthy": self.is_healthy,
            "last_check_time": self.last_check_time
        }


# ==================== 战术四:降级(错误恢复) ====================

class GracefulDegradation:
    """
    优雅降级
    当非核心功能不可用时,核心功能仍能工作
    """
    
    def __init__(self):
        self.degraded_features = set()
    
    def execute_with_fallback(self, primary_func: Callable, fallback_func: Callable, feature_name: str):
        """
        执行主要功能,失败时使用降级方案
        """
        if feature_name in self.degraded_features:
            print(f"Feature '{feature_name}' is degraded, using fallback")
            return fallback_func()
        
        try:
            return primary_func()
        except Exception as e:
            print(f"Feature '{feature_name}' failed: {e}, degrading")
            self.degraded_features.add(feature_name)
            return fallback_func()
    
    def recover_feature(self, feature_name: str):
        """恢复已降级的功能"""
        self.degraded_features.discard(feature_name)
        print(f"Feature '{feature_name}' recovered")


# ==================== 模拟服务 ====================

class UnreliableService:
    """模拟不稳定的服务"""
    
    def __init__(self, failure_rate: float = 0.3):
        self.failure_rate = failure_rate
        self.call_count = 0
    
    def call(self) -> str:
        self.call_count += 1
        if random.random() < self.failure_rate:
            raise ConnectionError(f"Service call failed (attempt {self.call_count})")
        return f"Success response #{self.call_count}"


# ==================== 测试 ====================

def test_circuit_breaker():
    """测试熔断器"""
    print("\n===== 测试熔断器 =====")
    
    service = UnreliableService(failure_rate=0.7)
    breaker = CircuitBreaker(failure_threshold=3, recovery_timeout=2.0)
    
    for i in range(8):
        try:
            result = breaker.execute(service.call)
            print(f"Call {i + 1}: {result}")
        except CircuitBreakerOpenError as e:
            print(f"Call {i + 1}: Rejected - {e}")
        except ConnectionError as e:
            print(f"Call {i + 1}: Failed - {e}")
        
        time.sleep(0.3)


def test_retry():
    """测试重试"""
    print("\n===== 测试重试带退避 =====")
    
    service = UnreliableService(failure_rate=0.5)
    
    try:
        result = retry_with_backoff(
            service.call,
            max_retries=3,
            base_delay=0.5
        )
        print(f"Final result: {result}")
    except ConnectionError as e:
        print(f"All retries exhausted: {e}")


def test_health_checker():
    """测试健康检查"""
    print("\n===== 测试健康检查 =====")
    
    checker = HealthChecker(check_interval=1.0)
    
    # 注册检查
    checker.register_check("database", lambda: random.random() > 0.2)
    checker.register_check("cache", lambda: random.random() > 0.1)
    
    checker.start()
    
    # 运行一段时间
    time.sleep(3)
    
    status = checker.get_status()
    print(f"Health status: {status}")
    
    checker.stop()


def test_degradation():
    """测试降级"""
    print("\n===== 测试优雅降级 =====")
    
    degradation = GracefulDegradation()
    
    def primary_recommendation():
        if random.random() < 0.5:
            raise ConnectionError("Recommendation service unavailable")
        return "Personalized recommendations based on AI"
    
    def fallback_recommendation():
        return "Popular items (fallback)"
    
    for i in range(5):
        result = degradation.execute_with_fallback(
            primary_recommendation,
            fallback_recommendation,
            "recommendations"
        )
        print(f"Request {i + 1}: {result}")
        time.sleep(0.5)


if __name__ == "__main__":
    test_circuit_breaker()
    test_retry()
    test_health_checker()
    test_degradation()

这个示例展示了四种可用性战术:

熔断器:防止对故障服务的持续调用

重试带退避:临时故障时自动重试

健康检查:定期检测组件状态

优雅降级:非核心功能失败时核心功能仍可用


五、深入思考

5.1 历史背景

架构战术的概念起源于 20 世纪 90 年代的软件工程研究。

起源

  • 1990s 初:Mary Shaw 和 David Garlan 在卡内基梅隆大学研究软件架构,开始系统化架构知识
  • 1996 年:《Software Architecture in Practice》第一版出版,首次提出战术的概念
  • 2000 年:SEI 发布《Software Architecture in Practice》第二版,完善了战术分类体系

发展

  • 2000s:战术被广泛应用于工业界,成为架构设计的标准工具
  • 2005 年:云计算兴起,新的战术不断涌现(如自动伸缩、服务网格)
  • 2010s:微服务和 DevOps 普及,战术与自动化运维结合
  • 2015 年:混沌工程兴起,将可用性战术推向新高度
  • 2020s:Serverless 和边缘计算带来新的战术挑战

演变

战术本身也在演化:

  • 从手动到自动:早期的冗余需要手动配置,现在 Kubernetes 自动管理
  • 从静态到动态:早期的缓存配置固定,现在有自适应缓存策略
  • 从单一到组合:早期战术单独使用,现在强调战术组合和协同

关键人物

  • Len Bass, Paul Clements, Rick Kazman:战术体系的创始人
  • Mary Shaw:软件架构学科的先驱
  • Martin Fowler:推广了许多现代战术(如熔断器、绞杀者模式)
  • Brendan Gregg:性能工程和可观测性领域的专家

5.2 未解决的问题

战术的自动化选择

目前,战术选择主要依靠架构师的经验和判断。能否开发出智能系统,根据质量属性需求和系统特征自动推荐战术组合?这需要结合机器学习和领域知识。

战术的形式化验证

能否在实施战术之前,通过形式化方法验证战术的有效性?例如,数学地证明某种缓存策略能够满足性能要求。

新兴战术的发现

随着新技术的出现(如量子计算、区块链、AI),新的架构战术不断涌现。如何系统地发现和整理这些新战术?

战术的组合优化

多个战术组合时,如何找到最优的组合方式?这是一个组合优化问题,可能需要启发式算法或强化学习。

战术的技术债务

不当的战术应用可能导致技术债务。如何量化战术带来的技术债务?如何制定战术的偿还策略?

5.3 延伸阅读

《Software Architecture in Practice》(第 4 版) - Len Bass, Paul Clements, Rick Kazman

本书对架构战术有最系统和深入的讲解,是学习战术的首选参考。

《Release It!》 - Michael Nygard

专注于生产环境的稳定性战术,介绍了许多实用的模式。

《Building Microservices》 - Sam Newman

微服务架构中的战术应用,包括弹性、可观测性等。

"Architectural Tactics for Cloud-Native Applications"(论文) - Various authors

云原生环境下的新战术研究。

《Designing Data-Intensive Applications》 - Martin Kleppmann

数据密集型系统中的性能和可靠性战术。


六、实验笔记

实验一:战术应用分析

目标:分析真实系统中使用的架构战术。

步骤:

选择一个开源项目或公司内部系统

阅读其架构文档和代码

识别系统中使用的架构战术

对每个战术,分析:

a. 针对哪个质量属性?

b. 解决了什么问题?

c. 带来了什么代价?

d. 是否有更好的替代方案?

绘制战术映射图,展示战术与系统组件的关系

评估战术选择的合理性

预期结果:

  • 一份战术分析报告
  • 至少识别 10 个战术
  • 战术映射图
  • 改进建议

思考题:

哪些战术被过度使用?哪些被忽视?

战术之间是否存在冲突?系统是如何解决的?

如果重新设计,你会选择不同的战术吗?

实验二:战术组合设计

目标:为给定场景设计战术组合。

场景:设计一个在线票务系统,要求:

  • 高峰期(演唱会开票)支持 10 万 QPS
  • 全年可用性 99.99%
  • 防止黄牛刷票
  • 支持快速添加新的演出类型

步骤:

分析每个要求对应的质量属性

为每个质量属性选择合适的战术

设计战术组合,说明各战术如何协同工作

分析战术之间的潜在冲突

估算实施成本

设计验证方案

预期结果:

  • 战术设计方案
  • 战术组合图
  • 冲突分析和解决方案
  • 成本估算
  • 验证计划

思考题:

哪些战术是必须的,哪些是可选的?

如果预算减半,你会去掉哪些战术?

如何验证战术确实达到了预期效果?


七、本章小结

架构战术是实现质量属性的具体手段,是架构设计的工具箱。

核心要点:

战术的层次:

- 战术 < 模式 < 风格

- 战术是模式和风格的构建块

性能战术:

- 资源需求:减少计算、减少数据、减少延迟

- 资源管理:缓存、资源池、负载均衡

- 资源仲裁:优先级、队列、并发控制

可用性战术:

- 错误检测:心跳、异常检测、监控、健康检查

- 错误恢复:冗余、重试、降级、回滚

- 错误预防:移除服务、事务、进程监视

安全性战术:

- 抵抗攻击:认证、授权、加密、输入验证

- 检测攻击:入侵检测、审计、蜜罐

- 恢复:备份、密钥管理、账户恢复

可修改性战术:

- 局部化变更:高内聚、信息隐藏、单一职责

- 防止连锁反应:松耦合、中介、限制通信

- 延迟绑定时间:配置、插件、多态

战术的选择:

- 根据质量属性场景选择

- 考虑战术的代价和收益

- 注意战术之间的协同和冲突

战术应用原则:

  • 没有免费的战术,每个都有代价
  • 战术应组合使用,形成完整方案
  • 战术选择应基于量化需求,而非直觉
  • 定期回顾战术的有效性,及时调整

八、习题

基础题

解释架构战术、架构模式和架构风格的区别和联系。

列出性能战术的三个类别,并为每个类别举两个例子。

可用性战术如何对应错误处理的三个阶段?每个阶段有哪些典型战术?

解释以下安全性战术的原理:

a) 纵深防御

b) 最小权限

c) 默认拒绝

可修改性战术中的"延迟绑定时间"有哪些实现方式?每种方式的优缺点是什么?

进阶题

为一个视频流媒体系统设计战术组合,满足以下要求:

a) 支持百万级并发观看

b) 99.99% 可用性

c) 防止盗版

d) 快速上线新内容

说明每个战术针对的质量属性和实施方式。

分析以下战术冲突,并提出解决方案:

a) 缓存(性能)vs 数据一致性(安全性)

b) 冗余(可用性)vs 成本(业务)

c) 加密(安全性)vs 响应时间(性能)

阅读一个开源项目的代码,识别其中使用的至少 5 个架构战术,分析其合理性和改进空间。

挑战题

设计一个战术评估框架:

a) 定义评估维度(效果、成本、复杂度、风险等)

b) 为每个维度设计评分标准

c) 用该框架评估 5 种常见的性能战术

d) 给出战术选择的决策流程

研究混沌工程与可用性战术的关系:

a) 混沌工程的基本原理是什么?

b) 混沌工程如何验证可用性战术的有效性?

c) 设计一个混沌实验,测试系统的熔断器和降级机制

d) 分析实验结果,提出战术改进建议

4/10架构战术
0%