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