EP15:横向扩容和低延迟的关系¶
| 上集回顾 | EP14《节点池管理:把资源边界放进调度器》 |
|---|---|
| 下集预告 | EP16《如何应对突发性高并发场景》 |
0. 本集目标¶
看完这一集,观众应该能做到四件事:
- 理解横向扩容的拓扑基础:把 Origin(收流)和 Edge(分发)拆成两种角色,才有"一次推流、N 份扇出"的空间;但代价是对称的——Origin 自己的处理时延会被这个 N 倍放大。
- 分清 Origin → Edge 的两种连法:主动推(push fanout)和按需回源(pull-on-demand)各自的成本模型,以及为什么这条链路最好走同 AZ/同 Region 的私网——这就是"region 组网"。
- 知道跨 region 怎么桥接:ppmmx 参与横向扩容拓扑的四种节点角色(Origin/Forward/Edge/Record),节点之间转发走的是 WHIP 会话,以及"一跳时延"目前是工程预算,不是逐跳实测出来的数字。
- 拿到一份可核实的容量换算:64 vCPU 的新加坡 Region 配额能开多少台 Edge,"峰值并发"在不同口径下分别是多少,以及为什么不能把单机压测的故障点直接乘以台数。
1. 横向扩容的起点:把 Origin 和 Edge 拆成两种角色¶
1.1 一次推流,N 份扇出¶
PPCDN 的媒体链路里,Origin 是媒体数据进入系统的唯一入口,Edge 从 Origin 回源、向观众分发,同一路流在同一台 Edge 上被多个观众共享一条回源链路(EP02 §1.1)。这个拆分带来的直接好处是:扩容观众容量,只需要加 Edge,不需要动推流端——ppobs 从始至终只认一个 Origin 地址,Edge 加到多少台、怎么加,对推流侧完全无感。
flowchart LR
P["ppobs 推流"] --> O["Origin<br/>媒体数据进入系统的唯一入口"]
O --> E1["Edge-1"]
O --> E2["Edge-2"]
O --> E3["Edge-3"]
E1 --> V1["观众群 1"]
E2 --> V2["观众群 2"]
E3 --> V3["观众群 3"]
这正是 EP02 §2.3 讲过的"Edge 层可水平扩展":Edge 按节点池调度、优先导向剩余容量最大的节点,单台 Edge 过载不会拖垮全局(EP14 §1 的"剩余容量最大优先"排序)。横向扩容的第一前提,是先有这个 1 对 N 的拓扑结构,而不是先有更多机器。
1.2 代价对称:Origin 的处理时延会被 N 倍放大¶
好处和代价是一体两面的。所有 N 条下游分支,都要先经过同一个 Origin 才能分叉——Origin 自己的处理环节(接收、转发给 Record/Edge,不重新编码)如果多花 10ms,这 10ms 会被所有下游、所有观众一起继承,不像某一台 Edge 出问题那样只影响落在它上面的那批观众。
这是一条工程判断,不是这个项目哪份文档写死的 SLA 数字。目前没有人专门量过"Origin 这一跳自己的处理时延"——能拿到的是 EP12 记录的端到端基线:P2P 直连约 70ms,Edge 分发下 WHIP 入库约 108ms、SRT 入库约 386ms,这些数字已经包含了采集、编码、网络传播、解码、渲染的全部开销,Origin 自己的转发环节只占其中很小的一段。把 Origin 自身的处理开销压在几十毫秒的量级,是为后面每一层扇出预留的安全余量,是一种设计取向,不是已经被独立测量验证过的结论。
2. Origin → Edge 的两种连法:主动推 vs 按需回源¶
2.1 push fanout:主动推¶
生产配置里,Origin 对外转发走的是 forwardMmxTargets——每个下游目标各开一条独立的 WHIP 会话、独立的 Forwarder,互不影响(docs/deployment/ppcdn-production-deployment-plan.md §二)。deploy/config/mmx-origin.yml 的 paths.all 写的是 source: publisher + sourceOnDemand: false:只要配置里有这个目标,Origin 就一直往那推,不管这台 Edge 当前有没有观众。
好处是路径始终"热着",没有建链等待;代价是每加一台走这条路的 Edge,都要同步去 Origin 的 forwardMmxTargets 里追加一条私有 IP 并 reload,而且从追加的那一刻起,不管有没有观众,Origin 出站就立刻多一条转发会话。
2.2 pull-on-demand:按需回源¶
deploy/config/mmx-edge.yml 的 paths.all 写的是 source: redirect + sourceOnDemand: true:Edge 在观众到来之前,跟 Origin 之间没有任何管线。流程是观众发起 WHEP 播放请求 → 这台 Edge 才去 Origin 拉这一路流 → 同一台 Edge 上同一路流的多个观众共享这一条回源链路。EP14 §2.3 讲的控制台自助建节点,走的正是这条路——Edge 每次回源前向 ppcenter 动态解析 Origin 地址(POST /internal/mmx/v1/origin/resolve),不需要 Origin 预先认识它。
好处是一台没有观众的 Edge,对 Origin 的额外负载接近 0,加 Edge 不需要碰 Origin 配置;代价是冷启动会增加建链和首帧等待。
2.3 两种连法同时存在,分工不同¶
生产里不是"二选一打穿全局",而是按角色分工:预先规划、长期在线的核心 Edge 走 push fanout,接进 forwardMmxTargets 固定清单,换一条始终热着、延迟最可预期的路径;用户自助添加、规模和上线时间都不确定的 Edge,走 pull-on-demand,不用每加一台就去改 Origin。
| 连法 | 配置 | 对 Origin 的即时负载 | 代价 | 典型场景 |
|---|---|---|---|---|
| push fanout(主动推) | source: publishersourceOnDemand: false |
配置即生效,立即占用出站 | 每加一台要改 Origin 配置并 reload | 预先规划的核心 Edge |
| pull-on-demand(按需回源) | source: redirectsourceOnDemand: true |
接近 0,直到有观众落上来 | 冷启动建链 + 首帧等待 | 自助添加、动态扩出来的 Edge |
2.4 为什么这条链路最好走内网:region 组网¶
docs/deployment/ppcdn-production-deployment-plan.md 把 Origin/Record/Edge 刻意摆在同一可用区:同 AZ 内网免费;同 Region 跨 AZ 要按约 $0.01/GB 双向计费;跨 Region 费用更高。拓扑图上标得很清楚——forwardMmx(WHIP 推流)私有 IP · 同 AZ · 免费。把 Origin 和 Edge 摆进同一个 AZ/Region,省的不只是账单,更是把这条承载实时媒体的链路挪出公网,避开公网拥堵带来的抖动——这正是第 1 节说的"把 Origin 这一跳的开销压低"在部署层面的落地。
要说准确:这目前是一条部署层的约定,不是调度器强制的硬边界。ppcenter/internal/manager/node_manager.go 的排序逻辑明确写着"Region is deliberately not consulted: pool members compete purely on free capacity"——池内候选只按剩余容量排序,不看 region。region//admin/region-routes 提供的是一层城市→候选节点的软性优先级(regionRank,只影响候选排序,不参与过滤),真正的隔离边界是 EP14 讲过的 PoolID。把节点摆进哪个 AZ、哪个 Region,今天仍然是运维在建节点时的人工决定。
3. 跨 region:引入 Forward 节点¶
单个 Region 的 Lightsail vCPU 配额是有上限的(第 5 节会算这笔账)。要把流量桥接到另一个 Region 的 Origin-Edge 集群,ppmmx 新增了一种节点角色:NODE_ROLE_FORWARD(ppcenter/pb3/mmx.proto)。
Forward 是一个纯 WHIP 中继:不接收 OBS 直推,不对观众提供播放,数据流是"被动收 + 主动转发"——
- Region A 的 Origin 用既有的
forwardMmx把流推给 Forward 节点(source: publisher,这是被动收的一端); - Forward 节点再用自己的
forwardMmxTargets把整条多层 simulcast 流原样推给 Region B 的 Edge 集群(这是主动转发的一端)。
flowchart LR
subgraph RA["Region A"]
OA["Origin"] -- "push(forwardMmx)" --> FA["Forward 节点<br/>被动收 + 主动转发"]
end
subgraph RB["Region B"]
EB1["Edge"]
EB2["Edge"]
end
FA -- "forwardMmxTargets(push)" --> EB1
FA -- "forwardMmxTargets(push)" --> EB2
两个细节值得记住:第一,Forward 复用的是既有 mmx-to-mmx 转发实现,没有新写一条转发代码路径,本质上只是让 push fanout 多串一跳;第二,Forward 不对自建客户开放创建(EP14 §2.3 的控制台建节点四选一里没有它)——跨 Region 桥接到哪、怎么桥,目前是平台统一规划的内部角色,不交给用户自己配。
老实说现状:
docs/ppcdn-development-progress.zh-CN.md§3.18 把这项工作标注为"已完成(代码与配置),真实跨区域联调待验证"。也就是说 ppmmx/ppcenter 两端的代码、配置、单元测试都已经就位,但目前还没有做过一次真实的跨 Region 联调——这是待验证项,不是已经在生产跑通的能力。
4. ppmmx 的四种横向扩容角色,以及节点间的转发协议¶
4.1 四种角色:Origin / Forward / Edge / Record¶
ppcenter/pb3/mmx.proto 的 NodeRole 枚举实际有 7 个值,但真正参与"跨节点转发、可以横向扩容"这套拓扑的是四种:
| 角色 | 职责 | 回源/下发方式 |
|---|---|---|
NODE_ROLE_ORIGIN |
接收推流(WHIP/SRT/ERTMP),是媒体数据的唯一入口 | 向下游 push(forwardMmxTargets) |
NODE_ROLE_FORWARD |
跨区域桥接:被动收 + 主动转发 | 上游 push 收,下游 push 发 |
NODE_ROLE_EDGE |
从 Origin 回源,向观众提供 WHEP | push 或 pull-on-demand(第 2 节) |
NODE_ROLE_RECORDER |
按场次 split-rec 录像,上传对象存储 | 上游 push 收(source: publisher) |
剩下 3 个枚举值不在本集讨论范围:NODE_ROLE_STANDALONE 是单实例自成一体、不与其它节点组网的角色(EP14 §2.1 已经讲过),不参与跨节点转发;NODE_ROLE_PUBLISHER/NODE_ROLE_PLAYER 是协议预留位,不对应真实部署的 ppmmx 服务器角色。
4.2 节点间协议:WHIP 会话,不是另起一套协议¶
不管是 Origin→Record、Origin→Edge 还是 Origin→Forward→Edge,节点之间转发走的都是独立的 WHIP 会话——"每个目标是一条独立的 WHIP 会话与独立的 Forwarder,互不影响"(ppcdn-production-deployment-plan.md §二)。Forward 复用既有 mmx-to-mmx 转发实现这件事也说明同一点:节点间没有为"转发"这件事另起一套协议,WHIP 会话里原样带着标准的 RTP/SRTP 媒体,整条多层 simulcast 原封不动地往下传,中间节点不解码、不转码。
4.3 一跳的时延预算:量级在几十毫秒,但这是工程预算,不是逐跳实测¶
节点间转发不重新走一遍编解码管线,理论上开销应该很小——主要是网络 RTT、WHIP 协商和很小的缓冲。
要诚实说清楚:目前的时延测量没有单独覆盖过 Origin→Edge 或 Origin→Forward→Edge 这一段。
docs/design/p2p-latency-measurement-plan.md§4 的表格写得很明确——SEI/OBU 时间戳方案覆盖的是采集到渲染的完整链路;Edge 侧 WHEP 的currentRoundTripTime只覆盖"最后一跳"(Edge↔观众),不覆盖 Origin→Edge 段,不能代表完整链路延迟。换句话说,"一跳大约 20ms"是一个倒推出来的工程预算,不是已经被逐跳测量验证过的数字:EP12 记录的端到端基线本身只有 70~108ms(SRT 入库为 386ms),如果每多转发一跳都加上两位数、三位数毫秒,几跳叠加下来就会把这条基线吃光。把单跳开销控制在几十毫秒的量级,是为了让"加一跳 Forward 桥接到另一个 Region"这件事,不会把整条链路的时延预算透支——这也是为什么 Forward 目前只用来做一次性的跨 Region 桥接,不会被设计成可以无限串联的中继链。
5. 一个真实的容量换算:64 vCPU 的新加坡 Region,能开多少台 Edge¶
5.1 documented 的那部分:64 vCPU → 30 台 Edge¶
docs/deployment/ppcdn-production-deployment-plan.md §4.6 按已确认的生产口径算过这笔账(Origin/Record 保留在同一 Region/AZ,不占用额外 vCPU 之外的资源):
| 项目 | 计算 | 数值 |
|---|---|---|
| 媒体 Region Lightsail vCPU 配额 | 已申请/规划 | 64 vCPU |
| Origin 占用 | 1 × 2 vCPU | 2 vCPU |
| Record 占用 | 1 × 2 vCPU | 2 vCPU |
| Edge 可用 vCPU | 64 − 2 − 2 | 60 vCPU |
| 最大 Edge 数量 | 60 ÷ 2 vCPU/台 | 30 台 |
文档原话强调:"这里的 30 台是 64 vCPU 配额下的计算上限,不是业务 SLA。"
5.2 30 台 Edge,"峰值并发"到底是哪个数字¶
30 台之后,"能撑多少观众"取决于每台 Edge 的并发上限按哪个口径算,这里有四个完全不同性质的数字:
| 口径 | 单台并发 | 30 台合计 | 性质 |
|---|---|---|---|
生产实际准入(mmxNodeCapacity) |
100 | 约 3000 | 今天真正在跑、调度器用来做准入判断的配置容量(EP16 已从早期的 12 上调到 100,即满容量的一个留有余量的基准值) |
| Lightsail 月度配额折算均值 | 5.5 | 约 165 | 按 3TB 月配额折算的平均并发,不是峰值 |
| EP14 压测"干净通过"上限 | 250 | 理论 7500 | 单机实验室压测的清洁通过点;EP16 已把这个数字采纳为 Edge 的满容量规划上限,但仍是线性折算,没有在 30 台集群上联调验证过 |
| EP14 压测"主机失联"故障点 | 300 | 理论 9000 | 单机内存耗尽的失败点,依然不是容量,不采用 |
64 vCPU 新加坡 Region → 30 台 Edge:四种口径下的"峰值并发"
生产准入 (100/台) |████████████████ 3000 ← 今天真正在跑的数字(EP16 已从 12 上调)
配额月均 (5.5/台) |████ 165
压测干净通过 (250/台) |███████████████████████████████████ 7500 ← EP16 已采纳为满容量规划上限,仍未联调过
压测故障点 (300/台) |████████████████████████████████████████ 9000 ← 内存耗尽的失败点,依然不是容量
+-----------------------------------------▶
把 EP14 压测报告里最后一档"主机失联"(300 路,内存耗尽)直接乘以 30 台,算术上确实能凑出约 9000 这个数字——但这是把"单机压测的故障点"当成了"可以安全操作的容量",混淆了口径,不采用。EP14 §3.3 已经强调过:300 不是通过点,是故障点;真正干净通过、0 断连的是 250。EP16 把这个 250 采纳为 Edge 的满容量,日常配置容量则设在 100(满容量留出安全余量的基准值,经过 EP16 上调,此前是 12)——这是比早期准入门槛宽松得多的数字,但仍不是测出来的容量上限,也从没有在 30 台的真实集群规模上做过联调验证。
结论:64 vCPU 的新加坡 Region,算力上限是 30 台 Edge;但上面四个口径有一个共同的盲区——全部都是"单台数字 × 30"的线性折算,没有一个考虑过 Origin 自己能不能把流转发给这 30 台 Edge。下一节把这个盲区补上。
5.3 后续更正:这四个口径都没算对,真正的瓶颈在 Origin 的网卡¶
docs/deployment/ppcdn-production-deployment-plan.md §4.8 后来补充的真实测算,推翻了"单台容量 × Edge 台数"这整条思路:Origin 用 forwardMmx 把每一路直播流全量广播给每一台 Edge,Origin 自己的出站带宽 = 同播路数 × Edge 台数 × 单路码率——这个数字和观众数量完全无关,却会在 Edge 台数远没涨到 30 台之前,就先把 Origin 自己的网卡挤爆。7500 和 9000 这两个数字,不是"偏乐观",而是从一开始就没有对的维度:它们把瓶颈设想在 Edge 一侧(CPU/内存),但 30 台 Edge 规模下,真正先顶到天花板的是 Origin。
按 7.5Mbps 的 simulcast 整梯(观众实际只拉其中一层,按 1.5Mbps/观众评估;这是本集的示意口径,与 EP11 的示意阶梯不同)重新测算,一台 5Gbps 网络档的 Origin:
| Edge 台数 | 性质 | 峰值观众($12 档 Edge) |
峰值观众($24 档 Edge) |
|---|---|---|---|
| 15 | 5 Gbps Origin 安全值 | ~2,700 | ~3,300 |
| 22 | 5 Gbps Origin 理论上限 | ~4,000 | ~4,800 |
四种旧口径 vs 新口径(按 Origin 网卡约束重算)
旧:压测干净通过线性折算 (250/台×30) |███████████████████████████████████ 7500 ← 盲区:假设 Origin 转发能力无限
新:22 台 Edge,5Gbps Origin 理论上限 |████████████████████ 4800 ← 考虑 Origin 自己的出站带宽
新:15 台 Edge,5Gbps Origin 安全值 |█████████████ 3300 ← 留了余量的规划值
机型选择上还有一层:1.5Mbps 的观众负载下,$12 档(2vCPU/2GB)单台先撞到的是内存瓶颈;$24 档(2vCPU/4GB)同价多一倍内存、多 33% 流量配额,单台能多扛不少观众——同样预算,$24 档更划算,这也是上表两列差距的来源。想突破这个约 15~22 台 Edge 的规模,三条路:把 forwardMmx 从"全量广播"改成"按需转发"(只给确实有观众在看的 Edge 转发,最有效)、用多台 Origin 分层 fan-out、或者换更大网络档的 Origin 实例。
注意这个口径只算了一套 H264 梯子:如果推流端同时发布 H264 与 HEVC 两条轨(EP20),Origin 到 Edge 的转发量还要相应增加,所以 HEVC 带来的下行节省要与这部分内部转发增量一起核算,不能只看观众侧。
这次更正真正的教训不是"数字改小了",是"折算方法本身漏看了一整条链路":64 vCPU 配额算出的是能开多少台 Edge,和"这 30 台 Edge 实际能一起扛多少观众"是两个完全不同的问题——中间那一跳(Origin 能不能把流转发给所有 Edge)必须单独验算,不能假设它理所当然成立。
6. 框图¶
6.1 Origin/Edge 分离:1 对 N 扇出¶
flowchart LR
P["ppobs 推流"] --> O["Origin<br/>唯一入口"]
O --> E1["Edge-1"]
O --> E2["Edge-2"]
O --> E3["Edge-3"]
6.2 push fanout vs pull-on-demand¶
flowchart LR
subgraph PUSH["push fanout:主动推(forwardMmxTargets)"]
direction TB
O1["Origin"] -- "持续推送,不管有没有观众" --> E1["预置核心 Edge"]
end
subgraph PULL["pull-on-demand:按需回源(sourceOnDemand)"]
direction TB
V["第一个观众到达"] -- "触发回源" --> E2["动态扩出来的 Edge"]
E2 -. "之前无管线,零额外负载" .-> O2["Origin"]
end
6.3 跨 region:Forward 桥接¶
flowchart LR
subgraph RA["Region A"]
OA["Origin"] -- "push" --> FA["Forward<br/>被动收 + 主动转发"]
end
subgraph RB["Region B"]
EB1["Edge"]
EB2["Edge"]
end
FA --> EB1
FA --> EB2
6.4 64 vCPU → 30 Edge:四种容量口径,和被纠正的那一个¶
flowchart TB
Q["64 vCPU 新加坡 Region"] --> A["Origin 2vCPU + Record 2vCPU"]
Q --> B["Edge 可用 60 vCPU ÷ 2 = 30 台"]
B --> C1["生产准入 100/台 → 约 3000<br/>今天真正在跑的数字(EP16 已从 12 上调)"]
B --> C2["配额月均 5.5/台 → 约 165"]
B --> C3["旧:压测干净通过 250/台 → 理论 7500<br/>✗ 已被 §5.3 推翻:忽略了 Origin 自己的转发带宽"]
B --> C4["压测故障点 300/台 → 理论 9000<br/>内存耗尽,依然不是容量"]
C3 -.->|"§4.8 真实测算"| D["新:按 Origin 网卡口径<br/>15 台 Edge ~3,300 / 22 台 ~4,800<br/>($24 档 Edge,5 Gbps Origin)"]
7. 结尾与下集预告(逐字稿要点)¶
这一集说清楚了横向扩容的拓扑基础:Origin 和 Edge 拆成两种角色,才有"一次推流、N 份扇出"的空间,但代价对称——Origin 自己的处理时延会被这个 N 倍放大,所以要尽量压在几十毫秒的工程预算内,这是设计取向,不是已经逐跳测过的数字。Origin 到 Edge 有两种连法:预先规划的核心路径走主动推(
forwardMmxTargets),动态扩出来的边缘走按需回源(sourceOnDemand),两者分工不同、同时存在;这条链路最好走同 AZ/同 Region 的私网,省真金白银的跨区费用,也避开公网抖动,但这目前是部署层的人工约定,不是调度器强制的硬边界。跨 Region 要桥接,就引入 Forward 节点——被动收、主动转发,复用既有转发实现,代码和配置都已就位,但真实跨区域联调还没做过。节点间转发走的是 WHIP 会话,原样转发不解码不转码,"一跳约几十毫秒"是倒推出来的工程预算,不是独立测量过的逐跳实测值。最后算了一笔真账:64 vCPU 的新加坡 Region 配额,刨掉 Origin/Record,能开 30 台 Edge——但"30 台 Edge 能一起扛多少观众"不能拿单台容量直接乘以台数,Origin 自己用forwardMmx全量广播给所有 Edge,它自己的出站网卡才是真正的瓶颈:按 5Gbps 网络档的 Origin 测算,15 台 Edge($24档)安全值约 3,300 观众、22 台理论上限约 4,800——比"250×30=7500"这个早先的线性折算小得多,因为后者完全没考虑 Origin 转得过来转不过来。加 Edge 这件事,手动做的话是一套七步流程:新建机器 → 部署 mmx → 配置节点 ID 和容量 → 开防火墙、配 TLS → 在 Origin 的
forwardMmxTargets追加新节点的私有 IP 并 reload → 若有 region 映射再更新一次 → 在 ppcenter 确认节点 online 并拉一路 WHEP 验证。这套流程分钟级起步,负载缓慢爬升时够用。下一集,我们看一种这套流程完全来不及应付的场景——当流量不是缓慢上涨,而是突然爆发,Edge 怎么用配置容量留出弹性余量、扩容来不及时怎么靠"业务降级许可"联动自动把画质降到 720P 顶住窗口期。