第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 展示多种性能战术的组合应用。
/**
* 性能战术综合示例
* 展示缓存、资源池、批处理和异步处理的应用
*/
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 展示可用性战术的实现。
"""
可用性战术示例
展示错误检测、恢复和预防的实现
"""
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) 分析实验结果,提出战术改进建议