Arch Day 116: 软件架构面试专题
Arch Day 116: 软件架构面试专题
日期: 2026-07-24 (Day 116) 阶段: 第四阶段 - 高阶融合 标签: #面试 #软件架构 #DDD #微服务 #事件驱动 #CQRS #Saga #ATAM #ADR
概述
软件架构面试是技术面试中难度最高的环节——面试官不仅考察你对架构模式的理解,更考察你在实际场景中的权衡和决策能力。本专题整理15道软件架构高频面试题,从DDD到微服务、从CQRS到Event Sourcing、从分布式事务到架构评估,覆盖架构师核心知识域。
每道题包含30秒版本(电梯演讲)、2分钟版本(标准回答)、追问准备(深度展示)。
题目 1:DDD中限界上下文如何识别?
30秒版本
限界上下文(Bounded Context)是DDD战略设计的核心——它定义了一个模型的适用边界。识别方法有三:一是语言边界法,当同一个词在不同团队含义不同时(如"账户"在存款部门和信用卡部门含义不同),就是两个上下文;二是Event Storming法,通过领域事件流中的"枢纽事件"和"角色转换"识别边界;三是业务能力法,每个Level 1业务能力通常对应一个限界上下文。
2分钟版本
三种识别方法详解:
方法1: 语言边界法 (Linguistic)
────────────────────
核心原则: 当同一术语在不同团队有不同含义时,画一条线
示例(电商):
"商品" 在商品域 = SKU + 属性 + 描述 + 图片
"商品" 在订单域 = SKU + 数量 + 价格
"商品" 在库存域 = SKU + 库位 + 数量
→ 三个不同的限界上下文,各有自己的"商品"模型
方法2: Event Storming (事件风暴)
────────────────────
步骤:
1. 列出所有领域事件 (橙色便利贴)
2. 按时间线排列
3. 找到"枢纽事件" (多个后续流程的起点)
4. 找到"角色/参与者变化" (不同人负责不同阶段)
5. 在枢纽事件处画边界 → 限界上下文
示例: "订单已支付"是枢纽事件
→ 之前: 订单上下文 (客户+购物流程)
→ 之后: 履约上下文 (仓库+物流)
→ 两个限界上下文
方法3: 业务能力映射 (Capability Mapping)
────────────────────
Level 1能力 ≈ 限界上下文 (近似映射)
客户管理能力 → 客户上下文
订单管理能力 → 订单上下文
库存管理能力 → 库存上下文
支付处理能力 → 支付上下文
验证标准(识别后如何验证):
- 团队对齐:一个上下文应该由一个团队完整负责(Conway定律)
- 独立部署:修改一个上下文不需要修改另一个
- 数据自治:每个上下文有自己的数据存储
- 业务完整性:一个上下文能独立完成一个有意义的业务功能
追问准备
追问: "限界上下文和微服务是1:1关系吗?"
不一定。初期可以一个上下文包含多个微服务(如订单上下文拆成订单服务+退款服务);但反过来,一个微服务不应该跨越多个上下文。经验法则:限界上下文是服务边界的上限,可以在上下文内进一步拆分,但不要合并不同上下文。
追问: "识别错误的上下文边界怎么办?"
这很常见。信号是:两个服务之间频繁同步调用(耦合太紧,可能应该合并)、一个服务太大改动困难(可能应该拆分)。修正方法:重新做Event Storming,用实际开发中的痛点反推边界调整。DDD的边界设计是迭代的,不是一次性的。
题目 2:微服务拆分的原则?什么时候不该拆?
30秒版本
拆分原则:单一职责(一个服务做一件事)、团队对齐(一个团队拥有1-3个服务)、数据自治(不共享数据库)、独立部署(修改部署不影响其他服务)。不该拆的信号:团队少于10人(运维成本大于收益)、强一致性需求(跨服务事务太复杂)、紧密耦合的业务逻辑(拆了反而更复杂)。"先单体后拆分"是最安全的策略——先用模块化单体证明边界,再按需拆微服务。
2分钟版本
拆分原则(优先级排序):
P0: 业务域边界 (DDD限界上下文)
└── 最重要的拆分依据,确保服务边界有业务意义
P1: 团队边界 (Conway定律)
└── 一个团队(5-8人)负责1-3个服务
└── 跨团队的服务一定有协调成本
P2: 变更频率 (差异化演进)
└── 频繁变更的(如促销) vs 稳定的(如账户) 应该分开
└── 允许不同节奏发布
P3: 扩展性需求 (资源隔离)
└── 流量差异大的应该分开(搜索vs管理后台)
└── 资源类型不同的分开(CPU密集型vs内存密集型)
P4: 安全/合规边界 (隔离敏感数据)
└── PCI DSS要求支付数据隔离
└── GDPR要求个人数据可追溯
不该拆的场景:
| 场景 | 原因 | 替代方案 |
|---|---|---|
| 团队<10人 | 运维成本>收益 | 模块化单体 |
| 强一致性业务 | 分布式事务太复杂 | 单进程+DDD模块 |
| 紧密耦合逻辑 | 拆了变成"分布式单体" | 先解耦再拆 |
| 高频同步调用 | 网络延迟累积 | 合并为一个服务 |
| 数据需要JOIN | 拆了要跨服务查询 | 共享读模型(CQRS) |
| 刚开始的项目 | 边界不清楚 | 先Monolith后拆 |
2025-2026趋势——模块化单体回潮:
行业趋势:
2015-2020: "微服务万岁" → 过度拆分
2020-2023: "反思期" → 分布式单体之痛
2023-2026: "模块化单体" → 理性回归
模块化单体 = 单体部署 + DDD模块化
├── 模块间通过接口通信(非直接依赖)
├── 每个模块有独立的数据Schema
├── 需要时可以拆成独立服务
└── 保持了部署简单+本地事务的优势
代表项目: Shopify(从微服务回归模块化单体)
追问准备
追问: "你遇到过微服务拆分失败的案例吗?"
有。某项目将"订单+支付+库存"拆成三个独立服务,但业务要求下单时必须同步完成库存扣减和支付冻结。结果跨服务的Saga极其复杂,补偿逻辑比正常逻辑还多。最终我建议将订单和库存合并为"交易服务",支付保持独立。教训:不要为了拆而拆,业务一致性边界 > 技术架构理想。
题目 3:分布式事务方案对比(2PC/TCC/Saga)?
30秒版本
三种方案各有适用场景:2PC(两阶段提交)强一致但性能差,适合数据库层面;TCC(Try-Confirm-Cancel)性能好但侵入性强,适合资金类业务;Saga(编排/协同)最灵活但最终一致,适合长流程业务。我的选择优先级是:能用本地事务解决就不用分布式事务,必须跨服务时优先Saga(简单场景)或TCC(资金场景),2PC基本不推荐。
2分钟版本
三种方案对比:
| 维度 | 2PC | TCC | Saga |
|---|---|---|---|
| 一致性 | 强一致 | 强一致 | 最终一致 |
| 性能 | 差(锁资源) | 好(预留不锁) | 最好(异步) |
| 侵入性 | 低(DB驱动) | 高(需改业务) | 中(补偿逻辑) |
| 复杂度 | 中 | 高 | 中 |
| 适用场景 | DB层面 | 资金/高价值 | 长流程/一般业务 |
| 隔离性 | 好 | 需自己处理 | 需自己处理 |
| 回滚方式 | 自动 | Cancel操作 | 补偿事务 |
| 超时处理 | 协调者管理 | 自定义 | 自定义 |
详细机制:
2PC (Two-Phase Commit):
Phase 1 (Prepare): 协调者问各参与者"能提交吗?"
Phase 2 (Commit/Rollback): 全部Yes→提交,任一No→回滚
问题: 协调者单点 / 参与者锁资源等待 / 网络分区时阻塞
适用: 数据库XA事务 (同一DB集群内)
TCC (Try-Confirm-Cancel):
Try: 预留资源 (冻结余额/锁定库存)
Confirm: 确认使用 (扣款/扣库存)
Cancel: 释放预留 (解冻/释放)
优势: 不锁DB资源,性能好
代价: 每个服务需要实现T/C/C三个接口
适用: 资金账务 (银行/支付)
Saga:
编排式: 中央编排器控制流程
Create Order → Reserve Inventory → Charge Payment
如果Payment失败 → Release Inventory → Cancel Order
协同式: 事件驱动,无中央控制
OrderCreated → InventoryReserved → PaymentCharged
PaymentFailed → InventoryReleased → OrderCancelled
优势: 最灵活,无资源锁定
代价: 中间状态可见(隔离性差)
适用: 电商下单/物流/任何长流程
我的决策树:
需要跨服务数据一致性吗?
│
├── No → 本地事务 + 领域事件(异步最终一致)
│
└── Yes → 涉及资金/高价值?
│
├── Yes → TCC (严格一致性)
│
└── No → 流程多长?
│
├── 短(2-3步) → 编排式Saga + Outbox
│
└── 长(4步+) → 编排式Saga + 状态机
追问准备
追问: "Saga的隔离性问题如何解决?"
Saga没有原生隔离性——中间状态对外可见(如库存已扣但订单未创建)。三种缓解方案:(1) 语义锁——在资源上标记"处理中"状态,其他操作看到这个状态就等待或跳过;(2) 业务补偿——设计业务流程时就考虑中间状态是可接受的;(3) 版本号/乐观锁——通过版本号防止并发冲突。
题目 4:Event Sourcing的优缺点?
30秒版本
Event Sourcing不存储当前状态,而是存储所有状态变更事件——通过回放事件重建状态。优点是完整审计追踪、时间旅行(查看任意时刻状态)、天然支持CQRS、事件可驱动下游系统。缺点是查询复杂(需要投影/物化视图)、Schema演进困难(旧事件格式变了怎么办)、存储量大、调试困难。适合金融交易/审计/复杂领域模型,不适合简单CRUD。
2分钟版本
核心概念:
传统方式: 存储当前状态
Account: { id: 1, balance: 800 }
→ 只知道"现在余额800",不知道怎么来的
Event Sourcing: 存储变更事件
Event 1: AccountCreated { id: 1, balance: 0 }
Event 2: MoneyDeposited { amount: 1000 }
Event 3: MoneyWithdrawn { amount: 200 }
→ 回放事件: 0 + 1000 - 200 = 800
→ 知道完整历史,可以回到任意时刻
优点详解:
| 优点 | 说明 | 适用场景 |
|---|---|---|
| 完整审计 | 每次变更都有记录,不可篡改 | 金融/合规 |
| 时间旅行 | 可以重建任意时刻的状态 | 调试/分析 |
| 事件驱动 | 事件天然可以通知下游 | 微服务集成 |
| 读写分离 | 天然支持CQRS | 高读写比 |
| 调试回放 | 可以在测试环境回放生产事件 | 问题复现 |
缺点详解:
| 缺点 | 说明 | 缓解方案 |
|---|---|---|
| 查询复杂 | 不能直接查"余额>1000的账户" | 建投影/物化视图 |
| Schema演进 | 旧事件格式变了怎么办 | Upcaster(事件版本转换) |
| 存储量大 | 事件只增不删 | 快照(Snapshot)机制 |
| 学习曲线 | 团队不熟悉思维模式 | 培训+渐进采纳 |
| 最终一致 | 投影有延迟 | 同步投影(牺牲性能) |
| 调试困难 | 状态是计算出来的 | 好的工具+日志 |
快照优化:
没有快照: 回放全部事件
Event 1 → Event 2 → ... → Event 10000 → 当前状态
(重建一次需要回放10000个事件,慢!)
有快照: 从最近快照开始
Snapshot@Event 9900 → Event 9901 → ... → Event 10000
(只需回放100个事件,快!)
快照策略:
- 每N个事件创建一次快照(如每100个)
- 或定时创建(如每小时)
- 快照 + 后续事件 = 当前状态
追问准备
追问: "Event Sourcing和Event-Driven Architecture有什么区别?"
完全不同的概念。Event Sourcing是一种存储模式——用事件作为数据的一等公民。Event-Driven Architecture是一种通信模式——用事件解耦服务间通信。两者经常一起用(Event Sourcing产生的事件发布到消息总线),但可以独立使用——你可以用Event-Driven但不用Event Sourcing(服务间通过事件通信,但内部存储的是状态)。
题目 5:CQRS什么时候用?什么时候是过度设计?
30秒版本
CQRS(命令查询职责分离)在读写模型差异大时最有价值——比如写是复杂的领域操作(创建订单涉及库存/促销/支付),读是简单的列表查询(订单列表+搜索+统计)。过度设计的信号:读写模型几乎一样(简单CRUD)、团队不熟悉(学习成本高)、业务简单不需要事件驱动。我的经验是:10个服务中大约2-3个需要CQRS,不要全局使用。
2分钟版本
CQRS核心概念:
传统架构:
客户端 → Service → DB (读写同一模型)
CQRS架构:
客户端 → Command Handler → Write Model (领域模型)
│
Event
│
▼
客户端 ← Query Handler ← Read Model (查询优化模型)
Write Model: 丰富的领域模型,强调业务规则和不变量
Read Model: 扁平的查询模型,为具体查询场景优化
适用场景:
| 场景 | 为什么适合 |
|---|---|
| 读写比差距大(100:1) | 读模型可以用缓存/ES/Redis,写模型保持一致性 |
| 读写模型差异大 | 写是聚合根操作,读是跨聚合JOIN |
| 多种查询视图 | 同一数据需要不同的展示方式(列表/报表/搜索) |
| 高并发读 | 读模型可以多副本/缓存/CDN |
| 事件溯源系统 | Event Sourcing天然需要CQRS(事件→投影→查询) |
不适用(过度设计)的场景:
| 场景 | 为什么过度 | 替代方案 |
|---|---|---|
| 简单CRUD | 读写模型一样 | 标准MVC |
| 团队不熟 | 学习成本>收益 | 先学习再引入 |
| 业务简单 | 复杂度在别处 | 直接Repository |
| 强一致性 | 读写延迟不可接受 | 同步投影(部分CQRS) |
| 数据量小 | 一个DB就能搞定 | 单库+缓存 |
实现复杂度梯度:
Level 0: 无CQRS
→ Service → Repository → DB
Level 1: 同库分模型 (最简CQRS)
→ Command → Write Table
→ Query → Read View (DB视图)
Level 2: 异库分模型 (标准CQRS)
→ Command → Write DB (MySQL)
→ Event → Read DB (Elasticsearch)
Level 3: Event Sourcing + CQRS (完整版)
→ Command → Event Store
→ Event → Projection → Read DB
建议: 从Level 1开始,按需升级
追问准备
追问: "CQRS的读模型延迟怎么处理?"
三种方案按场景选:(1) Write-then-Read——写完后返回写模型的数据(不查读模型);(2) 版本号轮询——前端带上版本号,如果读模型版本号不够新就等待或从写模型读;(3) 同步投影——关键场景用同步方式更新读模型(牺牲一些性能保证即时一致)。绝大多数场景用户不会注意到毫秒级的延迟。
题目 6:如何评估架构方案质量(ATAM)?
30秒版本
ATAM(架构权衡分析方法)是SEI提出的系统化架构评估方法。核心流程是:收集利益相关方的质量属性需求(如性能/可用性/安全),将需求转化为具体场景,针对架构方案分析每个场景,识别风险点(可能出问题的决策)、敏感点(影响多个质量属性的决策)、权衡点(改善A但恶化B的决策)。ATAM的价值不是"评分",而是让所有人对架构风险达成共识。
2分钟版本
ATAM流程:
Phase 1: 展示 (Presentation)
1.1 架构师介绍架构方案 (45min)
1.2 识别架构方法/模式/关键决策
Phase 2: 调查与分析 (Investigation)
2.1 利益相关方提出质量属性场景
示例场景:
"双11零点,系统QPS从5万飙到50万,
响应时间仍保持在200ms以内"
2.2 分析场景与架构的匹配
这个场景触发了哪些架构决策?
这些决策的风险是什么?
Phase 3: 测试 (Testing)
3.1 针对每个场景,评估架构的响应
3.2 识别:
- 风险 (Risks): 可能导致质量属性不达标的决策
- 非风险 (Non-risks): 已经验证的安全决策
- 敏感点 (Sensitivity): 微小变化影响大的点
- 权衡点 (Tradeoffs): A↑则B↓的决策
Phase 4: 报告 (Reporting)
输出风险清单 + 权衡清单 + 改进建议
简化版ATAM(适合团队内部使用):
不必搞3-4天的正式评审,可以用"轻量ATAM":
1. 架构师准备: ADR + 架构图 + 质量属性列表
2. 半天工作坊:
- 30min: 架构方案展示
- 60min: 场景走查(按质量属性逐个讨论)
- 30min: 风险识别和优先级排序
3. 产出: 风险登记簿 + 行动项
频率: 每个重大架构决策做一次 (每季度1-2次)
追问准备
追问: "ATAM和Architecture Review的区别?"
ATAM是方法论——有完整的流程、角色和产出。Architecture Review是活动——形式可以多样。ATAM可以作为Architecture Review的一种方法。日常Review可以更轻量(ADR评审+场景走查),重大决策用完整ATAM。关键不是方法论本身,而是"有没有系统化地评估架构质量"。
题目 7:架构决策如何记录和治理(ADR)?
30秒版本
ADR(Architecture Decision Record)是记录架构决策的轻量文档。每个ADR包含:标题(一句话决策)、上下文(为什么需要决策)、决策(选了什么)、被否决的选项(为什么不选)、后果(正面/负面/风险)。ADR存在代码仓库中(和代码一起版本控制),编号递增,状态有Proposed/Accepted/Deprecated/Superseded。治理方面,新服务/技术选型/大变更必须有ADR,在PR中评审。
2分钟版本
ADR模板:
# ADR-XXXX: [标题——一句话决策]
## 状态
Proposed | Accepted | Deprecated | Superseded by ADR-YYYY
## 上下文
为什么需要做这个决策?当前面临什么问题?
## 决策
我们决定...
## 被否决的选项
- 选项A: ... → 不选因为...
- 选项B: ... → 不选因为...
## 后果
### 正面
- ...
### 负面
- ...
### 风险
- ...
## 合规性
与架构原则 AP-XX 一致/冲突
ADR治理体系:
┌─────────────────────────────────────────────┐
│ ADR治理金字塔 │
├─────────────────────────────────────────────┤
│ │
│ L1: 团队级ADR (团队自主) │
│ 范围: 服务内部技术选择 │
│ 评审: Tech Lead审批 │
│ 示例: "用Redis还是Memcached做缓存" │
│ │
│ L2: 域级ADR (跨团队协调) │
│ 范围: 跨服务的架构决策 │
│ 评审: 架构评审会议 │
│ 示例: "域间通信用gRPC还是REST" │
│ │
│ L3: 组织级ADR (架构委员会) │
│ 范围: 全局架构方向 │
│ 评审: 架构委员会 │
│ 示例: "上云策略选AWS还是多云" │
└─────────────────────────────────────────────┘
工具链:
- 存储:Git仓库
docs/adr/目录(和代码一起管理) - 编号:自增编号
ADR-0001,ADR-0002 - 工具:
adr-toolsCLI / Markdown模板 - 搜索:GitBook/Confluence汇总索引
- CI检查:PR包含架构变更时自动提醒"需要ADR吗?"
追问准备
追问: "团队不愿意写ADR怎么办?"
三步走:(1) 降低门槛——ADR不是论文,5分钟能写完的模板最好;(2) 展示价值——找一个"之前没有ADR导致重复讨论同一问题"的案例,让大家感受到痛点;(3) 融入流程——在PR模板中加"是否需要ADR?"的checklist,让写ADR成为自然习惯。
题目 8:如何设计高可用架构?
30秒版本
高可用的核心是"消除单点故障+快速恢复"。四层策略:应用层(无状态+多副本+负载均衡)、数据层(主从复制+读写分离+自动故障切换)、基础设施层(多AZ部署+异地灾备)、流程层(超时+熔断+降级+限流)。可用性目标决定架构复杂度——99.9%(年宕机8.7小时)用主从足够,99.99%(53分钟)需要多AZ,99.999%(5分钟)需要异地多活。
2分钟版本
可用性等级与架构:
| 等级 | 年宕机 | 架构要求 | 成本 |
|---|---|---|---|
| 99% | 87.6小时 | 基本冗余 | $ |
| 99.9% | 8.7小时 | 主从+自动切换 | $$ |
| 99.99% | 53分钟 | 多AZ+自动failover | $$$ |
| 99.999% | 5分钟 | 异地多活 | $$$$ |
四层高可用策略:
Layer 1: 应用层高可用
├── 无状态设计 (Session外部化到Redis)
├── 多实例部署 (至少3个实例)
├── 健康检查 (Liveness + Readiness)
├── 优雅停机 (Pre-stop hook,处理完当前请求)
└── 版本控制 (蓝绿/金丝雀发布)
Layer 2: 数据层高可用
├── MySQL: 主从复制 + MHA/Orchestrator自动切换
├── Redis: Sentinel(中小) / Cluster(大规模)
├── ES: 多副本 + 跨AZ分布
├── Kafka: 3副本 + ISR机制
└── 数据备份: 增量+全量,跨AZ存储
Layer 3: 基础设施层高可用
├── 多AZ部署 (同一Region的2-3个AZ)
├── 负载均衡 (L4/L7 LB,自动摘除不健康节点)
├── DNS容灾 (多Region DNS记录)
└── CDN (静态资源全球分发)
Layer 4: 流程层高可用
├── 超时控制 (合理的超时设置,避免级联等待)
├── 熔断器 (Circuit Breaker,防止雪崩)
├── 降级策略 (核心功能保障,非核心可降级)
├── 限流 (令牌桶/滑动窗口,保护后端)
└── 预案 (故障预案+定期演练)
故障处理原则:
- 快速发现:监控+告警,秒级发现异常
- 自动恢复:自动切换/自动扩容/自动重启
- 人工兜底:自动恢复失败时的应急预案
- 事后改进:Postmortem + 改进项跟踪
追问准备
追问: "异地多活怎么做?最大的挑战是什么?"
异地多活最大的挑战是数据一致性。两种模式:(1) 同城双活——两个AZ同步写入(强一致),延迟低(<2ms);(2) 异地多活——多个Region各自写入,异步同步(最终一致),延迟高(>50ms)。异地多活需要解决:数据路由(用户数据归属哪个Region)、冲突解决(同一数据被两个Region修改)、故障切换(一个Region挂了用户自动切到另一个)。核心思路是"单元化"——按用户维度将数据和请求路由到固定Region,避免跨Region写入冲突。
题目 9:可观测性三支柱如何落地?
30秒版本
可观测性三支柱:Metrics(度量——"系统怎么样")、Logs(日志——"发生了什么")、Traces(追踪——"为什么慢")。落地关键是统一采集(OpenTelemetry SDK)、统一存储(Prometheus+Loki+Tempo 或 Elastic+Jaeger)、统一展示(Grafana)。比工具更重要的是规范——统一的日志格式、统一的Metric命名、统一的Trace上下文传播。
2分钟版本
三支柱关联:
问题: "用户反馈下单慢"
1. Metrics → 发现: 订单服务P99延迟从100ms升到5s
(Grafana仪表盘, 发现问题)
2. Traces → 定位: 订单服务→库存服务的调用耗时4.8s
(Jaeger追踪, 缩小范围)
3. Logs → 根因: 库存服务日志显示"DB连接池耗尽"
(Loki日志, 找到根因)
三者串联:
Metric告警 → 关联TraceID → 查到具体慢调用 →
从TraceID查到日志 → 找到根因
统一技术栈(2025-2026推荐):
采集层: OpenTelemetry (统一SDK)
├── Auto-instrumentation (自动埋点)
├── Manual instrumentation (自定义埋点)
└── Context propagation (W3C Trace Context)
存储层:
├── Metrics: Prometheus / VictoriaMetrics
├── Logs: Loki / Elasticsearch
└── Traces: Tempo / Jaeger
展示层: Grafana (统一Dashboard)
├── Metric → Log 联动 (通过时间+标签)
├── Trace → Log 联动 (通过TraceID)
└── 统一告警 (Grafana Alerting)
规范比工具更重要:
| 规范 | 说明 | 示例 |
|---|---|---|
| 日志格式 | JSON结构化 | {"traceId":"xxx","level":"ERROR","msg":"..."} |
| Metric命名 | 业务域+类型+单位 | order_create_duration_seconds |
| Trace标签 | 统一标签键 | service.name, http.method, db.system |
| 告警分级 | P0-P3 | P0: 影响用户, P1: 影响性能, P2: 需关注 |
追问准备
追问: "微服务太多,链路追踪数据量爆炸怎么办?"
采样策略:(1) 头部采样——按概率采样(如1%),简单但可能漏掉异常请求;(2) 尾部采样——所有请求都记录,但只保存异常/慢的链路(推荐);(3) 动态采样——正常时低采样率,发现异常时自动提高采样率。2025-2026趋势是用Grafana Tempo + 尾部采样,只保存"有价值"的链路,存储成本降低90%。
题目 10:API设计最佳实践?
30秒版本
API设计的核心原则:RESTful语义(名词资源+HTTP动词)、版本化(URL/Header)、幂等性(重复调用结果一致)、分页(大列表必须分页)、错误码标准化(业务码+HTTP码)、安全(OAuth2+速率限制)。对内服务间通信优先gRPC(性能好+强类型),对外API用REST(通用+易理解)。API Gateway做统一鉴权/限流/日志/版本路由。
2分钟版本
API设计规范:
1. 资源命名 (RESTful)
✓ GET /orders (查询订单列表)
✓ GET /orders/{id} (查询单个订单)
✓ POST /orders (创建订单)
✓ PUT /orders/{id} (更新订单)
✓ DELETE /orders/{id} (删除订单)
✗ POST /createOrder (动词命名)
✗ GET /getOrderById (冗余)
2. 版本策略
方式A: URL路径 /v1/orders (简单直观,推荐)
方式B: Header Accept: application/vnd.api+v1 (优雅但复杂)
方式C: Query参数 /orders?version=1 (不推荐)
3. 幂等设计
GET/DELETE: 天然幂等
POST: 用幂等键(Idempotency-Key Header)
PUT: 用版本号(If-Match/ETag)
4. 错误响应标准化
{
"code": "ORDER_NOT_FOUND",
"message": "订单不存在",
"details": { "orderId": "12345" },
"traceId": "abc-123-def"
}
5. 分页
GET /orders?page=1&size=20&sort=createdAt:desc
响应: { data: [...], pagination: { total, page, size, pages }}
6. 安全
├── 认证: OAuth2 / JWT
├── 限流: Rate Limiting (Token Bucket)
├── 加密: TLS 1.3
└── 输入验证: 防注入/XSS
gRPC vs REST 选择:
| 维度 | REST | gRPC |
|---|---|---|
| 协议 | HTTP/1.1 或 2 | HTTP/2 |
| 序列化 | JSON | Protobuf |
| 性能 | 一般 | 高(5-10倍) |
| 强类型 | 弱(需要文档) | 强(proto文件) |
| 浏览器 | 原生支持 | 需要gRPC-Web |
| 流式 | SSE/WebSocket | 原生双向流 |
| 适用 | 对外API | 内部服务间 |
追问准备
追问: "API向后兼容怎么保证?"
三条规则:(1) 只加字段不删字段(新增的字段设默认值);(2) 不改字段类型(string不能变int);(3) 不改语义(同一个字段不能改含义)。如果必须做破坏性变更——发布新版本API(v2),老版本继续维护一段时间(至少6个月),通知客户端迁移,最终下线老版本。用API Gateway做版本路由最优雅。
题目 11:如何做性能优化?
30秒版本
性能优化的方法论:先度量后优化(不要猜)、先定位瓶颈后解决(80/20法则——20%的问题导致80%的性能损耗)。常见优化路径:数据库(慢查询优化/索引/分库分表)、缓存(多级缓存/热点预加载)、应用层(异步化/池化/批量化)、网络(减少调用次数/压缩/CDN)。目标是在成本可接受的范围内达到业务要求的性能指标,不是无限追求极致性能。
2分钟版本
性能优化方法论:
Step 1: 定义目标
"首页加载时间从3s降到1s以内"
"下单接口P99从500ms降到200ms"
Step 2: 度量现状
├── APM工具: 链路追踪看各环节耗时
├── DB慢查询: 找到Top 10慢SQL
├── 压测: 确定当前极限QPS
└── 火焰图: CPU/内存热点分析
Step 3: 定位瓶颈 (通常80%的问题在20%的代码)
常见瓶颈排序:
1. DB查询 (50%的性能问题)
2. 网络调用 (20%)
3. 缓存未命中 (15%)
4. 应用层计算 (10%)
5. 序列化/GC (5%)
Step 4: 针对性优化
DB优化: 索引/SQL重写/分库分表/读写分离
缓存优化: L1本地缓存+L2分布式缓存+预热
应用优化: 异步化/批量化/池化/算法优化
网络优化: 减少调用/合并请求/压缩/CDN
Step 5: 验证和监控
压测验证 → 灰度发布 → 生产监控
多级缓存方案:
请求 → L1本地缓存(Caffeine) → 命中? → 返回
│ 未命中
▼
L2分布式缓存(Redis) → 命中? → 写入L1 → 返回
│ 未命中
▼
DB查询 → 写入L2 → 写入L1 → 返回
TTL策略:
L1: 短TTL(30s~5min),容忍少量不一致
L2: 中TTL(5min~1h),主动失效+TTL双保险
追问准备
追问: "缓存穿透/击穿/雪崩怎么处理?"
穿透(查不存在的数据):布隆过滤器+缓存空值。击穿(热Key过期瞬间大量请求打到DB):永不过期+后台刷新 或 互斥锁(只放一个请求穿透到DB)。雪崩(大量Key同时过期):随机化TTL(基础TTL+随机偏移)+预热+限流。
题目 12:安全架构的核心原则?
30秒版本
安全架构的核心原则:零信任(不信任任何网络位置,每次访问都验证)、最小权限(只给必需的权限)、纵深防御(多层安全措施,一层被突破还有下一层)、安全左移(在设计和编码阶段就考虑安全,而非上线后补)。落地措施包括:认证(OAuth2+MFA)、授权(RBAC/ABAC)、加密(传输TLS+存储AES)、审计(所有敏感操作留痕)、漏洞管理(SAST/DAST/SCA自动扫描)。
2分钟版本
零信任架构(2025-2026主流):
传统安全模型 (城堡模式):
外部 → 防火墙 → 内部(信任区)
问题: 一旦突破防火墙,内部横向移动无阻
零信任模型:
每个请求都需要:
1. 身份验证 (Who are you?)
2. 设备验证 (What device?)
3. 上下文评估 (Where/When/How?)
4. 最小权限 (What can you access?)
5. 持续验证 (Still trusted?)
落地要素:
├── Service Mesh (mTLS,服务间加密)
├── IAM (统一身份管理)
├── API Gateway (统一鉴权+限流)
├── Secret Management (Vault管理密钥)
└── Network Policy (K8s网络策略,微隔离)
安全左移:
| 阶段 | 安全活动 | 工具 |
|---|---|---|
| 设计 | 威胁建模(STRIDE) | Microsoft Threat Modeling |
| 编码 | 安全编码规范 | IDE插件(SonarLint) |
| 构建 | SAST(静态扫描) | SonarQube/Semgrep |
| 测试 | DAST(动态扫描) | OWASP ZAP |
| 部署 | 镜像扫描+合规检查 | Trivy/Snyk |
| 运行 | WAF+IDS+审计 | CloudFlare/Falco |
追问准备
追问: "如何应对供应链攻击(如Log4j这类)?"
三层防御:(1) 预防——用SCA工具(如Snyk/Dependabot)扫描所有依赖,自动告警已知漏洞;(2) 检测——SBOM(软件物料清单)记录所有组件版本,一旦新漏洞披露立即知道是否受影响;(3) 响应——预案和流程,收到CVE后X小时内评估,Y小时内修复。Log4j事件的教训是:你必须知道自己用了什么(SBOM),否则无法快速响应。
题目 13:如何管理技术债?
30秒版本
技术债管理的关键是"看见它"——看不见的债务才是最危险的。四步走:识别(代码扫描+团队反馈+架构评审)、量化(用修复成本/不修复的每年维护成本来量化)、优先级(关键路径上的债>非关键路径的)、还债(每个Sprint分配20%容量处理技术债)。不要追求零债务——适度的技术债是合理的商业决策,关键是有意识地管理而非无意识地积累。
2分钟版本
技术债四象限:
故意的 无意的
┌──────────────┬──────────────┐
审慎的 │ "我们知道快速 │ "我们现在知道 │
│ 发布会欠债, │ 当初应该怎么 │
│ 以后再优化" │ 设计了" │
│ │ │
│ → 记录+计划还债│ → 重构计划 │
├──────────────┼──────────────┤
鲁莽的 │ "赶紧上线, │ "什么是分层 │
│ 管它的" │ 架构?" │
│ │ │
│ → 紧急修复 │ → 培训+重写 │
└──────────────┴──────────────┘
量化方法:
技术债利息 = 因为债务导致的额外成本/年
├── 开发减速: "本来1天能做的功能,现在要3天" → ΔCost
├── 故障成本: "每月因为这个坑导致1次事故" → ΔIncident
├── 招聘影响: "技术栈太老,招不到人" → ΔHiring
└── 安全风险: "老版本框架有已知漏洞" → ΔRisk
技术债本金 = 彻底修复所需的投入
├── 重构成本: 多少人天?
├── 测试成本: 回归测试范围?
└── 风险成本: 重构过程可能引入的问题
还债优先级 = 利息 / 本金 (比值越高越优先还)
"维护成本高+修复成本低" → 优先还
"维护成本低+修复成本高" → 可以忍一忍
追问准备
追问: "领导说'先做需求再说',怎么争取技术债时间?"
两个策略:(1) 用数据说话——"因为技术债,过去3个月平均每个需求多花2天,相当于浪费了6个人天/月";(2) 捆绑策略——将技术债修复嵌入到需求开发中(做新需求时顺便重构相关模块),而非单独申请"重构时间"。最有效的是每个Sprint分配20%容量给技术债——这不需要领导特别批准,是团队对工程质量的基本投资。
题目 14:如何选择数据库?
30秒版本
数据库选型取决于数据模型和访问模式:关系型数据(MySQL/PostgreSQL)——ACID事务+复杂查询;文档型(MongoDB)——Schema灵活+快速迭代;键值型(Redis)——高性能缓存+计数器;列族型(HBase/Cassandra)——海量写入+时序数据;搜索型(Elasticsearch)——全文搜索+日志分析;图数据库(Neo4j)——关系图谱+推荐。选型原则:一个系统可以用多种数据库(Polyglot Persistence),但要控制种类数量(不超过3-4种)。
2分钟版本
选型决策树:
你的数据模型是什么?
│
├── 结构化+关系 → 需要事务?
│ ├── Yes → MySQL/PostgreSQL (OLTP)
│ └── No → 数据量?
│ ├── <100GB → PostgreSQL
│ └── >100GB → TiDB/CockroachDB
│
├── 半结构化/文档 → MongoDB / DynamoDB
│
├── 键值/缓存 → Redis / Memcached
│
├── 时序数据 → InfluxDB / TimescaleDB
│
├── 搜索/日志 → Elasticsearch / OpenSearch
│
├── 图关系 → Neo4j / Neptune
│
└── 海量写入/宽表 → HBase / Cassandra / ScyllaDB
Polyglot Persistence示例:
电商系统:
├── 订单/支付: MySQL (ACID事务)
├── 商品搜索: Elasticsearch (全文搜索)
├── 用户Session: Redis (高性能)
├── 用户画像: MongoDB (灵活Schema)
├── 日志/监控: Elasticsearch (日志分析)
└── 推荐关系: Neo4j (图查询)
原则: 为每种数据选择最合适的存储,但控制总数(≤4种)
追问准备
追问: "MySQL还是PostgreSQL?怎么选?"
技术上PostgreSQL更强(支持JSON/数组/GIS/全文搜索/物化视图),MySQL生态更成熟(工具多/人才多/云服务完善)。我的选择:新项目首选PostgreSQL(功能更全面),已有MySQL生态的团队继续用MySQL(迁移成本不值得)。2025-2026趋势是PostgreSQL市场份额持续增长,是最"全能"的关系型数据库。
题目 15:架构师如何平衡"理想架构"和"交付压力"?
30秒版本
核心原则是"做正确的权衡,而非追求完美"。三个实用策略:第一,区分"不可逆决策"和"可逆决策"——不可逆决策(如数据库选型/服务边界)必须慎重,可逆决策(如具体实现方式)可以快速做;第二,"最小可行架构"——第一版只实现核心质量属性需求,不过度设计;第三,"技术债预算"——有意识地欠债并记录还债计划。架构师的价值不是设计完美架构,而是在约束条件下做出最优决策。
2分钟版本
Jeff Bezos的决策框架:
Type 1 决策 (不可逆,单向门):
├── 数据库选型 → 慎重,充分讨论,写ADR
├── 核心服务边界 → 慎重,Event Storming+评审
├── 云平台选择 → 慎重,PoC验证
└── 对外API契约 → 慎重,向后兼容
Type 2 决策 (可逆,双向门):
├── 具体框架版本 → 快速决定,不行再换
├── 缓存策略 → 快速实验,数据说话
├── 内部API设计 → 快速迭代,逐步优化
└── 日志格式 → 先定一个,后续统一
最小可行架构(MVA):
阶段1: MVA (最小可行架构)
├── 只解决当前确定的质量属性需求
├── 用最简单的方案实现
├── 记录"知道但暂不处理"的决策(ADR)
└── 示例: 先用单体+模块化,不上微服务
阶段2: 按需演进
├── 业务增长触发架构演进(如QPS从1K到10K)
├── 每次演进只解决当前瓶颈
├── 更新ADR,记录演进理由
└── 示例: 某模块成为瓶颈 → 拆为独立服务
阶段3: 持续优化
├── 定期架构评审(ATAM轻量版)
├── 技术债可视化和管理
├── 架构适应度函数自动监控
└── 示例: 自动检测服务间耦合度
实用技巧:
| 策略 | 说明 |
|---|---|
| YAGNI | 你不需要它(不为假想需求设计) |
| 最后责任时刻 | 推迟决策到不得不做的时刻(信息最充分) |
| 可逆性 | 优先选择容易修改的方案 |
| 增量架构 | 小步演进而非大步重构 |
| 技术债预算 | 每Sprint 20%容量用于偿还技术债 |
追问准备
追问: "如果领导要求'三个月做出来'但你觉得架构需要六个月,怎么办?"
三步沟通:(1) 分析三个月能交付什么——"三个月可以上线MVP,核心功能完整但缺少高可用/监控";(2) 明确风险——"三个月版本大促可能扛不住,需要额外1个月做性能优化";(3) 提供分阶段方案——"3个月上线MVP→第4个月性能优化→第5-6个月完善监控和安全"。不要说"做不到",而是说"可以分阶段做到"。
面试技巧总结
软件架构面试评分维度
| 维度 | 权重 | 优秀表现 |
|---|---|---|
| 权衡能力 | 30% | 能分析多个方案的优劣并做出合理选择 |
| 深度理解 | 25% | 不只知道"是什么",更知道"为什么"和"什么时候不该用" |
| 实战经验 | 20% | 有真实项目案例,能说出踩过的坑 |
| 全局视野 | 15% | 不局限于技术,能关联业务/团队/成本 |
| 表达清晰 | 10% | 结构化回答,能画图说明 |
15题速查表
| # | 题目 | 核心关键词 |
|---|---|---|
| 1 | 限界上下文识别 | 语言边界 / Event Storming / 能力映射 |
| 2 | 微服务拆分 | 何时拆何时不拆 / 模块化单体 |
| 3 | 分布式事务 | 2PC/TCC/Saga / 本地事务优先 |
| 4 | Event Sourcing | 优缺点 / 快照 / vs Event-Driven |
| 5 | CQRS | 何时用 / 过度设计 / 复杂度梯度 |
| 6 | ATAM | 场景驱动 / 风险-敏感-权衡 |
| 7 | ADR | 模板 / 治理金字塔 / 存代码仓库 |
| 8 | 高可用 | 四层策略 / 可用性等级 / 异地多活 |
| 9 | 可观测性 | 三支柱 / OpenTelemetry / 采样 |
| 10 | API设计 | REST/gRPC / 幂等 / 版本化 |
| 11 | 性能优化 | 先度量 / 定位瓶颈 / 多级缓存 |
| 12 | 安全架构 | 零信任 / 安全左移 / 供应链安全 |
| 13 | 技术债 | 四象限 / 量化 / 20%容量 |
| 14 | 数据库选型 | 决策树 / Polyglot / MySQL vs PG |
| 15 | 理想vs现实 | Type1/Type2决策 / MVA / YAGNI |
今日总结
关键收获
- 权衡是架构师的核心能力——没有"最佳架构",只有"最适合当前约束的架构"
- "什么时候不该用"比"什么时候用"更重要——过度设计和设计不足一样有害
- DDD不是微服务的同义词——限界上下文可以在模块化单体内实现
- Event Sourcing和CQRS是工具,不是目标——10个服务中2-3个需要就够了
- 架构决策要记录(ADR)——6个月后你自己都不记得当初为什么这么决定
明日预告: Day 117 - 金融领域面试专题,15道精选金融架构面试题,涵盖核心银行、支付、风控、合规、交易等核心主题。