06 · 质量属性与取舍
一句话点题:功能决定系统能不能用,质量属性决定系统好不好用、扛不扛得住、值不值得。而这些属性彼此冲突,你不可能全都要——架构的本质,就是按业务排好优先级,然后清醒地做取舍。
先接上 02:质量属性是什么,为什么它才是架构的主战场
在 02 · 架构师的思考框架 里,我们把需求拆成了两类:
- 功能性需求:系统要做什么,比如下单、搜索、发消息。
- 非功能性需求 / 质量属性:系统要把这些事做得多好,比如多快、多稳、多省、多安全。
新人盯着功能,架构师盯着质量属性。原因很简单:功能往往有标准答案。要个购物车,大家做出来都差不多;质量属性几乎全是取舍,没有标准答案——它才是真正考验判断力的地方。
这一章,我们把 02 里列过的质量属性清单逐个展开。每一个,我都讲清三件事:
① 怎么度量。说不出数字的目标都是空话。② 怎么实现。常用哪些架构手段。③ 和谁冲突。它不是免费的,总会牺牲一些东西。
第三点是灵魂。因为本章最核心的真相是:这些属性互相打架。 把任何一个拉满,几乎必然伤害另一个。
一、性能(Performance)
① 怎么度量 —— 先分清两个常被混为一谈的指标:
- 延迟(Latency):单个请求从发出到拿到结果有多快。它关心一次操作要等多久。
- 吞吐(Throughput):单位时间能处理多少请求,比如 QPS。它关心一秒能服务多少人。
这俩不是一回事,甚至常常对立。一个直觉类比:
延迟 = 一辆车从北京到上海要多久,关心单程时间
吞吐 = 这条高速公路每小时能通过多少辆车,关心总流量
修一条限速200的赛道:延迟极低,单车飞快,但只有一条道,吞吐低
修一条限速80的十车道:单车不快,延迟高,但车流巨大,吞吐高度量延迟时,千万别只看平均值。平均延迟 100ms 听着不错,但可能 99% 的人是 50ms,1% 的人卡了 5 秒——而那 1% 可能正是你最重要的大客户。所以要看百分位(P95、P99、P999),也就是 99% 的请求能在多少毫秒内完成。尾延迟(长尾)往往才是体验杀手。
还有一个常被忽视的维度:感知延迟。 用户觉得快不快,和真实的总耗时常常不是一回事。
还记得 AI 对话产品 的流式输出吗?它没让总生成时间变短,但让用户1 秒内就看到第一个字往外蹦,体验天差地别。这就是感知延迟的魔力。任何让用户更早看到反馈的设计,比如流式、骨架屏、乐观更新、进度提示,都在改善感知延迟——而它常常比真实延迟更重要。
② 怎么实现:缓存把重算和远取变成近读,读写分离分担压力,异步化把慢活儿丢进队列并先返回,CDN 把内容放到离用户更近的地方,再配合更好的数据结构、索引和预计算。注意——这些手段你在 04 和 05 都见过了。
③ 和谁冲突:主要和成本冲突,因为更快往往要更多机器、更贵的存储、更多缓存;也和一致性冲突,因为缓存、异步换来速度,代价是数据可能不那么新;还和简单性冲突,因为每一项性能优化都是一份额外的复杂度。
二、可用性(Availability)
① 怎么度量 —— 通常用几个 9表示正常运行时间的百分比。这串数字背后是冷冰冰的每年允许停机多久,务必有体感:
可用性 每年停机时间 每月停机时间 体感
─────────────────────────────────────────────────────────
99% (两个9) ≈ 3.65 天 ≈ 7.2 小时 玩具/内部工具
99.9% (三个9) ≈ 8.76 小时 ≈ 43 分钟 一般在线服务的及格线
99.99%(四个9) ≈ 52.6 分钟 ≈ 4.3 分钟 严肃的商业服务
99.999%(五个9) ≈ 5.26 分钟 ≈ 26 秒 电信/支付级,极其昂贵每多一个 9,成本和复杂度往往是数量级地往上翻。 从三个 9 到五个 9,不是再努力一点,而是几乎重做一遍架构、再砸几倍的钱。所以——别张口就要五个 9,先问业务:这服务挂一小时,到底损失多少?有时三个 9 完全够,有时差一分钟都是巨额损失。
② 怎么实现:核心思想就一个——消灭单点故障(Single Point of Failure),用冗余兜底。
- 冗余(Redundancy):任何关键组件都至少有备份,一个挂了另一个顶上。还记得 05 章 的主从复制吗?从库就是主库的冗余兜底。
- 故障域隔离(Fault Domain):把鸡蛋放在不同篮子里——多机器、多机架、多可用区、多区域。让任何单点的失败,都不会带垮全局。
- 无单点:从入口到存储,逐层检查这一环挂了,整条链路是不是就断了,把每一个这样的环都做成冗余的。
- 优雅降级:扛不住时,宁可关掉次要功能、保住核心。比如大促时关掉猜你喜欢,保住下单支付,也别整个崩掉。
有单点(脆弱): 用户 ─▶ [唯一的网关] ─▶ [唯一的数据库]
↑ 任何一个挂 = 整个系统挂
无单点(健壮): 用户 ─▶ [网关×3] ─▶ [主库 + 多从库,跨可用区]
↑ 挂一个,其余顶上,用户无感③ 和谁冲突:和成本冲突,因为冗余就是花钱养一堆平时用不上的备份资源;和一致性冲突,这是下面会专门讲的核心矛盾;也和简单性冲突,因为多活、故障切换、健康检查都是复杂度。
三、可扩展性(Scalability)
① 怎么度量:当负载,也就是用户数、数据量、请求量增长 N 倍时,系统能不能通过加资源平滑地撑住,而不是直接崩掉或要推倒重来。好的可扩展性意味着加机器就能扛更多,且代价大致是线性的。
② 怎么实现 —— 先分清两种扩法:
垂直扩展 (Scale Up) 水平扩展 (Scale Out)
给单台机器升级配置 增加更多台机器
(换更强的 CPU、更大内存) (一变十,十变百)
┌──────┐ ┌──────────┐ ┌──┐ ┌──┐┌──┐┌──┐┌──┐
│ 小机器 │ ─▶ │ 大机器 │ │机│ ──────▶ │机││机││机││机│
└──────┘ └──────────┘ └──┘ └──┘└──┘└──┘└──┘
优点:简单,代码不用改 优点:理论上可无限扩,还顺带有冗余
缺点:有物理天花板、越贵越不划算、 缺点:架构必须支持(关键前提是
且这台机器本身是个单点 ——组件得是无状态的!见 05 章)关键洞察:垂直扩展是治标,有天花板;水平扩展才是治本,但它有一个硬前提——组件必须能被水平扩展。 而什么东西好水平扩展?无状态的。 这就直接接上了 05 章 那条第一性原理:无状态好扩,有状态难扩。 所以可扩展性很大程度上,就是尽量把系统做成无状态,把难搞的状态收拢到少数几处去专门处理,比如分片和复制的艺术。
③ 和谁冲突:和一致性冲突,因为水平扩展通常意味着多副本和分片,一致性会变难,详见 05 章;和简单性冲突,因为分布式永远比单机复杂;也和成本冲突,因为短期看,把架构改造成可水平扩展本身就是一笔投入。
四、一致性(Consistency)—— 承接 05
这一条我们在 05 · 数据与状态 已经讲透了,这里只把它放回质量属性的棋盘,强调它为什么总会引发冲突。
- ① 怎么度量:在强一致 ←→ 最终一致这条谱系上,你的数据落在哪?写入后多久能保证所有读都看到最新值?
- ② 怎么实现:强一致靠事务、靠协调协议,代价是慢,也可能拒服务;最终一致靠异步同步、靠 BASE 思路,代价是存在不一致窗口。
- ③ 和谁冲突:它几乎和扩展性、可用性、性能都冲突。 CAP 已经说死了:分区时,一致性和可用性二选一。这是架构里最根本、最绕不开的一组矛盾。
一句话回顾:强一致很贵,要花在真出事的地方,比如钱和库存;其余用最终一致换可用和扩展。 详见 05 章。
五、安全性(Security)
① 怎么度量:安全难以用单一数字度量,但可以问清楚:攻击面有哪些?最坏情况下会泄露/损失什么? 它更像是一条底线,而不是越多越好的指标。
② 怎么实现 —— 几个贯穿始终的原则:
- 永远不信任输入:所有外部进来的数据,包括用户输入、第三方回调,甚至上游服务的返回,都当成可能有毒的来处理。
还记得 AI 产品的提示注入吗?那只是不信任输入这条老规矩在 AI 时代的新形态。任何重新进入核心系统的外部文本都不可信。
- 最小权限:每个组件只拿它干活必需的那点权限,不多给。一处被攻破,损失被限制在最小范围。
- 纵深防御:别指望一道墙挡住所有人。鉴权、限流、加密、隔离、审计……层层设防,攻破一层还有下一层。
- 数据分级保护:密码、支付、隐私等敏感数据,传输和存储都加密,且严格隔离、可审计。
③ 和谁冲突:和性能冲突,因为每一道校验、每一次加解密都有开销;和易用性 / 可维护性冲突,因为安全措施往往让系统更难用、开发更繁琐,这就是著名的安全 vs 便利之争;也和成本冲突,因为安全是持续投入。
安全的特殊之处:它平时是看不见的成本,出事时是致命的代价。它不能用 ROI 不高为由省掉——一次数据泄露可能直接让公司关门。
六、可维护性 / 可演进性(Maintainability / Evolvability)
① 怎么度量:改一个功能、修一个 bug、加一个特性,要多久?新人多久能上手?改动会不会牵一发而动全身、引入意外的连锁故障?这衡量的是系统面对变化时的从容程度。
这是最容易被忽视、却影响最深远的属性。因为软件 99% 的时间都处在被修改的状态——你写它一次,却要改它一千次。一个跑得飞快但谁都不敢碰的系统,长期看是巨大的负债。
② 怎么实现:清晰的模块边界与低耦合,让改一处不波及全身,见 04 章 的分层与模块化;高内聚,把相关的东西放一起;好的抽象;充分的可观测性,用日志、监控、追踪看见系统内部在发生什么;以及——把决策和理由写下来。
这正是 08 · 架构决策记录与演进 要讲的:用 ADR 记录当时为什么这么决定。半年后的你或继任者看到一个奇怪的设计,最想知道的就是这到底是深思熟虑,还是历史包袱? 可演进性,很大程度上取决于后人能不能读懂前人的取舍。
③ 和谁冲突:和性能冲突,因为很多极致的性能优化,代价就是代码变得晦涩、难懂、难改;也和短期交付速度冲突,因为写得干净、留好扩展点要花更多时间。这就是技术债的来源:借明天的可维护性,换今天的上线速度。
七、成本(Cost)—— 最常被忽视,却常常是真正的约束
① 怎么度量:服务器 / 存储 / 带宽 / 第三方服务 / 人力运维的总花费。最有用的是看单位成本——每用户、每请求、每订单、每千 token 要花多少钱。还记得 AI 产品每千 token 成本是头号指标吗?
② 我要专门为它说几句重话:
成本是最常被工程师忽视的质量属性,但它常常是那个真正卡住你的、不可逾越的约束。
新人画架构图时,脑子里常常默认资源无限:多加几个缓存、多上几个副本、多开几个服务、要五个 9……每一个听起来都更好。但现实是:这些更好全都在烧钱,而钱是有限的。 一个理论上完美、但把公司烧破产的架构,是失败的架构。
成本的几个反直觉之处:
- 它会偷偷复利:一个低效的设计,在 1 万用户时每月多花几百块没人 care;到 1 亿用户时,就是每月多烧几百万——同一个设计缺陷,被规模放大成了天文数字。
- 它和你前面学的几乎所有手段都挂钩:冗余要钱、缓存要钱、多副本要钱、强一致因为难扩、要更强的机器而要钱,低延迟也因为要更多更好的资源而要钱。你为每一个质量属性买单,最终都记在成本这张账单上。
- 它常常是真正的约束:很多架构争论,扒到最后发现争的不是技术上行不行,而是这点钱、这点人,值不值得这么干。
③ 和谁冲突:它和几乎所有其他质量属性都冲突——因为提升任何一个属性,大概率都要花更多的钱。成本是那个站在所有取舍背后、最终拍板的会计。
核心:这些属性互相冲突,你不可能全都要
把上面七条连起来,你会发现一张冲突网。这是本章——也是整个架构思维——最重要的一课:
不存在性能又高、又强一致、又高可用、又便宜、又简单、又安全的系统。 每一个属性拉满,都在牺牲别的属性。架构师的工作不是全都要,而是按业务决定要哪个、舍哪个。
几组经典冲突,刻进脑子:
┌─────────────────────────────────────────────────────────┐
│ ① 一致性 vs 可用性 (CAP:网络分区时,鱼与熊掌不可兼得) │
│ 钱/库存偏一致 ◀──────────────▶ 点赞/动态流偏可用 │
│ │
│ ② 性能 vs 成本 (要更快,几乎总要砸更多/更贵的资源) │
│ │
│ ③ 灵活/可扩展 vs 简单 (微服务很灵活,但复杂度爆炸; │
│ 单体很简单,但难独立扩 —— 见 04 章) │
│ │
│ ④ 安全 vs 便利/性能 (每道防线都增加摩擦和开销) │
│ │
│ ⑤ 上线速度 vs 可维护性 (赶工 = 借技术债,迟早连本带利还) │
└─────────────────────────────────────────────────────────┘最经典的直觉图,是一致性、可用性、延迟构成的不可能三角——你很难同时把三个角都拉满,优化任何一个,往往要在另外两个上让步:
一致性 (C)
(数据永远最新)
╱ ╲
╱ ╲
╱ 你只能站在 ╲
╱ 这个三角的某处 ╲
╱ 离哪个角近,就在 ╲
╱ 哪个上更强,代价是 ╲
╱ 离另外两个更远 ╲
╱____________________________________╲
可用性 (A) 低延迟 (L)
(随时都能服务) (响应飞快)
• 想离一致性近,靠强一致和同步 → 往往牺牲可用性或延迟
• 想离低延迟近,靠缓存和异步 → 往往牺牲一致性
• 想离可用性近,靠多副本冗余 → 分区时往往得放弃强一致这张图不必当成严格的数学定理,把它当直觉用:每次有人说我全都要,你就把这个三角或上面那张冲突网摆出来,问一句:那你打算从哪个角往后退?
怎么破? 答案永远是同一句:回到业务,排优先级。 没有放之四海皆准的最优解,只有在这个业务、这个规模、这笔预算下,更合理的那个解——这正是 README 里那条原则:没有最好的架构,只有最合适的架构。
怎么和老板 / 产品经理谈取舍:把技术翻译成业务后果
这是质量属性这一章里,最实用、也最被工程师做错的一件事。
你跑去跟老板说,我们应该上最终一致,而不是强一致。老板一脸茫然,觉得你在炫术语,然后凭感觉拍板。错的不是老板,是你。 取舍的优先级,本就该由懂业务的人来定;而你的职责,是把技术选择翻译成他们听得懂、能决策的语言——也就是钱、风险、上线时间。
翻译公式:别讲技术参数,讲业务后果。
❌ 工程师的说法(对方听不懂,无法决策)
"这里用强一致会导致写吞吐下降,我们得加分片,还会牺牲可用性。"
✅ 翻译成业务后果(对方能拍板)
"方案 A(强一致):保证一分钱都不会错,但大促时可能要排队、慢一点,
而且每月多花 X 万运维。
方案 B(最终一致):又快又便宜,但极端情况下,用户余额可能有几秒
显示不准——这个我们能接受吗?
我的建议是 A,因为这是钱,出一次错赔的比省的多得多。您怎么看?"几条沟通心法:
- 永远给选项 + 代价,而不是结论。 把 A、B 两条路各自会得到什么、放弃什么、花多少钱、多久能上摆清楚,让业务方在知情下选择。这既尊重了对方的决策权,也保护了你自己,因为决策是共同做的。
- 用三种货币报价:钱、风险、时间。 几乎任何技术取舍,都能折算成多花或省多少钱、出事概率和后果多大、能早或晚多久上线。这三样,是商业世界的通用语言。
- 把质量属性绑到业务指标上。 别说可用性要四个 9,要说按我们的客单价,挂一小时大约损失 Y 万,所以值得为可用性投入 Z。
- 明确区分底线和优化项。 安全合规、资金正确性通常是不可谈判的底线;延迟从 200ms 优化到 100ms,可能只是锦上添花。别把所有事都说得同样紧急,否则你会失去信誉。
一个好架构师,有一半的功夫在白板上,另一半在会议室里。 你不仅要做对取舍,还要能把取舍讲清楚,让掏钱的人和定需求的人,和你一起做出明智的选择。
📌 真实案例:几个 9 到底值多少钱
- 持久性:AWS S3 设计为 11 个 9,也就是 99.999999999% 的持久性——直观说,存 1000 万个对象,大约要 1 万年才可能丢一个。代价是数据跨至少 3 个可用区冗余。(S3 FAQ)
- 可用性:Google SRE 用错误预算把它讲透了——既然 100% 不可能,就定一个 SLO,比如 99.9%,剩下的 0.1% 就是错误预算;用完了就停止上新功能、专心搞稳定。它把可用性从玄学变成了可管理的预算。(Google SRE Book)
印证本章那句:每多一个 9,成本数量级上涨——所以要回到业务问,这个系统真的需要那么多 9 吗。
🎯 随堂检验
- A答应下来,技术上总有办法全都要
- B把冲突摊开:每多一个 9 成本数量级上涨,强一致与全球低延迟/高可用根本性冲突——请业务排优先级
- C私下只做 99.9%,反正用户感觉不到差别
本章小结
- 质量属性决定系统好不好,而它们几乎全是取舍——这才是架构的主战场。评估每个属性,都要问:怎么度量、怎么实现、和谁冲突。
- 七个核心属性:
- 性能:分延迟 vs 吞吐,看 P99 别看平均,感知延迟常比真实延迟更重要。
- 可用性:用几个 9 度量,对应每年停机多久;靠冗余 + 消灭单点 + 故障域隔离实现,每多一个 9 成本数量级上涨。
- 可扩展性:垂直扩展治标、有天花板;水平扩展治本,但要求无状态。
- 一致性:承接 05,强一致很贵,和扩展/可用/性能根本性冲突。
- 安全性:不信任输入、最小权限、纵深防御;是底线不是优化项。
- 可维护性 / 可演进性:最易被忽视却影响最深远,因为软件总在被改。
- 成本:最常被忽视,却常常是真正的约束;你为每个属性买的单,最后都记在这张账上。
- 核心真相:这些属性互相冲突,你不可能全都要。典型冲突包括一致 vs 可用、性能 vs 成本、灵活 vs 简单。破局之道永远是:回到业务,排优先级——没有最好的架构,只有最合适的。
- 谈取舍的功夫:把技术选择翻译成钱、风险、上线时间;永远给选项 + 代价而非结论,让业务方在知情下决策。
承上启下:到这里,第二段:掌握工具箱,也就是 04 模式 / 05 数据 / 06 取舍就讲完了——你手里有牌了,懂数据这块硬骨头了,也明白了万事皆取舍。接下来进入第三段:实战与演进。07 · 从 0 到 1 设计一个系统 会给你一套照着做就能产出架构方案的方法论,把前六章学到的判断力,真正用起来。
💬 评论