EP19:压力测试方法论:从单机到全球压测¶
| 上集回顾 | EP18《录像与回放:给直播加上 VOD》 |
|---|---|
| 下集预告 | EP20《省 30% 带宽是否可行:真实数据复盘》(全系列收官) |
0. 本集目标¶
看完这一集,观众应该能做到四件事:
- 理解测试是分层的:单元测试、黑盒集成测试(本地一次性实例 / 针对线上真实部署的生产回归)、单机单流时延实测、单机容量压测、真实网络验收、全球容量压测——每一层回答的是不同的问题,不能互相替代。
- 看懂怎么量"端到端时延":打点、时钟校准、跨浏览器、样本量与误差,以及"一次实测"和"全网 SLA"的区别。
- 理解为什么有些东西 mock 不出来:NAT 多样性、竞速真实时序、失败兜底、真实供应商网络限速,只能在真实网络里试。
- 知道这套系统在测试阶梯上爬到了哪一级:时延实测完整;黑盒测试现在有两个档次(本地一次性二进制覆盖录像链路,针对线上
api.pp-cdn.org/record.pp-cdn.org的生产回归覆盖 Play/NAT/records/split-rec 的 HTTP 契约);自建 standalone 节点的单机容量已经有两份真实压测报告。但全球容量压测还没做,平台自身 Origin/Record/Edge 的mmxNodeCapacity(EP15 讲过)仍是待校准项——这两件事不能混为一谈。
1. 开场钩子(逐字稿要点)¶
前面十八集,我们把系统从架构讲到部署、再讲到录像回放。这一集做一件最容易被跳过、却最容易翻车的事:测试。很多人对"测试通过"的理解是"
go test ./...全绿",但真实世界里,单元测试全绿和"能扛住生产流量"之间隔着好几级台阶。这一集把这几级台阶从下往上走一遍——从单机单流,一直讲到(还没爬到的)全球压测。我会尽量把每一级"回答什么问题、以及不回答什么问题"都说清楚,因为测试最大的陷阱,是用一个层级的通过去证明另一个层级的结论。
2. 测试是一场分层的攀登¶
先用一张表说清楚每一层测什么、不测什么:
| 层级 | 代表做法 | 回答的问题 | 不回答的问题 |
|---|---|---|---|
| 单元测试 | go test ./...、go vet ./... |
逻辑对不对、边界处理对不对 | 真实网络下能不能工作 |
| 黑盒集成测试(本地一次性实例) | integrationTest/record:编译真实 mmx 二进制、真实 ffmpeg 推流 |
协议/接口契约对不对 | 性能上限、容量 |
| 黑盒生产回归测试 | integrationTest/api:对线上真实部署发真实 HTTPS 请求 |
生产 API 的鉴权/校验/路由契约是否还对得上文档 | 媒体是否真的建立、播放质量 |
| 单机单流时延实测 | 一条真实流、限定网络场景 | 端到端时延的量级与构成 | 并发规模、全网 SLA |
| 单机容量压测 | 对一台真实节点做并发阶梯加压 | 这台机器的并发天花板在哪、退化是线性还是悬崖 | 多机/多区域规模下的容量 |
| 真实网络验收 | 两个真正独立的 NAT 环境 | NAT 穿透、竞速、失败兜底 | 规模化容量 |
| 全球容量压测 | 多区域、多并发、峰值流量 | 系统容量上限、瓶颈在哪 | (这一层目前还没做) |
核心原则:在哪一层测,就只能得到那一层的结论。单元测试全绿,证明的是"代码逻辑自洽",不是"生产能扛住"——这句话在很多项目里被反复说,但真正做的时候还是容易忘。
3. 怎么测"端到端时延":口径与原理¶
时延是这门课从头到尾的主轴,那它是怎么被测出来的?这套系统沉淀了一套可复现、可对外公开的方法:
① 在码流里打绝对时间戳。 ppobs 采集时在码流内打入绝对 UTC 时间戳(H.264/H.265 的 SEI),Origin/Edge 逐跳透传,不解码、不转码——所以打点本身不破坏"网络侧不转码"这条架构原则(EP05/EP10 讲过)。
② 播放端做应用层时钟校准。 ppplayer 无法访问系统级 NTP,所以它向 ppcenter 做一次应用层的时钟校准(类似 NTP 的 offset 估算),把本地时钟校正后再和码流里的时间戳相减:
corrected_now = local_clock + offset
one_way_delay = corrected_now - embedded_timestamp
③ 不用固定常数掩盖不可观测段。 打点之后到真正显示之间若无法直接测量,应分别埋点或报告"测量边界",并通过高速摄影/外部基准校验。把所有读数机械加同一个补偿会引入系统误差,不同编码器、设备和渲染路径也不会共享一个常数。
④ 跨浏览器可测。 iOS Safari 不支持 WebCodecs Insertable Streams,读不了码流里的 SEI——为了让这些设备也能测,Edge 节点会自己解析入库流的 SEI,通过 ABR 控制 WebSocket 把 OBS_TIMESTAMP 下发给播放器(复用播放器早就为分层切换持有的那条连接)。
⑤ 单次读数 → 带样本信息的分布。 播放器上报后按场景、codec、设备和网络分组,报告样本量 n、测试时段、P50/P80/P95/P99,并尽可能给置信区间或重复试验离散度。百分位没有样本量就无法判断稳定性,跨场景混合聚合也会制造误导。
这套方法测出来的结果(调参后,见《直播时延性能测试报告》ppcenter/docs/test/live-latency-performance-test-report.zh-CN.md):
| 路径 | 实测时延 |
|---|---|
| P2P 直连 | 约 70ms |
| WHIP 入库(Edge 分发) | 约 108ms |
| SRT 入库(Edge 分发) | 约 386ms |
这组数据证明在当次真实测试条件下,P2P 直连链路最短、时延最低;走 Edge 分发时,入库协议是主要变量——WHIP 入库没有固定接收窗口,SRT 为了弱网丢包重传特意放大了入库端的 TSBPD 接收窗口,这是协议层"丢包鲁棒性 ↔ 时延"的有意取舍,不是实现缺陷。特别要注意这里对比的变量是入库协议(WHIP vs SRT),不是编解码器(HEVC vs H264)——如果需要比较 codec 本身对时延的影响,必须固定入库协议、只切换 codec 做受控实验,这份报告没有覆盖这个问题,不能拿协议差异的数字去回答 codec 差异的问题,这正是下一集要反复强调的"别把两个不同的轴混在一起"。这些数字本身仍是近似点估计,不足以仅凭三个数字断言协议差异必然造成全部观测值;报告还必须给出每组样本量、采样时段、设备、编码参数、网络条件、时钟同步误差和分位数;样本不足时只应称"本次观测",不代表全网 SLA。
4. 黑盒集成测试:对着真实产物测,分两个档次¶
单机实测之前,还有一层比单元测试更"真"、但又比真实部署更可控的测试:黑盒集成测试。这套系统里它其实分成两个档次,"真实"的程度不一样。
4.1 对着本地一次性真实二进制测——integrationTest/record¶
以录像链路为例(EP18 讲过),这套测试做的是:
- 编译真实的 mmx 二进制(不是进程内的 mock),启动真实进程;
- 用真实的 ffmpeg RTMP 推流触发 fMP4 分片真正落盘;
- 通过真实 HTTP 覆盖
/v3/recordings/*和 split-rec 两阶段协议、鉴权失败分支; - 再拿 API 的返回和磁盘上真实写出的文件交叉核对。
它的价值在于:一个"在进程内跑"的单元测试,永远证明不了"真实二进制 + 真实编码器 + 真实网络栈"这条链路是通的。而黑盒测试把整条管道真正拼起来跑一遍。
它有几条铁律,值得所有做集成测试的人借鉴:
- 一次性、隔离:每次运行自己编译、自己起进程、自己用临时目录,测试产物和密钥每次新生成。
- 绝不碰生产:
SPLIT_REC_SECRET是每次随机生成的,deletesegment用例只删自己创建的文件。测试可以真删真写,但只在自己的一次性沙盒里。 - 知道自己的"不覆盖"清单:这套测试只覆盖 record 角色,而且本地没有对象存储,所以它只验证"上传不阻塞响应",不验证真实上传成功。把"不覆盖什么"写清楚,比多写几条用例更重要。
4.2 对着真实线上部署测——integrationTest/api¶
这套系统还有第二个档次的黑盒测试,目标更"真":不编译、不起本地进程,直接对线上真实部署的 https://api.pp-cdn.org(ppcenter)和 https://record.pp-cdn.org(mmx-recorder)发真实 HTTPS 请求,覆盖 Play API(/v1/play/requests,就是 EP13 讲过的 Edge/P2P 选路入口)、NAT 探测(/v1/nat/probe)、录像回合/清单查询(/v1/records/*)和 split-rec 两阶段协议。它用一个专门注册的测试账号(凭证放在只在本机的 .env,不进源码库),覆盖鉴权失败、字段校验、过期 token、跨 app 冒用等负向用例,正向用例也写得足够诚实——例如 Play API 的 happy-path 测试同时接受 200(刚好有健康 Edge)、503 no_edge_available(刚好没有可用 Edge)、402 account_in_arrears(测试账号刚好欠费)三种结果,因为这套测试不控制"此刻是否真的有 Edge 可用",一个诚实的"没有容量"也证明鉴权+校验那一层工作正常,不该算测试失败。
这个档次不是摆设:生产部署记录(docs/deployment/ppcdn生产部署总结.md)里能看到它被当成每次 ppmmx/ppcenter 发布后的真实回归用例反复跑,最近几次记录是 35 PASS / 0 FAIL / 5 SKIP(SKIP 全是环境依赖项,如 manifest 后端暂不可达)。它也不是只用来确认"没坏"——跑的过程中曾经揪出过协议升级后遗留的测试代码漂移(split-rec 的签名密钥来源,测试代码一直没跟着"不再用部署级共享密钥"这条协议升级走)和一处真实的生产 nginx 路由缺口,这些发现和修复过程都记在部署总结文档里,是"跑真实环境才发现的问题"的又一个例子。
但它仍然只测HTTP 控制面契约:Play API 返回"用 WHEP 去连哪个 Edge"的 URL 和 mode(edge-only/p2p-connect),这套测试只验证这个返回本身对不对;它不会真的去建一条 WebRTC/WHEP 连接看视频画面是否正常——origin/edge 真实媒体链路的自动化端到端测试,仍然是空的(见第 7 节)。
5. 真实网络验收:mock 不出来的东西¶
有些东西,再好的单元测试和集成测试都 mock 不出来,只能上真实网络。P2P 已有一次真实跑通约 70ms,但一次成功不能覆盖 NAT 多样性和失败分布,因此仍需要系统化验收:
- 先证基线连通性:用最容易满足准入的组合(一端公网 IP)先确认 offer/answer/ICE/媒体整条链路是通的,排除"根本没走到 P2P"这类底层问题。
- 再造真实 NAT 组合:两端各自过一层真实的运营商/路由 NAT,要求两边不是同一个 NAT 设备。这是 mock 永远的盲区——你可以 mock 出"对称 NAT"这个类型,但 mock 不出真实 NAT 设备在真实运营商网络下的行为。
- 验证"拒绝也要无感":决定不做 TURN 之后(EP13),"该拒绝的组合是否被干净拒绝、而且用户完全无感"就成了功能能否上线的关键。这一条验收的不是"能不能识别出 CGNAT"(第 4.2 节的
/v1/nat/probe请求/响应契约已经有黑盒生产回归覆盖),而是"识别出来之后观众是不是完全没有感觉"——这是契约测试覆盖不到的体验层面。 - 独立的媒体证据:在 Edge 节点上看这条流的会话字节数——P2P 建立后应该趋于零。这是独立证据,证明"媒体真的没走 Edge",而不是"ppcenter 汇报了 p2p-connect 但实际还在 Edge 兜底"。
- 把探索性场景和 pass/fail 分开:像"P2P 失败后顺序回退 Edge 的真实耗时分布"这类(当前是服务端按编码格式提前分支 + 顺序回退,不是并行竞速,见 EP13/EP09),验收目标是拿到一组真实数据,而不是判定通过与否——因为前者用来校准参数(比如 5 秒握手超时、首帧宽限期设得对不对),不是通过/失败。
- 证据留档:每次都留
chrome://webrtc-internals的截图、/v1/play/requests的完整响应、ppcenter 侧当次的准入判定 reason。没有证据的"测过了"等于没测。
一句话:真实网络验收的特点是——慢、靠人、但不可替代。
6. 单机容量压测:同一套方法、两台机器,退化都是悬崖不是渐变¶
前面几节测的是"协议/接口对不对"和"时延量级",都是单流视角。还有一层问题前面都没回答——一台真实机器能同时扛住多少路,天花板垮的时候是什么样子?
这跟 EP15 留下的"单机 capacity 取值偏乐观、待压测校准"那条待办是同一类问题,但不是同一件事:EP15 说的是平台自己的 Origin/Record/Edge 三角色节点池,mmxNodeCapacity 到今天仍然没有被真实压测校准过;这里说的是另一类正在跑的、面向自建 standalone 节点(licenseCode 自托管客户用的单机单角色部署,EP17 讲过)的压测。方法是同一套:用 whep-loadgen 对一台真实节点做并发阶梯加压,边加边看 CPU/内存/断连/丢包。目前有两份报告,机型不同,数字不能混着引用:
2 vCPU / 4GB(LightNode,马尼拉,第一轮,SRT 单轨 H264,benchmark/report/ppmmx-standalone-stress-test.zh-CN.md):12→25→50 路全部稳定(CPU 均值 26%→63%),75 路直接失败:CPU 冲到 134%/177%(2 核机器),B2 分片建连超时,服务端对慢读者丢帧。但这份报告自己的第 7 节又把这个结论推翻了一半——换到另一家云厂商(AWS Lightsail,同机型 2vCPU/2GB)重测,同样的阶梯干净做到 250 路、300 路才因内存耗尽失败。对照之下才发现:第一家提供商的真实出口带宽实测只有约 50~106Mbps(标称 1Gbps 从未达到),才是 50→75 那次崩溃的真正瓶颈,不是这台机器的 CPU 上限。
1 vCPU / 2GB(LightNode,另一台实例,同样的方法,只换机型,2026-10-08,docs/test/ppmmx-standalone-1vcpu-stress-test.zh-CN.md):18 路以内全程稳定(CPU≤53%),第 19 路一上,CPU 在约 15 秒内从约 40% 冲到 88~103% 并稳定在高位,SRT 入口 RTT 从约 4ms 飙到 100~500ms——这次加压端和被测机近似同机房(RTT~4ms),测的确实是机器本身的天花板,这次先撞到的是 CPU,不是内存(和之前"小规格节点先撞内存"的结论不同,因为这次砍掉了一整个核)。
两份报告放在一起,能看出三条跟单元测试无关、但同样重要的教训:
- 退化是悬崖,不是渐变。两种机型都在某一档之后,几秒到十几秒内从"健康"冲到"过载",中间几乎没有过渡带——10→18 路 CPU 平缓爬升(约 2~3%/路),18→19 路直接跳到 88~103%,按前面的斜率外推应该只到 48~55% 左右。
- 不能跨机型线性外推。按 2vCPU 机器"CPU% ≈ 15 + 0.97%×并发数"的斜率去对半估算 1vCPU 的容量,会得出约 35 路的乐观值,但实测只有 18 路——单核机器没有第二个核心兜底 GC、网络轮询和突发处理,悬崖来得早得多、也窄得多。
- "是不是机器的锅"必须换个环境才能确认。同一台 2vCPU 机器,换一家网络给得起的云厂商,承载能力从 50 跳到 250——说明原先"75 路崩溃"测的根本不是 CPU,而是供应商的出口限速。一次压测的失败结论,如果不换个环境复测,很容易把"供应商的隐藏限速"误判成"机器的真实天花板"。
这次压测还有一个意外收获,呼应第 4 节的主题——有些 bug 只有在真实环境里才会暴露:1vCPU 那次压测过程中顺带发现并修复了两个影响所有 licenseCode 自建部署客户的真实问题——NodeRegisterAck 原来从不把 nodeSecret 回传给仅用 licenseCode 注册的节点,导致这类节点永远拿不到凭证、发布白名单永远空,任何 appId 都推不进流(已修复并部署生产);以及默认 bridge 网络部署下,WebRTC ICE 候选地址采集到的是容器内部 IP 而不是公网 IP,导致 WHEP 播放连不上(已记录为已知问题,暂未改代码,当次用手动配置项绕过)。这两个 bug 不是压测要拿到的"数据点"本身,却是压测过程里最有价值的产出——再一次印证第 4、5 节的论点:有些东西只有在真实二进制、真实网络条件下跑起来才会暴露,单元测试和小规模 mock 都不会把它们逼出来。
7. 诚实的现状:阶梯只爬了一半¶
这一集的立场是:诚实标出每一级台阶爬到了哪、哪级还是空的。
- 做完了的:单元测试(
go test/go vet全绿);单机单流的时延实测(那份报告完整、可复现);黑盒集成测试覆盖录像链路(本地一次性二进制,§4.1);黑盒生产回归测试覆盖 Play/NAT/records/split-rec 的 HTTP 契约(针对线上真实部署,§4.2);自建 standalone 节点的单机容量压测(两份真实报告,§6)。 - 做了一半的:P2P 已真实建立连接并观测到约 70ms,但跨运营商、跨 NAT 类型、不同设备的样本量和误差分析仍不足,不能据此形成命中率或 SLA。
- 还是空的:
- origin/edge 真实媒体链路(WebRTC 建连 + 播放质量)没有自动化端到端测试——§4.2 的生产回归只验证了 Play API 返回的 URL/mode 对不对,不会真的拿这个 URL 去建一条 WHEP 连接看画面;
- 没有全球容量压测;
- 平台自身 Origin/Record/Edge 的
mmxNodeCapacity(EP15)仍待压测校准——这跟 §6 自建 standalone 节点的压测是两件不同的事,不能把后者的结果拿来证明前者已经校准。
结论必须直白地说:不能仅凭单元测试结果推导生产容量。这不是谦虚,是工程事实——容量是真实流打出来的,不是代码推出来的。
8. 可以带走的测试原则¶
把这一集归纳成七条原则:
- 在正确的层级测正确的问题。别用单元测试的绿去证明生产的容量。
- 对着真实产物测。编译真二进制、跑真推流,比进程内 mock 真得多。
- 隔离、一次性、不碰生产。测试可以真写真删,但只在自己的沙盒里。
- 写清楚"不覆盖"清单。知道自己测不到什么,比多测几条更重要。
- 区分"通过/不通过"和"取数据"。校准参数类的测试,目标是数据分布,不是二值判定。
- 压测要逼近真实。NAT 多样性、地理分布、峰值形态——mock 越完美,离真实越远。
- 怀疑你的测试环境本身。同一个"失败"结论,换一家云厂商、换一台机器,很可能完全不成立——下结论前先问一句"我测的是机器本身,还是环境的隐藏限制"。
9. 框图¶
9.1 测试阶梯:爬得越高,越接近真实,也越慢越贵¶
全球容量压测 ← 还没做(多区域 / 多并发 / 峰值)
▲
真实网络验收 ← 方案完整,缺真实环境执行(NAT 多样性、竞速、兜底)
▲
单机容量压测 ← 已做(standalone 节点,1vCPU/2vCPU 两份报告;悬崖式退化)
▲
单机单流时延实测 ← P2P约70 / WHIP入库约108 / SRT入库约386ms;样本与误差待扩充
▲
黑盒生产回归测试 ← 已做(真实 HTTPS 打生产 API;只验证契约,不验证真实媒体)
▲
黑盒集成测试(本地) ← 已做(真二进制 + 真推流;只覆盖 record)
▲
单元测试 ← 已做(逻辑正确性;不能推导容量)
9.2 端到端时延的测量链路¶
ppobs 采集 ── 打入绝对 UTC 时间戳(SEI)
│
▼
Origin / Edge ── 逐跳透传,不解码不转码
│ │
│ └─ Edge 解析 SEI,经 ABR WS 下发 OBS_TIMESTAMP(给不支持 WebCodecs 的浏览器)
▼
ppplayer ── 应用层时钟校准(对齐 ppcenter)→ 相减
│
▼
上报 ppcenter ── 按场景分组,记录 n、P50/P80/P95/P99 与误差信息
10. 结尾与下集预告(逐字稿要点)¶
这一集把测试从下往上走了一遍:真实 P2P 直连已跑通约 70ms;走 Edge 分发时,WHIP 入库约 108ms、SRT 入库约 386ms(差值来自入库协议,不是编解码器)。黑盒测试现在有两个档次——本地一次性二进制(覆盖录像链路)和针对线上真实部署的生产回归(覆盖 Play/NAT/records/split-rec 的契约,但摸不到真实媒体链路)。自建 standalone 节点的单机容量也有了两份真实压测报告,共同的教训是:退化是悬崖不是渐变,而且不能跨机型线性外推。单元测试全绿不代表生产容量,一次真实跑通也不代表全网命中率或 SLA;全球容量压测仍是空白,平台自身 Origin/Edge 的
capacity也还没有被压测校准。下一集是这门课的最后一集。我们回到最初的那个商业问题:"省 30% 带宽"这句宣传语,到底有没有真实数据支撑? 我们会把哪些是实测的、哪些是建模的、哪些还没验证的,一条条摊开来讲。