MoQ 中继开通 API:把「开一台服务器」换成「划一个作用域」
Cloudflare 给 Media over QUIC 加了开通接口:一次调用得到隔离中继,发布与订阅凭证分离,draft-16 支持。
先说结论
- Cloudflare 给 MoQ(Media over QUIC)加了一套开通接口。新东西不是「低延迟直播」,是「一次 API 调用就能拿到一个隔离的中继域,且不启动任何进程」。
- 关键的架构选择:开通一个中继(relay)不创建虚拟机、容器或专用进程,而是在已经在运行的全球网络上划出一个隔离作用域。类比是加一个虚拟主机,不是开一台新服务器。
- 凭证被拆成两种角色。发布者拿
publish权限的令牌,观众拿subscribe的。拆开的理由很实际:不让观众的凭证被用来劫持主播的轨道。 - draft-16 加了两个机制:
PUBLISH让发布者在有人订阅之前就把流推给中继;SUBSCRIBE_NAMESPACE让订阅者按命名空间一次订全部轨道,包括直播中途新加的。两者都在削减首个观众要等的那趟往返。 - 这套开通模型被写成了 IETF Internet-Draft,目标是让多家 CDN 用同一套开通语义。目前还是 draft,不是 RFC,API 会变。
一个具体的麻烦
先想一件事:你要做一个实时竞拍网站。出价必须在几十毫秒内送到所有围观的人手里,否则拍卖就不公平——有人看到的价格比别人旧。
用传统办法你会发现自己在搭一支服务器舰队:几台负责接收主播/竞拍方的流,几台负责扇出给观众,前面挂负载均衡,按地区部署好几套,还要自己想清楚扩容策略。你想做的是竞拍,实际在做的是运维一个 CDN。
MoQ 想解决的就是这件事。它是 IETF 正在制定的开放协议(同一个组织标准化了 HTTP、TLS、QUIC),跑在 QUIC 之上——QUIC 是 HTTP/3 底下那层传输协议,低延迟主要来自它。
MoQ 的模型是发布/订阅:
发布者 中继 (relay) 订阅者
publisher subscriber
│ ┌───────────────┐ │
│ 推送有名字的流 │ │ 按名字要流 │
├───────────────────>│ 只负责复制 ├─────────────────>│
│ "my-namespace" │ 不看内容 │ │
│ │ ├─────────────────>│ (成千上万个)
│ └───────────────┘ │
│ │
└── 一个发布者,不需要自己处理扇出 ─────────────────────┘
要点在中继那个框里:中继从不需要看流里装的是什么。它只按名字复制字节。这一条带来两个后果:一是一个发布者能触达大规模受众而不必自己承担扇出;二是同一个协议能同时承载直播、视频通话、低延迟消息——因为协议不关心载荷是什么,那些原本各需要一套专用服务器的场景就合并了。
机制:作用域,不是服务器
去年 Cloudflare 已经把 MoQ 铺到了每一台服务器上,330 多个城市,免费开放给任何人测试。到现在每天仍有 1000 多个不同的客户端连上去做协议测试。
但那个端点不需要认证。这对协议开发是优点,对生产环境是致命的:你无法控制谁能发布、谁能订阅。竞拍那个例子里,观众和主播需要不同的权限,否则任何观众都能顶替主播往那个轨道里推东西。
今天补的就是这一层。有意思的是它的实现方式。在今天大多数 MoQ 部署里,一个中继是一台专用服务器,或者共享服务器上的一个专用进程;扩容意味着跑更多实例、把客户端分配过去、随需求变化加负载均衡。
Cloudflare 没这么做:
传统中继: Cloudflare 的中继:
┌──────────┐ 全球网络(已经在跑)
│ 进程/VM │ ┌─────────────────────────────┐
│ relay A │ │ ┌────────┐ ┌────────┐ │
└──────────┘ │ │作用域A │ │作用域B │ │
┌──────────┐ │ │namespace│ │namespace│ │
│ 进程/VM │ │ │ tracks │ │ tracks │ │
│ relay B │ │ │ objects│ │ objects│ │
└──────────┘ │ └────────┘ └────────┘ │
┌──────────┐ └─────────────────────────────┘
│ 负载均衡 │ ▲
└──────────┘ │ Anycast,路由由网络负责
客户端
开通一个中继就是创建一个隔离作用域。这个作用域把你的命名空间(namespace)、轨道(track)、对象(object)和别人的隔开,同时定义谁能进来、进来之后能发布还是只能订阅。客户端连的是 Anycast 端点,路由交给网络。
因为基础设施本来就在运行,开通请求只是塞进去一份配置和凭证,所以中继是立刻可用的——不用选区域、不用估容量、不用配负载均衡。
控制面和数据面被刻意分开:这套 API 只管中继和令牌,从不接触流经其中的媒体。资源只有两种:
- relay —— 上面说的隔离作用域,让一个应用的流永远不和另一个应用混在一起。
- token —— 一份凭证,授予在某一个中继上的一组操作(publish、subscribe,或两者)。
建一个中继只要一个名字:
curl -X POST \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/moq/relays" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Production Relay"}'
返回里带着两个默认令牌:一个能发布也能订阅,一个只能订阅。每个令牌可以单独设过期时间、单独吊销——吊销一个不影响其他人。
令牌走在 URL 路径里。用开源的 moq-rs 工具,推一路 ffmpeg 出来的分片 MP4:
ffmpeg -stream_loop -1 -re -i input.mp4 -f mp4 \
-movflags empty_moov+frag_every_frame+separate_moof+omit_tfhd_offset - \
| moq-pub -- --name my-namespace \
"https://draft-16.cloudflare.mediaoverquic.com/<publish_subscribe_token>"
观众那边:
moq-sub --name my-namespace \
"https://draft-16.cloudflare.mediaoverquic.com/<subscribe_token>" \
| ffplay -hide_banner -an -
会话打开时中继读取令牌,检查请求的操作是否被允许。
draft-16 的两个新机制
协议本身也在动。Cloudflare 现在同时支持 draft-14 和 draft-16,后者加了两件和发布/订阅直接相关的事。
PUBLISH:让发布者在有人请求之前就把轨道推给中继。没有它的时候,第一个订阅请求必须先沿着中继链一路走到发布者,发布者才开始发送。有了它,第一个观众连上来的时候中继已经在接收这条轨道了。省掉的是首个观众要等的那趟往返。
SUBSCRIBE_NAMESPACE:让订阅者请求某个命名空间下announce 的全部轨道,而不是一条条点名。这个订阅还覆盖后来新增的轨道——比如直播中途加了一路新清晰度或新音轨。
为什么这样设计
把中继做成作用域而不是进程,换来的是开通延迟趋近于零和运维负担归零。代价是隔离强度不同。专用进程给你的是操作系统级别的边界;共享网络上的逻辑作用域,其隔离性取决于实现的正确性。对大多数应用这个交换是划算的,但它是一个交换,不是纯粹的胜利。
令牌粒度也是一个明确的折中。目前每个令牌覆盖整个中继,允许发布、订阅或两者。这不够细——你没法只授权某一条轨道。Cloudflare 明说了这一点,并且说正在 IETF 和更广的 MoQ 社区里讨论更丰富的方案。先发一个粗粒度但能用的版本,比等一个完美的权限模型更符合 beta 的定位。
值得注意的是把开通模型写进 Internet-Draft 这个动作。理由是这样的:MoQ 的价值在互操作——客户端和中继实现同一套协议就能互通。但如果每家中继厂商都要求一套不同的控制面来创建作用域和签发凭证,那互操作的价值就被削掉一块。协议标准化了数据面,控制面却各家一套,锁定就从协议层转移到了开通层。那份草案把被开通的资源叫 scope 而不是 relay,但指的是同一个逻辑投递上下文。
边界
这是 beta,API 会变。 Cloudflare 自己在文里写了API will change as we develop it并让读者盯开发者文档看破坏性变更。现在写的集成代码有返工成本。
Internet-Draft 不是 RFC。 MoQ Transport 本身还在 draft 阶段(Cloudflare 支持的是 draft-14 和 draft-16 两个版本,说明版本在快速推进),那份开通草案的 API 模型还会随工作组的进展变化。押注在草案上的人需要接受这一点。
免费是 beta 期的免费。 「预览期内任意规模免费使用」是原话。定价之后是什么样,文章没说。
没有公开的延迟数字。 这篇文章讲的是开通和权限,通篇没有给端到端延迟的测量值,也没有和 WebRTC/LL-HLS 的对比基准。所以「低延迟」在这篇里是设计意图(来自 QUIC)而不是被验证的性能声明。想知道具体数字得另找来源。
令牌在 URL 路径里。 这是当前的传递方式。URL 会进日志、进浏览器历史、进 Referer——处理凭证时这一点需要按凭证的标准来对待,不能当成普通 URL 参数。文章没讨论这方面的运维影响。
来源
- An API for MoQ: provision your own isolated relaysCloudflare Blog