Skip to content

06 · 质量属性与取舍

一句话点题:功能决定系统「能不能用」,质量属性决定系统「好不好用、扛不扛得住、值不值得」。而这些属性彼此冲突,你不可能全都要——架构的本质,就是按业务排好优先级,然后清醒地做取舍。


先接上 02:质量属性是什么,为什么它才是架构的主战场

02 · 架构师的思考框架 里,我们把需求拆成了两类:

  • 功能性需求:系统要做什么(能下单、能搜索、能发消息)。
  • 非功能性需求 / 质量属性:系统要把这些事做得多好(多快、多稳、多省、多安全)。

新人盯着功能,架构师盯着质量属性。原因很简单:功能往往有标准答案(要个购物车,大家做出来都差不多),而质量属性几乎全是取舍,没有标准答案——它才是真正考验判断力的地方。

这一章,我们把 02 里列过的质量属性清单逐个展开。每一个,我都讲清三件事:

① 怎么度量(说不出数字的目标都是空话) ② 怎么实现(常用哪些架构手段) ③ 和谁冲突(它不是免费的,得罪了谁)

第三点是灵魂。因为本章最核心的真相是:这些属性互相打架。 把任何一个拉满,几乎必然伤害另一个。


一、性能(Performance)

① 怎么度量 —— 先分清两个常被混为一谈的指标:

  • 延迟(Latency):单个请求从发出到拿到结果有多快。关心的是「一次操作要等多久」。
  • 吞吐(Throughput):单位时间能处理多少请求(如 QPS)。关心的是「一秒能服务多少人」。

这俩不是一回事,甚至常常对立。一个直觉类比:

   延迟 = 一辆车从北京到上海要多久(关心单程时间)
   吞吐 = 这条高速公路每小时能通过多少辆车(关心总流量)

   修一条限速200的赛道:延迟极低(单车飞快),但只有一条道,吞吐低
   修一条限速80的十车道:单车不快(延迟高),但车流巨大,吞吐高

度量延迟时,千万别只看平均值。平均延迟 100ms 听着不错,但可能 99% 的人是 50ms,1% 的人卡了 5 秒——而那 1% 可能正是你最重要的大客户。所以要看百分位(P95、P99、P999):「99% 的请求在多少毫秒内完成」。尾延迟(长尾)往往才是体验杀手。

还有一个常被忽视的维度:感知延迟。 用户感受到的「快」,和真实的总耗时,常常不是一回事。

还记得 AI 对话产品 的流式输出吗?它没让总生成时间变短,但让用户1 秒内就看到第一个字往外蹦,体验天差地别。这就是「感知延迟」的魔力。任何让用户更早看到反馈的设计(流式、骨架屏、乐观更新、进度提示),都在改善感知延迟——而它常常比真实延迟更重要。

② 怎么实现:缓存(把重算/远取变成近读)、读写分离、异步化(把慢活儿丢队列,先返回)、CDN(把内容放到离用户近的地方)、用更好的数据结构/索引、预计算。注意——这些手段你在 0405 都见过了。

③ 和谁冲突:主要和成本(更快往往要更多机器/更贵的存储/更多缓存)、和一致性(缓存、异步换来速度,代价是数据可能不那么新)、和简单性冲突(每一项性能优化都是一份额外的复杂度)。


二、可用性(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,因为这是钱,出一次错赔的比省的多得多。您怎么看?"

几条沟通心法:

  1. 永远给「选项 + 代价」,而不是「结论」。 把 A、B 两条路、各自的「得到什么、放弃什么、花多少钱、多久能上」摆清楚,让业务方在知情下选择。这既尊重了对方的决策权,也保护了你自己(决策是共同做的)。
  2. 用三种货币报价:钱、风险、时间。 几乎任何技术取舍,都能折算成「多花/省多少钱」「出事概率和后果多大」「能早/晚多久上线」。这三样,是商业世界的通用语言。
  3. 把「质量属性」绑到「业务指标」上。 别说「可用性要四个 9」,要说「按我们的客单价,挂一小时大约损失 Y 万,所以值得为可用性投入 Z」。
  4. 明确区分「底线」和「优化项」。 安全合规、资金正确性通常是不可谈判的底线;延迟从 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 吗」。


本章小结

  • 质量属性决定系统「好不好」,而它们几乎全是取舍——这才是架构的主战场。评估每个属性,都要问:怎么度量、怎么实现、和谁冲突。
  • 七个核心属性:
    • 性能:分延迟 vs 吞吐,看 P99 别看平均,感知延迟常比真实延迟更重要
    • 可用性:用「几个 9」度量(对应每年停机多久),靠冗余 + 消灭单点 + 故障域隔离实现,每多一个 9 成本数量级上涨。
    • 可扩展性:垂直扩展(治标、有天花板)vs 水平扩展(治本、但要求无状态)。
    • 一致性:承接 05,强一致很贵,和扩展/可用/性能根本性冲突。
    • 安全性:不信任输入、最小权限、纵深防御;是底线不是优化项。
    • 可维护性 / 可演进性:最易被忽视却影响最深远,因为软件总在被改。
    • 成本:最常被忽视,却常常是真正的约束;你为每个属性买的单,最后都记在这张账上。
  • 核心真相:这些属性互相冲突,你不可能全都要(一致 vs 可用、性能 vs 成本、灵活 vs 简单……)。破局之道永远是:回到业务,排优先级——没有最好的架构,只有最合适的。
  • 谈取舍的功夫:把技术选择翻译成钱、风险、上线时间;永远给「选项 + 代价」而非结论,让业务方在知情下决策。

承上启下:到这里,「第二段:掌握工具箱」(04 模式 / 05 数据 / 06 取舍)就讲完了——你手里有牌了,懂数据这块硬骨头了,也明白了万事皆取舍。接下来进入第三段:实战与演进07 · 从 0 到 1 设计一个系统 会给你一套照着做就能产出架构方案的方法论,把前六章学到的判断力,真正用起来。