🎬 第九章 EasyDarwin搭建直播平台
📄 一句话概括

按「数据往哪走」分四类,比按文件名分有用得多。 六个环节里只有一个会成倍放大数据,就是转发。

📝 本节和前面几节的关系

9.1.1 讲的是骨架怎么搭,这一节讲里面的模块怎么分工。 分对类,扩容时才知道该加哪一类机器。

一、四类模块

① 接入类:RTSP 服务、RTMP 服务。 输入是推流端,输出是「还没解复用的流」。 ② 处理类:解复用、转发、转码。 输入是裸流,输出是「可以分发的单元」。 ③ 输出类:HLS 切片、HTTP-FLV、RTMP 再分发。 输入是可分发单元,输出是播放端。 ④ 管理类:REST 接口、配置、日志。 它不在数据链路上,只在旁边看着。

这样分的好处是「扩容只扩其中一类」: 看的人多就加输出,推流多就加接入。 处理类夹在中间,但它通常最先成为瓶颈——因为所有流都要过它,而且它干的活最重。

二、转发与转码不是一回事

这两个词经常被混着用,成本差一个数量级。

① 转发:不碰编码,只把已经压好的数据换个容器或者原样送出去。 CPU 几乎不花,只花内存与网卡。 一路变四路,就是四份数据往外走。 ② 转码:重新编码。 每一路都要独立算一遍, 四路就要四份 CPU,没有「算一次分四份」的好事。

所以「支持多少种输出协议」这个指标,成本在转发上,不在编码上。 而「支持多少种分辨率」这个指标,成本才在编码上。 看参数表的时候要分清这两件事。

三、一份流要过六道手

把链路摊开,一份流从进门到出门要经过六个环节:

① 网络收包:从 TCP 或 UDP 缓冲区拿到字节。 数据不变。 ② 解复用:把容器拆开,分成音频轨与视频轨。 数据只会变少,因为容器头被丢掉了。 ③ 缓存:按时间排好序,等关键帧。 数据不变,只是换了个地方放。 ④ 转发:复制给每个下游。 这是唯一成倍放大的一步,倍数就是下游数。 ⑤ 重新封装:换成 HLS 的 TS 或 HTTP-FLV。 会加一点容器头,通常不到百分之一。 ⑥ 网络发包:送出去。 数据不变。

认准这一点,容量规划就有依据了: 要看「几路输出」的带宽,别看「几路输入」的 CPU。 输入决定接入压力,输出决定出口带宽,两个数要分开算。

四、动手 Demo:模块分类——四类模块,一份流走一遍

先把模块按数据流向分四类, 再真把同一份流转发给 1/2/4 个下游,看字节怎么涨。

import io, os, tempfile, time

# head 是 FLV 的 9 字节文件头 + 4 字节 PreviousTagSize
# tags 是拆出来的 tag(11 字节头 + 载荷 + 4 字节尾部长度)
def split_flv(raw):
    tags, off = [], 13
    while off + 11 <= len(raw):
        size = int.from_bytes(raw[off + 1:off + 4], "big")
        tags.append(raw[off:off + 11 + size + 4])
        off += 11 + size + 4
    return raw[:13], tags

head, tags = split_flv(open(FLV_PATH, "rb").read())
print("%d 个 tag,共 %d 字节" % (len(tags), sum(len(t) for t in tags)))

for k in (1, 2, 4):
    outs = []
    for i in range(k):
        fh = io.open(os.path.join(TMP, "o%d.flv" % i), "wb")
        fh.write(head)
        outs.append(fh)
    t0 = time.perf_counter()
    for t in tags:
        for fh in outs:
            fh.write(t)
    el = (time.perf_counter() - t0) * 1e3
    for fh in outs:
        fh.close()
    total = sum(os.path.getsize(os.path.join(TMP, "o%d.flv" % i))
                for i in range(k))
    print("%d 路 -> 输出 %d 字节,耗时 %.1f 毫秒" % (k, total, el))

实跑输出:

