专业书籍精读 · 持续交付 · 第 9 章
Continuous Delivery · Ch 9 · Jez Humble & David Farley · 2010
抢演唱会门票的那一刻,App 转圈、然后告诉你「系统繁忙」。功能其实都写好了——按钮在、订单流程通——它只是扛不住那么多人同时来。《持续交付》第 9 章讲的就是这类要求:够快、扛得住、不被黑。怎么把它们提前变成机器每天能自动检查的事,而不是上线那天才知道。
盖房子有两种要求。一种是有几间房、厨房能不能做饭——一看就知道做没做到。另一种是楼板能站多少人、隔音好不好、着火能撑多久。第二种不是装修时再补的,它在打地基那天就被决定了;而且验收时你得真的把一百个人请上楼板站一站,光看图纸没用。这一章讲的,就是软件里第二种要求怎么验。
难点不在测,在于这类要求通常写成一句没法判对错的话:「系统要快」「要能支撑大量用户」。没人能说清做到没有,于是它被推到最后——上线前一周找几个人点一点,或者干脆等真实用户来测。等到那时才发现扛不住,问题往往出在最底下的结构里,改起来等于翻修地基。
这一章的办法就两步,都很朴素。
第一步,把话说死。「要快」改写成「一千个人同时下单时,九成五的人能在 0.8 秒内看到结果,连续一小时不掉队」。这样它就从愿望变成了一道能判对错的题。
第二步,把现成的脚本复制一万份。 上一章(自动化验收测试)已经写好了「机器自己走一遍下单流程」的脚本。压测不用另起炉灶:把同一批脚本同时跑上几千份,看系统在哪一步开始变慢、在哪一步塌掉。机器每天自动跑、不过关就亮红灯——和跑普通测试没有本质区别。
排练场不是真场地。 公司很少舍得为压测再配一整套和生产一样的机器,于是拿一台小机器测,再乘以三十。可小机器上数据只有十万条、生产是十亿条;小机器上什么都在缓存里、生产上要去翻硬盘。这个乘法几乎总是错的——书里的态度很实在:环境越像生产,数字才越可信;不像的时候,只能拿它看「这次比上次慢了吗」,别拿它承诺「我们能扛住多少」。
代价也说清楚:这类测试又慢又贵,一轮几十分钟起、还要占一整套机器,所以它不该卡在每个人每次提交的路上。
把「够快」「扛得住」写成带数字、能判对错的一句话,复用已有的测试脚本把它做成流水线里一道会亮红灯的关卡;同时记住,缩水的测试环境只能看趋势,不能当承诺。
想看容量拐点曲线、注入器架构和环境失真的对比表? → 切到精读版
你以为性能测试是上线前的一场彩排;第 9 章说它该是流水线里一个会亮红灯的阶段。核心命题:把容量、性能、安全这些「-ility」从形容词变成带阈值、可自动判定的验收条件,复用已有的验收测试脚本把它们做成一道关卡。作者甚至反对「非功能需求」这个名字——它们不是可选的边角料,而是横切所有功能的跨职能需求(cross-functional requirements)。
p95 就是第 95 快的那个——95% 的请求比它更快。本章属 Part II「部署流水线」,位置在 Ch8「自动化验收测试」之后、Ch10「部署与发布」之前:Ch8 教你把「功能对不对」自动化,本章教你把「够不够快、扛不扛得住、安不安全」自动化,用的还是同一批脚本。 它上承 Ch4「测试策略」里那个象限——面向技术、用于评价产品的测试;对应现实里的问题是:流水线上那个被所有人跳过的「性能测试阶段」,为什么总是形同虚设。
这类需求有两种死法,方向相反。
一种是不测。 需求写成「系统要快」「要支撑大量用户」——没有数字就没有判据,于是它永远排在功能后面,最后由真实用户来测。另一种是过度工程:为想象中的一百倍流量提前上分库分表、上消息队列、上缓存三层,用交付速度换一个从未到来的峰值。书的立场是中间那条:跨职能需求要在立项阶段就和功能需求一起分析、排优先级、写进待办清单,然后用测试把它钉住。
不解决的代价是发现得太晚。举个有量级的例子:一个 2000 万注册、日活 200 万的电商,人均每日 30 次请求 ≈ 700 QPS 均值,大促峰值按 8 倍算 ≈ 5600 QPS。上线后压不住,一查是商品页每次渲染触发 41 次数据库查询(一次列表 + 40 次逐条取详情)。这不是「调个参数」能解决的——它是数据访问方式的问题,而数据访问方式往往在第一周就定型了。容量问题几乎总是架构问题,越晚发现,改动半径越大。
本章最实操的一条:每个跨职能需求都要落成「场景 + 度量 + 阈值」三件套。「系统要快」写成:在 1000 并发用户、商品库 500 万条的条件下,下单接口 p95 < 800ms、错误率 < 0.1%,持续 60 分钟。 三个要素缺一不可——没有场景,数字无意义;没有阈值,机器判不了红绿。
这里有个容易被跳过的判据:阈值必须来自业务,不是来自「上次测出来是多少」。否则它会随系统一起退化——每次超了就把阈值往上抬,关卡就变成了橡皮图章。
这是本章最值钱的一页概念。三者不是同义词,关系藏在一条曲线里:随着并发上升,吞吐量先近似线性增长,到某个点后被资源(CPU、连接池、锁)顶住而走平,再往上甚至回落;而响应时间在同一个点开始陡峭上翘。 容量就定义在这个拐点上——在响应时间仍不越线的前提下能吃下的最大吞吐量。
为什么这个区分要紧:饱和区之后,你多发的请求不会变成吞吐,只会变成排队。 一个已经在拐点右侧的系统,表面看 CPU 打满、吞吐还在、其实每个用户都在等——这正是「服务没挂但没人用得了」的成因。
书把源头指向代码与架构里的几类常见做法,每一类都值得当成 review 时的检查项:
p99 一看一个准。本章最省力的一招:别为压测另写一套脚本,把 Ch8 的验收测试拿来放大。 验收测试已经表达了「一个真实用户会怎么用系统」,压测需要的只是「同时来一万个」。
但有个关键限定:录制要录在服务 / API 层,不要录在 UI 层。 原因有二——UI 脚本一改按钮就碎,维护成本吃掉整个收益;而且浏览器本身很重,用 UI 驱动压测,你的注入器先扛不住。这条今天仍然成立,只是名字变了:我们叫它「协议级压测」。
书承认现实:很少有团队养得起一套和生产完全同构的压测环境。 但它同时警告,缩比环境的数字不能线性外推。失真来自几处,每一处都会往「过于乐观」的方向偏:
务实的结论:缩比环境是「回归检测器」,不是「容量预测器」。它擅长回答「这次比上次慢了吗」,不擅长回答「我们能扛多少 QPS」。后一个问题只有同构环境或生产本身答得了。
本章把安全和容量并列,理由一致:都是不写成自动化关卡就会被无限期推迟的东西。 落到流水线上是三件可自动化的事——静态分析(每次提交就跑,扫 SQL 注入、硬编码密钥这类模式)、依赖扫描(查已知漏洞的库版本)、渗透测试(人工为主、周期性做)。判据和容量测试一样:能给出明确红绿的,放进流水线;只能给出报告的,别假装它是关卡。
第一个选择:拿什么环境测。这决定了你的数字能承诺什么。
| 方案 | 能回答什么 | 代价与风险 | 适用 |
|---|---|---|---|
| 生产同构环境 | 绝对容量:能扛多少 QPS、崩溃点在哪 | 机器成本近乎翻倍;数据还得定期脱敏同步 | 金融、大促前、SLA 有对外承诺 |
| 缩比环境 | 相对趋势:这次比上次慢了吗 | 便宜,但数字不可外推,误当承诺会翻车 | 日常回归,绝大多数团队的默认 |
| 生产内引流 / 影子流量 | 最真实:真实数据、真实依赖、真实抖动 | 可能影响真实用户;写操作要隔离;需要完善的熔断与回退 | 有成熟可观测性与自动护栏的团队 |
书写于 2010 年,默认第二行;第三行当时被视为不可接受,今天反而是大厂主流——见下一节。
第二个选择:用什么驱动负载。
| 方式 | 优点 | 代价 |
|---|---|---|
| UI 录制回放 | 贴近真实用户路径,上手快 | 脚本极脆(改个按钮就碎);浏览器太重,注入器先成瓶颈 |
| 服务 / API 层脚本 | 本章推荐:稳定、轻量、可复用验收测试 | 绕过前端,测不到浏览器端渲染与资源加载 |
| 生产流量回放 | 负载形状最真实,连长尾请求都在 | 需要采集与脱敏管线;回放写操作要额外隔离 |
第三个选择:测哪一种——四类测试抓的是不同的 bug,别混为一谈:负载测试问「预期峰值下达标吗」;压力测试问「超载后在哪崩、是优雅降级还是雪崩」;浸泡测试(跑 8–24 小时)抓内存泄漏、连接泄漏、日志撑爆磁盘;尖峰测试抓冷缓存和自动扩容的滞后。只跑第一种是最常见的偷懒——而真正打垮系统的通常是后三种。
这一章的主张今天已经成了默认配置,只是换了词:我们说 SLO 而不说「阈值」,说「性能测试即代码」而不说「复用验收测试」,说「协议级压测」而不说「录在服务层」。本章点名的 JMeter 至今仍在 Apache 基金会维护,而它的继任者们把「进流水线」这件事直接写进了产品定位。
大厂实证
另一条值得记住的实证是指标口径:Amazon 在 Dynamo 论文(SOSP 2007)中明确主张内部服务的 SLA 按 p99.9 而非平均值来定——因为平均值会把最慢的那批用户(往往正是买得最多、数据最多的那批)整个藏起来。
p95 / p99,并同时看错误率——只看时间,会把「快速失败」当成性能优异。① 核心命题:把容量、性能、安全变成带阈值、能自动判红绿的关卡,而不是上线前的一次性彩排。
② 作者主张改名跨职能需求:它们横切所有功能,该在立项阶段就和功能需求一起排优先级。
③ 需求要写成场景 + 度量 + 阈值三件套;阈值来自业务,不能因为测不过就往上抬。
④ 容量 = 响应时间仍在阈值内时的最大吞吐量;吞吐走平与响应时间起飞发生在同一个拐点。
⑤ 容量问题的常见源头:话太多的远程调用、N+1 查询、全局锁、会话状态、GC 停顿——都是架构级问题,越晚发现越贵。
⑥ 最省力的做法:复用验收测试脚本放大,并且录在服务 / API 层而非 UI 层。
⑦ 缩比环境是回归检测器而非容量预测器:数据量、缓存命中率、共享资源、桩,四种失真全都朝乐观偏。
⑧ 四类测试抓不同的 bug:负载、压力、浸泡、尖峰——只跑负载测试是最常见的偷懒。
⑨ 阈值用 p95 / p99 并同时看错误率;平均值会把最慢的那批用户藏起来(Amazon Dynamo 论文按 p99.9 定 SLA)。
⑩ 落到今天:SLO、性能测试即代码(k6)、生产流量回放(Iago)都是本章的直系后代;唯一被推翻的是「不能在生产上测」。