58.6 架构设计流程与三原则
一句话
架构设计不是 “画一张最好的图”,而是 四步流程(识别复杂度 → 3 个备选 → 360 度环评 → 详细设计)套上 三条原则(合适 > 演化 > 简单):流程防止你漏,原则防止你过度设计;两者缺一个,产出要么片面,要么昂贵。
先问:为什么要做架构设计
四个常见动机,三个是错的:
| 动机 | 问题 | 后果 |
|---|---|---|
| “架构重要,所以要做” | 小系统不做架构反而上线更快 | 花两个月设计一套 1 万用户用不上的东西 |
| “照搬业界最佳实践” | 大厂方案的规模前提与你无关 | 水土不服,最后推倒重来 |
| “流程要求,必须有架构文档” | 形式化产出,没人读 | 白耗人力 |
| “为了三高(高并发/高可用/高性能)” | 目标错位,每个功能都要为“可能的高并发”让路 | 加一个功能要改一个月 |
正确的目的只有一句:架构设计是为了解决软件系统复杂度带来的问题——没有复杂度,就不需要架构。
一个算例:学生信息管理系统,1–2 万用户、日均访问不到 1 次、宕机 2 小时没人投诉。
| 决策 | 该不该做 | 理由 |
|---|---|---|
| 缓存 | 不做 | 日均访问 < 1 次,缓存命中率没有意义 |
| 负载均衡 | 不做 | 宕机 2 小时可接受,冗余的收益小于运维成本 |
| 存储高可靠 | 做 | 数据丢了就是事故,主备 + 跨机房同步是底线 |
🔑 架构设计的第一个动作不是画图,而是给复杂度排序:哪个是真问题,哪个是你想象的问题。
三原则:合适 > 演化 > 简单
| 原则 | 含义 | 违反它的样子 | 量化判据 |
|---|---|---|---|
| 合适优于业界领先 | 方案匹配业务规模,不追最新最强 | 亿级平台一上手 40 多个子系统、开发近 1 年,号称 5 万 TPS,实际不到 500 | 目标规模 = 当前 ×2~4;一年内别超过 10 倍 |
| 演化优于一步到位 | 按阶段演进,每阶段可交付 | 一步到位照搬大厂架构,落地遥遥无期 | 每阶段 ≤ 6 个月能上线 |
| 简单优于复杂 | 组件越少越好,能用 3 个别用 5 个 | 把商品、搜索、订单、支付塞进一个千万行组件,clone 一次 30 分钟,改一行整站崩 | 组件故障率 10% 时:3 个组件可用性 72.9%,5 个 59% |
优先级是 合适 > 演化 > 简单,不是并列:先保证方向对(合适),再保证走得动(演化),最后才追求简洁(简单)。反过来先追求 “简洁”,会砍掉必要的冗余;先追求 “先进”,会一步到位地死掉。
两个真实案例说明 “演化” 不是保守,而是按阶段换引擎:
| 阶段 | 淘宝(2003–2005) | 手机 QQ |
|---|---|---|
| 起步 | 2003-04-07 立项、2003-05-10 上线,只有 1 个月 → 买 一个轻量简单的 | 单用户在线约 2 KB,2 GB 内存只撑 100 万在线 |
| 第一次撞墙 | 2003 年底 MySQL 撑不住 → 换 Oracle + NAS + RAC(先买 “方案”,再买 “性能”) | 亿级在线时:昵称加长要 2 个月、好友上限 500→1000 要 3 个月 |
| 结构性换引擎 | PHP 连接池 SQL Relay 死锁必须重启 → 整体换 Java(热切换);2005 年商品 1663 万、PV 8931 万、会员 1390 万后 “买” 彻底不够,才去 IOE 自研 | 4.0 重写 |
用 0.9 的三次方算一遍:单组件可用性 90% 时,串联 3 个是 72.9%,5 个掉到 59%——每加一个组件就多一个故障点,这就是 “简单” 不是审美而是算术。
四步流程
第 1 步:识别复杂度——多数系统只有 1 个
| 复杂度个数 | 含义 |
|---|---|
| 1 个(多数) | 正常:只抓主要矛盾,其余按常规做法 |
| 2 个(少数) | 需要权衡(例如 “高性能 + 低成本”) |
| 3 个以上 | 报警:要么系统原先设计有问题,要么架构师判断失误 |
把业务量翻译成技术指标,是这一步的核心动作(1000 万条/天、10 个子系统读取):
写:1000 万 / 86400 ≈ 115 次/秒
读:1 亿 / 86400 ≈ 1150 次/秒
峰值 ×3:TPS 345、QPS 3450
再留 4 倍:TPS 1380、QPS 13800
阈值:目标可以是当前的 2 倍,也可以是 8 倍(低基数乘 4、高基数乘 2),但不要按 10 倍以上设计,更不要一上来按 100 倍——那 40 个子系统、5 万 TPS 却只有 500 实际承载力的项目,就是这么来的。
第 2 步:设计备选方案——3 个,不是 1 个也不是 10 个
三个典型错误:
| 错误 | 表现 | 后果 |
|---|---|---|
| 只设计 “最优秀” 的方案 | 直接奔着 5 万 TPS 去 | 实际 500,推倒重来 |
| 只做一个方案 | 评审时因为一个缺点就被全盘否决 | 没有退路 |
| 备选做得过细 | 会上争 “健康检查 1 分钟还是 30 秒” | 浪费两小时,架构差异没讨论 |
数量:3–5 个最佳,少于 3 个容易思维狭隘,多于 5 个差别不明显;实践中 3 个最顺手(保守 / 激进 / 折中)。
差异判据——什么才算 “两个不同方案”:
| 差异 | 例子 | 属于 |
|---|---|---|
| 架构差异 | 主备 vs 集群;ZooKeeper vs Keepalived | 放进备选方案 |
| 细节差异 | 健康检查 1 分钟 vs 5 分钟;线程池 200 vs 300 | 留给详细设计 |
第 3 步:360 度环评——先过硬门槛,再按优先级
七个维度:性能、可用性、硬件成本、项目投入、复杂度、安全性、可扩展性。
两种常见的错打分:
| 错法 | 为什么错 |
|---|---|
| 数量对比(优点多者胜) | 等于把七个维度等权,性能和成本一样重 |
| 加权打分(10/5/3 分) | 权重没有客观依据,谁定权重谁赢 |
正解:先定这次最不能牺牲的 1–2 个维度当硬门槛,用它淘汰;通过门槛的方案再按优先级(例如 “可扩展优先、成本其次”)比较。下面这段能跑:
alts = {
"A 直接用 MySQL 单库": dict(性能=2, 可用性=2, 成本=5, 投入=5, 复杂度=5, 安全=3, 可扩展=1),
"B MySQL 主从 + 缓存": dict(性能=4, 可用性=3, 成本=4, 投入=3, 复杂度=3, 安全=3, 可扩展=3),
"C 分库分表 + 单元化": dict(性能=5, 可用性=5, 成本=2, 投入=1, 复杂度=1, 安全=3, 可扩展=5),
}
must = {"性能": 4, "可用性": 3} # 本次的硬门槛
print("方法一:数优点(等权)")
best = max(alts.items(), key=lambda kv: sum(kv[1].values()))
print(f" -> 选 {best[0]}(总分 {sum(best[1].values())})")
print("方法二:先过硬门槛,再按优先级比")
cand = [(k, v) for k, v in alts.items() if all(v[d] >= th for d, th in must.items())]
for k, v in alts.items():
ok = all(v[d] >= th for d, th in must.items())
print(f" {k:<22} {'通过' if ok else '淘汰'}")
cand.sort(key=lambda kv: (-kv[1]["可扩展"], kv[1]["成本"]))
print(f" -> 选 {cand[0][0]}")
本机实测输出(Python 3.14 / macOS arm64):
方法一:数优点(等权)
-> 选 A 直接用 MySQL 单库(总分 23)
方法二:先过硬门槛,再按优先级比
A 直接用 MySQL 单库 淘汰
B MySQL 主从 + 缓存 通过
C 分库分表 + 单元化 通过
-> 选 C 分库分表 + 单元化
方法一选中的 A,性能与可用性根本不达标——它靠 “便宜、简单、好实施” 拿了总分第一。这就是 “数优点” 最危险的地方:它会把致命短板平均掉。
第 4 步:详细设计——落到能开发
到这一步才讨论参数与选型细节。以 Nginx 负载均衡策略为例:
| 策略 | 用在什么情况 |
|---|---|
轮询(默认,down 剔除) | 后端同构、无状态 |
| 加权轮询 | 机器规格不同(32 核配权重 2、16 核配权重 1) |
ip_hash | 没做 session 共享,靠 IP 粘住 |
fair | 后端响应时间差异大(按响应时间分配) |
url_hash | 缓存命中率优先(同一 URL 落到同一台) |
极端情况:详细设计时才发现方案不可行,多半是漏了一个关键质量属性。典型案例:某项目整体排期 1 年,详细设计时才发现 “根本做不完”,只能废弃重排——漏的就是 “开发周期” 这个质量属性(把它列为必评项的做法见 4.6 决策框架与决策记录)。
三条规避:
| 规避 | 做法 |
|---|---|
| 架构师要懂关键细节 | 别把 “细节我们后面再讨论” 挂嘴边,否则不可行性会在最后一周暴露 |
| 分阶段、分系统 | 大系统拆成 2–3 个可独立交付的阶段,每阶段都可回退 |
| 把 “开发周期” 当质量属性 | 排期算不进质量属性的方案,不是方案 |
反模式
| 反模式 | 后果 |
|---|---|
| 没问题也做架构设计 | 小系统被设计成大系统,上线推迟数月 |
| 照搬大厂架构 | 规模不匹配,最后推倒重来 |
| 一上来按 100 倍规模设计 | 40 个子系统、实际承载力只有标称的 1/100 |
| 只做一个备选方案 | 评审因一个缺点全盘否决,没有退路 |
| 备选方案做到 1 分钟的细节 | 会上争论细节,架构差异没人看 |
| 用 “数优点” 或 “加权打分” 选方案 | 致命短板被平均掉,选中不达标的方案 |
| 同时解决 3 个以上复杂度 | 架构过度复杂,团队两年都在做减法 |
| 详细设计留到最后才发现不可行 | 项目整体重排,前面投入全部沉没 |
三条铁律
🔑 先算规模,再谈架构:
当前规模 × 2~4是合理目标;超过 10 倍就是在为想象中的流量付费。
🔑 备选方案 3 个:一个方案是赌博,两个是陪衬,三个才逼出真正的权衡。
🔑 评估先过硬门槛:把 “不能牺牲的 1–2 个维度” 当门槛,剩下的再比——数优点会把致命短板平均掉。
自检
Q1 学生系统(1 万用户、日均访问 < 1 次)要不要做缓存和负载均衡?为什么?
▸ 答案:都不做。日均访问不到 1 次,缓存命中率没有意义;宕机 2 小时没人投诉,冗余的收益小于运维成本。但存储高可靠要做(主备 + 跨机房同步),因为数据丢失是事故,和访问量无关。判据是 “复杂度在哪”,不是 “大厂都这么做”。
Q2 三原则的优先级为什么是 “合适 > 演化 > 简单”,而不是 “简单优先”?
▸ 答案:先错方向,越简单越糟(一个简单的错误架构上线快、死得也快);先保证方向合适,再保证每阶段能交付(演化),最后才在可行方案里追求最少组件(简单)。把 “简单” 放第一位,常见后果是砍掉必要冗余,故障时没有任何缓冲。
Q3 单组件可用性 90%,为什么 5 个组件的系统只有 59%?
▸ 答案:串联可用性是乘法:0.9^3 = 72.9%、0.9^5 = 59%。每加一个串联组件就多一个故障点,且是乘性衰减。这也是 “简单优于复杂” 的算术依据,而不是审美偏好——减少一个非必要组件,等于直接抬高整体可用性。
Q4 为什么 “多数系统只有 1 个主要复杂度”?出现 3 个以上说明什么?
▸ 答案:因为复杂度通常由业务形态决定,一个系统往往只在一个维度上真正吃紧(要么并发高、要么数据量大、要么一致性要求高)。同时要解决 3 个以上,往往说明系统原先的设计有问题(把不该耦合的东西耦合了),或者架构师判断失误(把想象的问题当成真问题)。
Q5 备选方案为什么是 3 个最佳?少于 3 个、多于 5 个分别会怎样?
▸ 答案:少于 3 个容易陷入 “锤子钉子”(熟悉的方案就是唯一方案),且没有折中选项;多于 5 个时,后面的方案与前面的差异已经不明显,讨论成本上升但信息量不增。3 个通常对应 “保守 / 激进 / 折中”,刚好覆盖权衡空间。
Q6 用 “数优点” 选方案,为什么会选中一个性能不达标的方案?
▸ 答案:因为等权求和会把致命短板平均掉。本机演示里方案 A(单库)在成本、投入、复杂度三项都拿 5 分,总分 23 排第一,但它的性能 2 分、可用性 2 分根本不达标。“便宜、简单、好实施” 这些优点与 “能不能扛住流量” 不是同一量级的问题,不能相加。
Q7 “详细设计阶段才发现方案不可行” 通常漏了什么?怎么提前发现?
▸ 答案:通常漏了一个质量属性,最常见的是 “开发周期/人力” 和 “运维复杂度”。提前发现的办法:在 360 环评阶段就把 “项目投入” 当成门槛维度之一(而不是只算硬件成本),并在详细设计前用 2–3 天做一次可行性走查(关键接口能不能实现、数据能不能迁、排期够不够)。