文 / 曾伟纪
整理 / LiveVideoStack
伴随直播逐渐成为日常生活的一部分以及直播玩法的快速更新,用户对低延迟、低卡顿的要求也不断提高,而在复杂的网络条件下开展直播服务势必会遇到诸多挑战,网心科技曾伟纪在LiveVideoStack Meet上分享了星域CDN独创的无限节点等技术,详细介绍从传统直播CDN向无限节点演进过程中关键技术难点的解决方案和经验。
大家好,首先自我介绍一下,我是来自网心科技的曾伟纪,我是在15年公司刚成立时就加入了网心科技,也是从头参与网心直播CDN的构建。很高兴来到LiveVideoStack Meet分享,我大概会分为五部分介绍:CDN背景介绍、直播CDN的经典架构、基于经典架构做的一些优化工作、无限节点技术以及展望未来的工作。
背景介绍
首先来看背景,什么是CDN?维基百科里对CDN的定义中最关键的就是利用最靠近每位用户的服务器实现可靠的内容分发。我们经常会开玩笑,世界上最遥远的距离不是生与死,而是你在电信,我在联通,虽然有点夸张、调侃的成分,但还是很生动的描述了国内复杂的网络条件。当我们进行一个长链路访问时,这个链路上的传输速率,包括它的连通性很可能都会得不到保证,而CDN正是利用其核心原理——将内容下沉到离用户最近的一个节点,让用户可以通过一些短链路去访问,从而获得较好的分发效果。
直播CDN它本身有怎样的特点?首先它同样是做内容下沉,但不同于传统CDN,直播CDN分发的是实时流,而传统CDN分发主要是一些静态文件,比如页面、图片或者点播视频文件,这些静态资源一旦预先分发一次之后,就可以服务很多次的访问,而在直播CDN中用户看到的每一帧都是在很短的时间内产生,实时传输到每个用户的,因此直播CDN的核心任务就变成了保证主播和观众之间每一条链路都有稳定的传输速率,但稳定在我们目前所处的复杂网络环境中是特别难做的,举个例子,假如我的网络带宽很好,正常情况下有一百兆,那么我在看点播的时候一下就缓冲完了,即使中间出现一些波动也没关系,因为有很大的Buffer可以扛;但对于直播来说,只有很短时间的Buffer,一旦出现抖动就无法正常观看,这也是我们在直播CDN领域遇到的挑战。
经典架构
在介绍完背景后,我们来看下经典架构。首先在核心的传输部分,我们也是采用了经典的三层树状架构——在全国设置若干中心节点,中心节点辐射到每个大区域的汇聚节点,这些汇聚节点在各个大区内又会辐射出很多边缘节点,而这些边缘节点就是内容下沉到离用户最近的节点。为了保证覆盖,边缘节点的数量是相对比较大的,基本上可以做到省市运营商级别的覆盖。
在提供传输服务的同时,还有一些功能需要特别注意,像截图,录制和转码。录制很好理解,对于截图最直观的印象就是每个直播间都会有封面,这就是截图服务产生的。不过它最关键的作用是黄反内容的监控,如果截图服务更新不及时,有可能会引起一些麻烦。虽然现在直播的趋势是越来越高清,但用户的使用环境还是千差万别的,有些用户可能本身没有足够带宽去传输这样的流,这就需要我们通过转码集群去实时转出一些稍微低码率的流来供用户选择观看。
在这样一个三级的架构中,每一个下级节点对应哪个上级节点的关系,是由内部路由的服务去控制的,外部用户如何找到就近的节点则是由调度服务来控制。在架构左边是两个支撑系统——运营平台和数据平台,运营平台提供了一个方便的操作界面让我们能去控制整个系统,数据平台则会收集整个系统中所有统计数据,以及关键逻辑的运行结果,从而提供计费、质量监控、告警等等一些功能。
基于经典架构的优化
- 自动路由规划
在了解经典架构之后,我们在这个架构上做了一些优化。首先就是自动路由规划,在系统构建初期,我们所有节点上层从属关系完全是由人工按经验设定的,但随着节点规模的扩大,依靠人去编这张网就变成了一个很繁重且容易出错的工作,因此我们开发了一套自动根据评分选取上行节点的机制,首先它会基于质量数据,比如基础网络监控,像丢包、延迟,以及业务上报的数据,像首屏统计、卡顿统计,基于这些数据再综合考虑成本和容量最终为每个节点选取一个比较合适的上级回源地址池。
- 自动切换
在有了回源地址池后,我们怎么样去用?最简单的也是我们一开始采用的方式,就是先连主,主挂了再去连备,也就是只有在当前连接出现TCP连接关闭,比如出现connection reset或者出现超时,我们才会去做备的切换,但往往切换已经晚了,用户侧已经出现明显的卡顿感知,因此我们基于实时的质量数据做了一个更灵敏的切换机制,当我发现传输的流帧率开始出现不足时,就会提早的去做主备切换。
但这样的切换也带来一些相关的问题:首先是防乒乓的问题,比如几个上级节点全欠速,切换本身又是有开销的,那这种情况还不如不切。最初也是用很粗暴的策略——两次乒乓之间必须经过一分钟以上,从而去避免很频繁的乒乓。但是同样会有问题,比如三个上行节点ABC,A和B坏了,C是好的,这样就需要两分钟的时间才能切到C,为了解决这样的问题,在主连接出现欠速的情况下,会并发新起一个连接去连备,同时进行测速,如果发现备的质量比主的好,才会实际去做切换。
第二个问题是防重放,为了让用户拉流能在第一时间出画面,在服务端会缓存一个当前时刻到上一个关键帧的数据,在新连接上来的时候会先把这个数据吐给用户,在重连的时候就会拿到这样的老数据丢给用户,而用户端就会出现重放的现象。针对这个问题,我们在回源的时候会对所有拿到的数据做摘要,在重连的时候会根据摘要把拿到的数据做去重,就避免了重放问题。
- 机房内部汇聚
第三点是一个机房内部的回源汇聚,这其实是出于成本上的优化。考虑这样一个场景,机房内ABC三台机器,同时都有人看同样一个流,那么每台机器都会单独有一个向上级的回源,但实际这三路拉到的是同样的数据,那么我们就可以只让A去回源,B和C走内网从A把数据拉过来就好,这对成本的节省是显而易见的,但同样也带来一个问题,如果A的回源出问题了,整个机房的质量就会受到影响,因此我们通过哈希的方式把回源的责任打散到机房内所有节点上,这样一个节点出问题了,影响的也只是一部分的流。但对于单个流来说,这个节点出问题还是影响了流在当前机房的服务,因此我们引入了一个备的回源节点的概念,也就是如果从主拉的流出现问题,可以再从哈希关系中找下一台再次尝试,如果两台都不行,最后还可以回退成单机回源的形式,这样就保证异常场景下的服务可用性。
- 最短链路
第四点是最短链路,前面介绍经典架构是一个三层树状的固定层级架构,每个节点都只能和自己上一节点通信,这样任何一个边缘节点回源必然要产生两跳——边缘到中继,中继到中心,这样一方面会产生两倍的回源带宽开销,一方面导致链路比较长。像这种回源带宽的开销在一些比较热的流上可能问题还不大,因为我们的回源是有充分的汇聚复用,但对于一些冷流,观众人数不多,分布也较散,就会出现服务N个用户、消耗2N的内部回源带宽,而在全民直播的趋势下,成本压力是很大的。
因此我们就要重新去审视这样的层级结构,对于数据源而言,所有的拉流大致划为三类:第一种是同网近距离拉流,这种情况下直接从数据源去拉就可以;第二种是同网远距离拉流,在这样一个长链路中加入一个中间结点,把它拆分成两个相对较短的链路,这样就会比较可控;第三种就是跨网的拉流,那这里就可以使用像多线机房或者BGP这样的一些节点去完成中继,当然这里还缺少汇聚,实际上在实现拓扑规划时候,需要充分运用已经在拉流的点,用它的回源去服务更多的边源节点,才能在成本上得到一个最大的优化。这样每一个流都会有一张自身最优化的分发网络,从而获得更好的服务质量,无论在成本、还缩短链路或者提高稳定性方面都有很好的效果。
- 调度
介绍完内部,再来看下外部调度,典型的调度方式就是DNS或者302,DNS很简单,拉流的域名直接解析到边缘节点上就可以,不过它的问题一方面配置可能会出错,一方面Local DNS本身结果可能也会有些问题,比如实效性不好,甚至可能被劫持,但好处是首屏很快;而302的方式因为是用户直接和调度服务器去交互,调度服务器再给302跳转,结果是最精确的,但因为多了一跳首屏会受到影响。因此我们将这两种方式结合,默认情况下是302,当它连到边缘节点时再去检查是否连错,DNS调准了没有,如果不准则再发个302跳走,这样就保证了大部分用户首屏能得到很好的保障,同时一小部分用户调度准确度也能得到提升。
此外还有HTTP DNS的调度方式,它需要客户端做一些适配,在拉流之前它会先向HTTP接口请求一个边缘节点地址,这样客户端可以给我们暴露很多信息,比如运营商属性、网络性质(WiFi / 4G)这样的一些输入,有了更多的输入我们就可以做更精细化的策略。除了作用在用户上的优化,对于策略本身的修正就利用到迅雷赚钱宝的节点,它分布在真实用户的网络环境中,那我们从每一个赚钱宝节点向服务发起探测,会给我们提供一个很详细的、覆盖很广的数据,特别是能发现从服务端很难去排查的一些问题,像URL劫持、DNS劫持。
- 更多挑战
虽然已经做了很多优化,但依然觉得还不够,首先IDC节点离用户还是不够近,同时IDC成本也还是很高的,而且虽然在内部分发可以做很多的控制,但在最后一跳依然摆脱不了传统CDN的协议——现在直播CDN主流的协议像RTMP、HTTP-FLV、TCP。对于TCP而言,它主要的问题就是对丢包和延时的抵抗力是很低的,作为一个单链路传输,很难实现平滑切换,针对这样的问题,我们就采用了无限节点的方式。
无限节点
在讲无限节点之前,我们需要介绍下迅雷赚钱宝,它其实是一个小的计算节点,当用户把它接入带家庭网络中,它就变成了我们在离用户最近的一个节点,而星域直播卓越版这个产品就是利用已有的百万级别的赚钱宝节点,从而实现了内容下沉到用户一公里,甚至下沉到用户的家庭网络内。
- 整体架构
这张图是卓越版整体的架构。重点看右边,在观众端需要嵌入一个我们的SDK,这个SDK对上层吐出来是标准的直播流,也就是FLV,而它的底层不仅能从我们的IDC节点拉取流数据,也可以并发的从Tracker获取附近的赚钱宝节点来拉取流数据,这是如何实现的?
- 技术原理
我们对每一个流都做了切片处理,切片粒度是比较细的,大概在几百字节到1K左右,那这样的一个切片就不会像HLS大切片一样引入非常明显的延迟。这些切片会被分发到很多节点上,包括IDC边缘服务器以及迅雷赚钱宝的小节点,然后SDK就会并发的从多个数据源把切片拉回来,比如每个地方拉一两片,最终组合还原成原本的视频流吐给上层,这样我们就通过切片把一个流拆成多个部分在不同的链路上并行传输,它的好处首先是降低了对每一条链路的带宽需求,其次因为每个链路上传输少了,当一条链路出现故障时,影响也就相对较小,这时无论是通过另一条链路替代它,或是从其他已有链路补发数据,代价都是相对较小的。
切片的另一个好处就是可以做FEC,对于客户端,如果它本身链路不好,那么到所有的点都会有丢包,假如丢10%的包,那我就加10%的冗余就能保障收到足够多的切片去还原完整的视频流,一部分程度解决了弱网场景。而对于FEC我们也是动态调整的,正常情况基本是不会加冗余的,只有发现所有链路开始整体劣化时,才会采取多发冗余数据的形式去抵抗。
- 关键问题
以上就是多路传输的基本原理,这其中还会涉及一些关键问题。首先就是与P2P的区别,刚才提到Tracker、并行传输,大家第一个反映可能是P2P,但两者还有明显的区别:在P2P网络中Peer本身是不可控的,当用户不观看直播流时,这个Peer就没了,但赚钱宝却是可控节点,它可以永远保证是有数据的;其次基于P2P直播方案基本上都是用延迟去换分享率,因为分享一定会造成延迟的增大,而在这个模式中,用户拉的永远都是从节点上的一手数据,延迟和传统CDN并没有明显区别。
第二个关键问题是动态调整,对于这一整套机制而言,运作起来后传输是比较稳定的,但它起播是需要一段时间的,因此我们会先从IDC节点去拉取初期数据,当机制跑起来后再逐渐切到以赚钱宝为主的多路传输上。其次,如何快速动态调整链路也是很关键的,虽然在多链路传输下,每个链路传输数据的比例比较小,切换成本比较低,但假如说某一条链路挂了,重新请球新节点建立连接也需要消耗一定时间,甚至有可能拿到的新节点质量也不好,这样传输就会受到影响。因此我们在稳定传输时,SDK同时会周期性请求一些节点测速,把好的节点留下来,当需要去切换链路时就可以直接去把这个结点拿出来用。最后是fallback机制,假如Tracker失效,或者赚钱宝集体都挂的情况下,我可以很快的fallback回到IDC节点拉取数据。
未来展望
最后是展望后续的一些工作,首先是P2SP, S主要指的是Server节点(可控节点)。而P主要是Peer,不可控的一些节点或者客户端。我们目前在直播CDN采用的卓越版,模式实际是P2S。P2SP则是在P2S对每个客户端的延时、传输的稳定性有明显提升的基础上,引入更多的Peer分享,同时可以进一步的降低服务成本。第二点,就是寻求更多的S,我们目前的S还只有IDC节点和赚钱宝,还需要进一步发觉更多种形式的S,扩大覆盖,进一步降低成本。
其实不论是引入Peer,还是寻求更多的S,对我们的系统都会有更多更大的挑战。但是,发现问题,面对挑战,以技术驱动产品升级,是网心科技成立之初秉承的信念,应该也是我们所有技术同学的理想。
LiveVideoStack招募全职技术编辑和社区编辑
LiveVideoStack是专注在音视频、多媒体开发的技术社区,通过传播最新技术探索与应用实践,帮助技术人员成长,解决企业应用场景中的技术难题。如果你有意为音视频、多媒体开发领域发展做出贡献,欢迎成为LiveVideoStack社区编辑的一员。你可以翻译、投稿、采访、提供内容线索等。