--------------------------------------------------------------------
模块分类——四类模块,一份流走一遍
--------------------------------------------------------------------
**① 模块怎么分类**

  **按「数据往哪走」分,比按文件名分有用得多。**

  类别     典型模块                     输入             输出
  -----------------------------------------------------------------------
  接入     RTSP 服务、RTMP 服务         推流端           未解复用的流
  处理     解复用、转发、转码           未解复用的流     可分发单元
  输出     HLS 切片、HTTP-FLV、RTMP     可分发单元       播放端
  管理     REST 接口、配置、日志        运维请求         状态与文件

  **这样分的好处是「扩容只扩其中一类」:**
  **看的人多就多加输出模块,推流多就多加接入模块。**
  **处理模块夹在中间,加机器通常先加它。**

**② 一份流分发到 1/2/4 个下游,开销怎么涨**

  **这是处理模块的核心工作,也是最容易被低估的地方。**
  **读一次、分多份,省的是读,不省的是写。**

  下游数     转发 tag     输出总字节     耗时毫秒     字节倍数
  ----------------------------------------------------------------
  1          28           10503          5.0          1.00 倍
  2          28           21006          4.5          2.00 倍
  4          28           42012          11.4         4.00 倍

  **这里做的是「纯转发」:不重新编码,连容器都不换,**
  **只是把每个 FLV tag 原样写给每个下游。**
  **先看字节那一列:1 路一万出头,4 路正好四倍——完美线性。**
  **再看耗时那一列,它在涨,但每次跑都不一样:**
  **沙箱里文件系统有缓存,同一段代码跑两遍能差一倍。**
  **所以这里只认字节数,不拿耗时下倍数结论。**

  **真要是重新编码,倍数会完全不同:**
  **编码是 CPU 密集的,4 路就要 4 份 CPU,没有「一次算完分四份」的好事。**
  **这也是为什么生产上「转码」永远比「转发」贵一个数量级。**

**③ 一份流被多少模块碰过**

  **把上面的链路摊开,一份流从进门到出门要过六道手:**

  序     环节         改的是什么             会不会放大数据
  ---------------------------------------------------------------
  1      网络收包     无                     不会
  2      解复用       拆成音频轨与视频轨     不会,反而变小
  3      缓存         按时间排好序           不会
  4      转发         复制给每个下游         会,按下游数翻倍
  5      重新封装     换容器格式             会,加容器头
  6      网络发包     无                     不会

  **只有第 4 步会成倍放大数据,其余各步要么不变要么只加一点头。**
  **所以「支持几种输出协议」这个指标,真正的成本在转发那一步。**
  **四种输出就是四份数据要出去,跟编码一次没关系。**

字节是严格线性的,1 路一万出头,4 路正好四万二。 耗时却在原地打转,因为沙箱里文件系统有缓存,跑两遍能差一倍。 所以这里只认字节数,不拿耗时下倍数结论。 转发就是「同一个字节写给更多人」,CPU 一点都不多花。

30 秒实验

  1. 把转发换成「每路重新编码」,想想耗时怎么变——从线性变成接近四倍,因为编码没有共享。
  2. 把 tag 排序打乱再发,看播放器还能不能播——不能,时间戳乱了,FFmpeg 会报时间戳回退。
  3. 只转发视频 tag、丢掉脚本 tag,看文件还能不能播——能播,但播放器不知道分辨率与编码参数。

常见误区

  1. 「转发与转码差不多」——差一个数量级,一个不碰编码一个重编。
  2. 「输出协议越多 CPU 越吃不消」——先吃不消的是出口带宽。
  3. 「解复用会放大数据」——相反,它丢掉容器头,数据变小。
自测三问(点开对答案) 1. 四类模块是按什么分的? —— **按数据往哪走**:接入、处理、输出、管理。 2. 六个环节里哪个会成倍放大数据? —— **只有转发**,倍数就是下游数。 3. 转码为什么比转发贵一个数量级? —— **每一路都要独立重算一遍编码**,没有共享。

相关笔记