使用 Varnish 实现流媒体直播加速
Varnish and HTTP Live Streaming
使用 Varnish 实现 HTTP Live Streaming
(HLS
) 协议流媒体访问加速。
根据最近做的一个小项目的笔记整理。
做 Web 加速方案时,最核心的组件——缓存软件——的可选项一般有 Nginx
、Squid
、Traffic Server
、Varnish
等。
由于命令行参数和环境变量丰富,Varnish
无疑是最易于部署的一款,即使不花费太多时间修改配置文件也能快速运行起来。
Varnish
内置支持 HLS
。
然而使用 Varnish
做视频缓存,绝对不仅仅是配置文件中 set beresp.do_stream = true;
这么简单。
典型的部署结构
如下图的这类典型的简化部署结构里:
摄像设备将视频推送到流媒体源,
流媒体源编码切割为 HLS
协议的视频流,
经由用户访问性价比高的 Varnish
节点所组成的 CDN 网络代理,最终分发给终端用户的播放器和电视设备。
无论是大型的视频网站,还是小型的直播项目,都努力从性能、成本和架构复杂性方面取得平衡。
缓存策略
playlist 的设置
.m3u8
是 playlist 文件,需要及时更新,默认不缓存。
然而在做大规模 CDN 时可以设置一定的缓存时间,从而提升访问性能,也减轻源站的请求量。
即使是 1 秒缓存对源端压力也能有很大缓解。
设置时的原则是:根据 playlist 中给出的 #EXT-X-TARGETDURATION
的值,将缓存时间设置低于该值的一半。
以下例子中 #EXT-X-TARGETDURATION:10
,所以将 playlist 的缓存时间设置为 5 秒。
较低的缓存时间(如 1s)可降低客户端平均播放延迟。
if (bereq.url ~ "\.m3u8$") {
set beresp.ttl = 5s;
set beresp.grace = 900s;
}
grace
设置源站失效时的缓存时间为 15 分钟。
这段时间客户端访问会循环播放最后一段可用的视频,而不是不播放。
传输流(TS)的设置
.ts
是切割后的媒体文件。
因为有时效性,不需要保存太长时间。
为保证有些播放滞后的客户端,这里设置最长时间为 5 分钟。
if (bereq.url ~ "\.ts$") {
set beresp.ttl = 300s;
set beresp.grace = 900s;
}
和 playlist 一样,grace
时间也设置为 15 分钟。
容量计算
容量包含两个层次的含义:
- 每个缓存节点所覆盖的区域、用户数量,及源节点的所需要承担的连接数量计算。
由于用户数量影响带宽的采购,而覆盖区域则影响性能,这一选择主要是性价比的考量。 - 每个 Varnish 节点的带宽、内存、磁盘等硬件资源计算。
根据缓存策略缓存到内存和磁盘,容量可以根据公式最大缓存容量 = 码率 x 缓存时长 x 频道数
来估算。 将 100 个 1080p 画质的直播频道在内存中缓存 5 分钟,用一台 32G 内存的主机基本够用了。 当然考虑到分散带宽压力,还需要考虑多个节点。
设置内存使用量
VARNISH_STORAGE="malloc,20G"
高可用
高可用 用以应对缓存节点不稳定、宕机等情况。
由于缓存节点无状态,可以在某个节点故障时,将相应流量自动切换到其它节点。 支持自动切换的负载均衡,可以自建,也可以使用云服务商提供的方案,不再赘述。
稳定性
稳定性 用于应对网络质量波动、突发流量、源站失效等情况。
不稳定时首先保证最小可用返回。 比如设定条件临时自动调高 playlist 的缓存时间,这些操作要能够自动无干预完成。
当无法保证最小可用时则退而求其次,保证最短恢复时间。 一种常见的情况是后端不稳定,会返回错误状态码。 为了后端能在最短时间里恢复,一小段时间之内不再进行回源尝试,从而为源站减轻压力。 当然实际配置中,对收到错误响应的请求重试一次,从而排除偶发抖动的情况。
if (beresp.status == 503) {
if (bereq.retries < 1) {
return (retry);
} else {
set beresp.ttl = 3s;
}
}
缓存键值规范化
由于客户端支持的特性不同,可能需要请求返回不一样的文件格式。 因此,缓存软件在本地存储文件时,不是简单直接根据文件名来保存。 而是通过几个特定键值的组合来区分在本地保存的文件。
URL 是这些键值里最重要的一个。 对 URL 的选择主要是考虑是否忽略其所包含参数。
另一个约定的是 HTTP 头里的 Vary 头。 一般是 Accept-Encoding。 但是不同浏览器发来的请求头五花八门。 即使同样需要 gzip,也可能是不一样的字符串,这会导致被区分本地存储为不同的文件。
Firefox, IE: gzip, deflate
Chrome: gzip,deflate,sdch
Opera: deflate, gzip, x-gzip, identity, *;q=0
所以我们需要对请求头进行规范化。 不相信