"你们系统用什么架构?"——这个问题通常期待一个名词:微服务、Spring Cloud、DDD、中台。但名词回答不了任何实际问题。换个问法就露馅了:这个决定如果三个月后要改,要付多少代价? 付不起的那个,才是架构。
所以架构有一个朴素定义:架构是那些事后改起来很贵的早期决定,加上做这些决定时不得不接受的限制。 它不是框架选型清单,也不是那张框框图——图是架构的副产品,不是架构本身。
这篇给三样立刻能用的东西:怎么判断一个决定算不算架构决定、约束怎么写得让方案空间显形、取舍怎么写成一句别人能反驳的话。
1. 朴素定义:改起来很贵的早期决定
同一个团队同一周做出的决定,改起来的代价可以差三个数量级。判断标准只有一条:看改它的代价,不看它听起来高不高级。
| 决定 | 改一次的典型代价 | 算架构决定吗 |
|---|---|---|
| 构建工具用 Maven 还是 Gradle | 半天,改脚本与 CI | 不算 |
| 单体还是拆成多个服务 | 数月,涉及部署、事务、联调、运维 | 算 |
| 数据按什么维度分库分表 | 数月,涉及迁移与双写 | 算 |
| 日志框架用 logback 还是 log4j2 | 一两小时 | 不算 |
| 对外接口用 REST 还是 gRPC | 接口一旦发布就难收回 | 算 |
| 主键用自增还是 UUID | 一周到数月,取决于数据量级 | 视数据量算 |
架构工作的意义,是把注意力压在上面那几行"贵"的决定上,而不是花三天争论下面那几行"便宜"的决定。大部分团队所谓的架构争论,其实是在为便宜的决定付昂贵的会议成本。
2. 架构决策的三个特征
同时具备下面三条里的两条以上,就该按架构决策对待、留下书面记录。
| 特征 | 怎么判断 | 反例(不满足的) |
|---|---|---|
| 难改 | 改它需要停机、迁移数据、改多个团队的系统 | 换一个工具类 |
| 影响面广 | 波及过半模块或超过一个团队 | 只影响一个页面的展示 |
| 约束他人 | 它规定别人"只能怎么做" | 一个可选的工具函数 |
第三条最容易被忽略。约束他人意味着这个决定会成为别人的前提:选了同步调用,别人就不能假设有消息重投;选了单库单表,别人就不能假设能按用户分片。测法是问一句:"这个决定定下来之后,谁的方案被砍掉了?"一个都没砍掉,它大概不是架构决定。
架构决策三问(贴进评审模板,逐条打勾)
[ ] 一年内改它,需要停机或数据迁移吗?
[ ] 它会让两个以上团队的工作方式发生变化吗?
[ ] 它排除了某些人原本想做的方案吗?
三条全否 → 按普通技术决定处理,不要上升为架构讨论。
3. 约束的四类:约束决定方案空间
新手最容易犯的错是先想方案再找约束。顺序应相反:约束先定,方案空间才收敛。 没有约束,任何方案都能被论证成"更好",讨论就永远不结束。
| 约束类型 | 具体表现 | 它砍掉了什么方案 |
|---|---|---|
| 业务规模 | 日订单 3 千 vs 3 千万;峰均比 | 规模小时分库分表是纯负债 |
| 团队能力 | 5 人、无人有 K8s 运维经验 | 砍掉"上服务网格" |
| 存量系统 | 老库不能改表、老接口不能下线 | 砍掉"重新设计数据模型" |
| 时间与成本 | 6 周上线;预算 3 台机器 | 砍掉需要半年的重构路线 |
写成清单比写成散文有用:
## 约束清单(开工前必填)
- 业务规模:日活 / 峰值 QPS / 数据量年增长,来源与时间点
- 团队:人数、值班能力、已有技术栈熟练度
- 存量:不可改的表 / 不可改的接口 / 必须兼容的客户端版本
- 时间:硬上线日期,以及这个日期由谁决定
- 成本:机器与人力上限,超出后谁批
- 不可谈判项:上面哪一条绝不松口
最后一行是关键。约束里一定有一条是硬的(合规、合同、老客户),把它单独标出来,讨论就有了裁判。
4. 质量属性的六个常见项,以及它们必然互相冲突
质量属性不是形容词,是可度量的目标。写不出度量方式的形容词,等于没写。
| 质量属性 | 入门者该用的度量口径 | 常见误判 |
|---|---|---|
| 性能 | P99 延迟、吞吐(每秒请求数) | 只看平均值,忽略长尾 |
| 可用性 | 月度不可用分钟数 | 把"没重启过"当成高可用 |
| 可维护性 | 改一个需求平均触碰几个模块 | 用代码行数衡量 |
| 可扩展性 | 加一台机器带来多少线性提升 | 把"能加机器"当成已扩展 |
| 安全 | 鉴权覆盖率、密钥轮换周期 | 只在登录处做校验 |
| 成本 | 每万次请求的机器与带宽开销 | 只算机器不算人力 |
关键认知:这六项不能同时最优,优化任何一项都在向另一项借钱。
| 你想要的 | 付出的代价 | 具体表现 |
|---|---|---|
| 更低延迟 | 一致性 | 本地缓存快,但可能读到旧值 |
| 更高可用 | 一致性 | 跨机房强一致在分区时拒绝写 |
| 更强可维护性 | 性能 | 多一层抽象多一次跳转与分配 |
| 更强可扩展性 | 成本 | 预留容量、常驻连接、空闲节点 |
| 更高安全 | 延迟与成本 | 每次调用验签、加密、审计落盘 |
| 更快交付 | 可维护性 | 先堆逻辑上线,半年后还债 |
拿这张表跟业务方对齐一次,比讲十页 PPT 有效。
5. 取舍的写法:用 A 换 B,而不是"这个更好"
"Redis 更好"不是取舍,是偏好。取舍必须能写成一句带对手方和代价的话。
坏写法:
选用 Redis 做缓存,性能更好,社区活跃。
好写法:
选用集中式 Redis 而不是本地 Caffeine,
换取多实例之间的数据一致与容量共享;
代价是每次读多一跳网络(典型量级:亚毫秒到数毫秒),
并新增一个必须运维的高可用组件。
当数据以只读配置为主、单机命中率足够高时,推翻这个决定。
| 维度 | 坏写法 | 好写法 |
|---|---|---|
| 主语 | 技术名 | 被放弃的选项 |
| 动词 | 更好 / 主流 | 换取 |
| 宾语 | 无 | 具体收益 + 具体代价 |
| 可反驳性 | 无法验证 | 别人能说"这代价我付不起" |
| 推翻条件 | 缺失 | 写明什么情况下改 |
最后一行是入门者最该抄的习惯:每个决定都写一句"什么情况下要推翻它"。写不出推翻条件,通常是因为代价没想清。
6. 架构的粒度:不同粒度决定不同的事
"架构"在不同场合指三种大小的东西,混着谈就会吵不出结果。
| 粒度 | 决定什么 | 不该在这里决定 |
|---|---|---|
| 系统级 | 系统边界、外部集成方式、数据归属 | 某个接口的字段命名 |
| 服务级 | 服务拆分、通信方式、事务边界 | 内部类的分层方式 |
| 模块级 | 包结构、依赖方向、模块间接口 | 是否上云、是否拆库 |
对应的最小记录模板,五个字段就够:
### 决定:<一句话,动宾结构>
- 问题:现在遇到的具体麻烦是什么(不带方案)
- 约束:来自四类约束里的哪几条
- 选项:A / B / C,每个一句话
- 决定:选 B
- 代价:放弃了什么,谁承担
- 推翻条件:出现什么信号时重开这个话题
- 粒度:系统级 / 服务级 / 模块级
一个决定一页,按时间归档。三个月后有人问"当初为什么这么定",翻这一页比翻会议记录快。
7. 常见误解
| 误解 | 为什么错 | 改成什么 |
|---|---|---|
| 微服务就是架构目标 | 它是交付与边界的手段 | 先写目标,再问微服务是否服务于它 |
| 架构是一次性设计 | 约束会变,设计必须跟着重开 | 按推翻条件复核,不按季度重画图 |
| 技术先进 = 质量高 | 先进性不度量,不匹配团队能力是负债 | 用第 4 节的度量口径说话 |
| 图画完就完成了 | 图不含代价与推翻条件 | 图 + 决策记录,两者都要 |
| 架构是一个人的事 | 被约束方不参与,落地即变形 | 决定由承担代价的一方共同签字 |
入门者的三个动作,今天就能做:
1. 先写清约束:四类约束写成清单,标出哪条不可谈判。
2. 记录决定与理由:每个"贵"的决定留一页,含代价与推翻条件。
3. 给每个决定写"什么情况下要推翻":写不出就说明代价没想清,
回到第 5 节,把"用 A 换 B"补完。
8. 排错:五种"以为在讨论架构"的情况
| 现象 | 真实原因 | 处理 |
|---|---|---|
| 讨论三轮无结论 | 约束没写清,方案空间没收敛 | 先补约束清单 |
| 所有人都在说"更好" | 缺度量口径,只能比形容词 | 每项给一个数或"典型量级" |
| 决定做了但落地走形 | 只约束了自己,没通知被约束方 | 写进接口文档与评审清单 |
| 半年后无法解释设计 | 只留了图,没留代价 | 从当前最贵的三个决定开始补记录 |
| 新技术引入后维护成本飙升 | 团队能力约束被忽略 | 回看第三类约束,评估是否换回 |
9. 边界:什么情况不该用架构决策的方式处理
| 场景 | 更合适的做法 | 理由 |
|---|---|---|
| 单个工具库选择 | 谁用谁定,评审兜底 | 可逆,成本低于讨论成本 |
| 代码风格与命名 | 工具链自动格式化 | 争论收益为零 |
| 一次性脚本 | 直接写,不记录 | 生命周期短于记录成本 |
| 明确的 bug 修复 | 直接改 + 回归测试 | 是缺陷,不是取舍 |
| 未到瓶颈的性能优化 | 先测再改 | 无度量就无取舍 |
一句话收束:架构只处理那些"改起来贵、且必须现在定"的事。 其余交给人、工具和时间。
10. 自测
- 为什么"换日志框架"不算架构决定,而"是否拆服务"算?
- 架构决策的第三个特征最容易被忽略,用什么问句能测出来?
- 约束清单里为什么必须单独标出"不可谈判项"?
- 把"我们用 Kafka,因为吞吐高"改写成符合第 5 节要求的取舍句。
- 系统级、服务级、模块级三种粒度,各自"不该决定"什么?
- 一个决定写不出"推翻条件",通常说明哪一步没做完?