众所周知运维成本是直播网站最大的成本组成,运维成本则主要体现在带宽,而伴随主播与用户对视频清晰度以及连麦的需求不断提升,直播带宽也在与日俱增。本文整理自学霸君音视频技术负责人袁荣喜在LiveVideoStackCon 2017上的分享,通过实践案例讲解了如何使用P2P技术将带宽和延迟降低到传统技术的1/3,并详细介绍了P2P分发算法的架构设计和技术实现细节。
演讲 / 袁荣喜
整理 / LiveVideoStack
大家好,我是来自学霸君的袁荣喜,我演讲的主题是《P2P技术是如何拯救一家直播网站的》,众所周知直播网站最大的成本就是运维成本,当然还会有一部分主播的成本,而运维成本的主要表现就在于带宽,从目前统计来看,一个直播网站的成本大概有30%来源于带宽。
一个直播网站的窘境
我的一个朋友是做直播平台的,每天大概有10万人观看,在经历了2016年的千播大战之后,他们升级了观看体验——主要是提高了分辨率和清晰度,但成本也因此提高到了原本的5倍,也就是假如原本是20G,现在每天要付出100G的带宽流量,这对于一个普通的平台来说成本是致命的。
基本情况
那么我们先来看下这家平台的具体情况:它是南方三线城市的直播平台,PC占有率大概有70%,移动端占有率在30%左右,码流也从320x240,20K升级到640x480,大概每秒的单路码流在800kbps左右,也就是100KB/S的样子 。因为它和YY类似,都属于聊天室形式的,也就说每一个观看端都会有上麦的需求,也因此必须要解决延迟的问题,如果延迟太大,在上麦的过程中和下麦的过程中有可能会产生信息不对等,由此丢失一部分信息,因此对于观看端延迟要求在两秒以内,而麦与麦之间的延迟要控制在500毫秒,这就对延迟有非常高的要求。
成本
前面提到平台观看人数能达到10万以上,它每天在边缘节点上就需要用83G来扛住边缘节点的分发,由于系统是分布式的,因此在边缘节点与边缘节点之间也需要做分发,这部分又会需要2G的BGP带宽流量,这个带宽很大。因此它需大概100台物理机来抗,对于私有云搭建的成本我并不是很了解,如果按照公有云来计算的话,每个月大概要支付百万以上的成本,这对于一个三线城市的小公司来说压力是巨大的,况且还有比较沉重的业务和寥寥无几的融资渠道。
存活的希望
那么平台想要继续存活,就需要将带宽成本降到现在的1/3,也就是只用30G就能解决问题。将产品迁移到CDN上是一个解决方法,毕竟各大云厂商目前的优惠力度还是比较大的,但他们在前期采用CDN做测试时发现会有两个问题:首先是延迟问题,在很多分享中也有提到CDN的延迟至少是3-5秒,有时候更大,尤其是在大规模直播下;而第二个问题则是每个观看端都会有上麦的需求,但连麦服务是需要额外付费的,综合计算使用CDN也无法降低成本,甚至还有增加。
那么我们考虑是否可以通过调整编解码来实现,大家都知道H.265从编码效果上来说可以减掉一半的成本,我们也在小规模内尝试了替换,不过发现存在CPU消耗的问题,因为观看端的设备比较多样,既有移动端、也有PC端,并且设备的性能参差不齐,在较差的机器上会频繁出现卡顿,根据统计大致有50%的用户无法接受这个方案。
那么既然是传输产生的高成本,是否可以对传输方式做优化——P2P技术,它早期源于音频分享和文件分享,并且是下一代CDN的主流技术。在做这样一个系统之前首先需要调研目前用户端上传的最大带宽,我们发现有50%以上用户的上传带宽在1.6mbs以上,平均来看大概可以做到640KB/S 码率的上传程度。前期调查后,我们确立了系统设计的三个目标:降成本、控延迟和保质量,也就是在保证用户体验下将成本降低1/3,在后期的小规模测试和大规模替换中的效果还是比较满意的。
流媒体P2P分发系统
网络架构
接下来我们重点讲解这个分发系统的具体细节,首先上图是系统的架构图,大致分为四层:最上层是BGP——用来协调边缘节点与边缘节点之间的分发;第二层是边缘节点,它主要是用来分发媒体数据到观看端;第三层我们在客户端层面上做了一层超级节点,用来分摊大部分边缘节点的分发压力,这一层的主要目的就是为了节省带宽;第四层是普通节点,这主要是由于有些端可能是4G或者一些比较差的WiFi,它们可能没有上传的能力。那么在旁边还有一个小红圈,它表示推流的过程,因为业务中对上麦、连麦有需求,我们没有用连麦系统,而是采用了一种推流加连麦的方式来做,使用的是可靠UDP,也就是RUDP技术。
三部分网络
整个网络分为三部分:一个是推流部分,就是我们要保证流是要推到边缘节点上;第二是边缘节点与边缘节点要做一个可靠、快速的分发,让所有数据尽量快到达Edge server,这样保证延迟比较小;第三部分是P2P,当数据到达Edge server上,我们要快速通过P2P网络分发到各个观看端进行播放。在这个系统中有个锚点,就是所有Edge sever都应该尽快拿到所有的视频数据和音频数据。
在具体介绍这三部分网络实现方式之前,我们先来看下流媒体中最重要的一部分——分发切片,也就说数据如何打包才能符合我们的要求和场景。最早我们采用HLS的方式,也就是一秒打一个分片,不过它会引来延迟的问题,从实际的测试中我们发现会有大于一秒的延迟,但如果分片太小了就又会引入其他问题。经过测试,我们最终采用一帧为一个分片,这样可以带来三个好处:首先是粒度小,每一帧大概在几十毫秒,这样我们可以尽量去优化这个延迟;第二点就是实现简单,因为每一个编解码都是基于帧的编解码,这样天然的就是一个分片模式;第三点是组织比较灵活,可以与播放buffer无缝接合。
推流
接下来我们具体讲解这三个网络是如何实现的。首先是推流,推流一般来说会用到RTMP或者说TCP的方式,但它会带来两个问题:在网络不好的情况下,因为每个端都需要上传和上麦的操作,这样会引来延迟问题,我们测试发现TCP最大的延迟有可能达到1.2秒,这是不可接受的;第二个就是在弱网环境下大量传输数据,它会产生TCP连接频繁的断开,这就会造成数据推不上去或者间断性的推上去,从而导致观看节点频繁卡顿。
UDP可以很好的解决延迟和连接的问题,但由于它的不可靠,会产生丢包问题,因此我们借助了WebRTC的GCC 原理,在UDP之上设计了一层可靠,保证数据可以尽快、尽好的传到Edge server上。上图是它的原理,它通过一系列并发的发包,最后再通过确认机制来回馈要重传的包或者说什么时候重传包。
- 单路径RUDP
数据到了Edge server上之后就要把数据快速的传输到其他Edge server,我们最初的设计和CDN一样——通过BGP server单线的中转,也就是Edge server将数据传到BGP server,再由BGP server传输到其他Edge server上,这种做法最大的好处在于实现简单,并且链路查找问题比较明确。但同时它也存在两个问题:首先就是成本比较高,因为BGP的成本是整个Edge server边缘节点带宽成本 的10倍;第二点因为它是建立在私有云上,而私有云每个机房都有防火墙,而当网站频繁受到攻击时就有可能变换防火墙策略,这样就有可能导致某条链路不通或者到BGP不通,由此就会影响Edge server无法接收数据包,从而导致观看节点卡顿。
- 多路径RUDP
由此我们在RUDP之上设计了一个多链路的传输(如上图):首先Edge server之间是直连的,同时有多个BGP server来做relay ,中间是无状态的,也就是即使某一条链路出现问题,其他几条链路还在并行传数据,这样就能保证尽量的可靠和可用。其次为了节省成本这里还有一个设计原则,Edge server之间尽量保证直连,不走BGP或者少量走BGP,这样就可以节省一大部分BGP流量,根据统计可以节省80%的成本。
P2P网络构建
要在P2P网络上做数据分发,就要构建一个鲁棒性的P2P网络——也就是动态可靠的P2P网络,构建这样一个网络需要分为三步:首先是连接——客户端之间要相互建立连接;第二是节点之间的评估;第三是节点分层。
- P2P连接
对于连接,穿越是一个非常头疼的问题,而我们在最初的版本也是基于NAT的方式来做的,但同时引来一个问题——穿越里只有60%,也就是说有可能在某个防火墙或者NAT后面的节点拥有很好的上传资源,却没有得到利用。因此我们通过STUN协议做了一个多端口的猜测机制,通过这个机制可以将整个穿越率优化到80%。但依旧有一部分无法穿透,这是由于有些厂商会设置黑名单机制导致无法穿越,因此我们设计了云端协调穿越时机的机制,从而将穿越率优化到大概90%,也基本达到我们的预期。
完成节点之间穿越,就可以做P2P通讯了。首先要建立心跳关系和心跳的状态交换,不过虽然从原则上来讲,一个节点跟所有的节点或者跟大部分节点能通讯是好的,但从实际效果来看并非如此,因为直播时有可能是一千个节点甚至更多节点,而一个节点不可能于这么多节点 频繁的做心跳或者信息交换,否则本身的带宽就会被这种探测包消耗掉,因此我们一般最多会选择40个节点,而这40个节点也并非是固定不变的,它会有优胜劣汰的机制,当有节点被淘汰时我们就会从没有穿越的节点中继续穿越,然后达到一个平衡,如此就会形成一种群居效应——好的节点会尽量聚集在一起。
- 节点评估
在确定了邻居和通讯状态以及探测机制后,我们就要对整个通讯做评估。评估分为两部分:一个是评价邻居,主要通过丢包率、RTT、通信命中率以及流媒体传输的稳定性计算出一个亲和度分值;第二就是评估自己,主要通过CPU、带宽复用、网络类型等计算出一个load(负载)值,通过它我们就能知道本地节点跟其他节点大概处于什么位置,我们就能进行网络策略调节、通讯的QOS、节点区分以及网络收敛。我们早期的网络收敛是通过对照表的方式实现的——也就是经验值,但在网络高度动态时,会出现网络波动以及与邻居之间的通讯失效,因此我们采用一个简单的神经网络和一个收敛函数f(x) 来做参数的调整决策。
- 节点分层
完成节点评估后,我们需要根据评估结果做节点区分,也就是要解决超级节点和普通节点的问题。超级节点最重要的作用就是分摊Edge server的分发压力,同时还可能承担一部分网络节点管理工作。超级节点的产生方式有两种:一种是自我推荐,这种方式它需要得到Edge server的许可;第二种是Edge server自身的分发压力过于繁重,就会从质量比较好的普通节点中挑选一些比较稳定的成为超级节点。由于网络是动态的,有可能会出现衰减、分割、断开的情况,因此超级节点并非是永久的,当超级节点的网络出现衰减,评估函数f(x) 会作出反应降级为普通节点,这其实是一个高度动态的循环过程 。
P2P分发媒体数据
成功构建网络之后就是如何做数据分发,我们将它分为三步:先推、后拉、再补偿,我们下面逐一做详细介绍。
- 三阶段——推
我们假定已经选举好一个超级节点,那么边缘节点就会按照上图向下推送媒体数据到超级节点,超级节点再向下推给其他节点或超级节点,这个推送本身是树型结构,而P2P是图形结构,因此我们引入了预先订阅的机制。举例说明,假设某个节点播放到媒体包的第十秒,它就要开始订阅第二十秒到三十秒的包。那么订阅是如何完成的呢?超级节点在确定自己身份时会确定一个区域值,那么普通节点就会向所在区域的超级节点订阅(有可能会向多个超级节点订阅),对前面的例子而言,节点有可能会向超级节点A订阅一三五七九秒的包,向B订阅二四六八十秒的包。而超级节点也会做预先订阅,比如对于超级节点A而言,如果它自身负责一三五七九秒的包,那么直接向Edge server订阅;相反则需要向负责对应分片 的超级节点做订阅。
有了这样的订阅关系,Edge server和超级节点就会根据订阅的记忆信息向下推送。而这个推送路径一般最多达到两层,因为三层的拓扑太长,会引来延迟,同时路径过长,一旦某一个节点退出,受影响的节点太多。
- 三阶段——拉
我们前面提到整个协议是建立在UDP之上的,而在推送过程中UDP有可能会出现丢包,这时我们就需要向邻居拉取,那么如何拉取?首先我们通过gossip协议确定拉取的目标:也就是在单位时间内定时发送本地缓冲区有哪些包,把这些包通过gossip协议交换出去,邻居节点之间就会知道其他节点包的信息,假如出现丢包的情况,就可以通过gossip信息找到需要去拉取的节点,而拉取节点的选择是通过前面提到的亲和度分值尽量选取近的、通讯友好的节点,而这个拉取过程有可能会是多次的,如果出现拉取失败,则会在一个RTT周期 之后向其他节点拉取,它是一个循环的随机过程。
- 三阶段——补偿
通过上述的手段我们就可以尽力把包拉取到本地,也可以减少对Edge server的依赖,但我们无法排除这样一种可能性,就是我的周围邻居都没有这个包,那么此时我们就只能向Edge server拉取,我们在前面有提到锚点的概念,Edge server应该具有大部分或者全部的流媒体信息。
向Edge sever发起补偿的条件一般有两种:第一种是在整个P2P网络中稀缺的包;第二种是迫切需要播放的包。但这里面还存在一个风险,也就是频繁的发起补偿请求其实对Edge server是非常大的冲击,尤其是大量存在稀缺块的时候 ,因此我们需要限制Edge server单位时间内的补偿请求,但由于有了这种限制可能会导致某些节点补偿失败,这时它就会通过前面拉取的过程,通过gossip协议做查找拉取 ,而不是所有的都向Edge server拉取,这也是为了防止Edge server瞬间被补偿请求打死掉。
- 三阶段播放buffer
当所有数据全部到了本地,本地会有一个播放缓冲区,包 括WebRTC都会有JitterBuffer这样的播放缓冲,我们也设计了一个Buffer,它与前面讲到的三阶段一一对应。
第一个是推区间,也就是负责推流区域的缓冲,相当于一个Push的JitterBuffer,这里设置的400ms缓冲主要是为了防止过度拉取,因为推送的流是不规则的,UDP会出现抖动、丢包、延迟,同时P2P多路径传输之间本身的问题会引来抖动,这个buffer就是为了防止太快进行拉取用的 。第二个是拉区间,在这个区间我们一般会向邻居拉取三次,这之间的缓冲也就是与邻居之间的3个RTT来回。如果三次没拉到,就会进入补偿区,补偿区就会向服务器拉取4次,如果没有拉取到数据则会返回拉区间。
经过这样的循环过程最终包被播放,对于已经播放的包是否要删除呢?我们知道P2P是对等系统,可能别人缺少你已经播放的包,向你拉取,但你播放结束后就直接删除了,这就导致没有命中,这种现象肯定不是我们希望看到的,因此我们将已经播放的分片放到缓冲区中保留3秒,尽量使得拉取请求命中。以上就是整个分发过程。
最终效果
下图是Edge server的对比数据,我们在Edge server上做了一个开关——将它等分为开启P2P分发和不开启P2P分发,对同一个Edge sever以一天为单位收集数据:每5分钟采集一次当时每秒出去的带宽,图中红色的线表示没有开启P2P,也就是完全使用Edge server来中转,蓝色的线表示开启P2P。图中我们可以看到在高峰期,开启P2P从Edge server输出的流量大概不到100mb/s ,而未开启P2P的则是达到了将近480mb/s ,也就是在 Edge server上可以节省到原来的1/4。
经过算法的升级,我们最终实现了这样的效果:边缘节点带宽大致降到24G;BGP由于采用了多链路保障和直链的方式,大概降到原来的1/3-1/4的比例;因为带宽的降低,对物理服务器的依赖也就自然减少,目前服务器降到41台左右。这里值得一提的是,通过测试我们所有的端延迟大概在1秒左右,最大延迟在2秒左右,连麦延迟在200~800毫秒之间 ,与原先的情况相比,在保留固有功能和连麦系统等服务没受到影响的情况下,我们节省了大概1/3的带宽,基本达到了我们最初的要求,同时我们也还在不断优化这个网络模型,也欢迎感兴趣的小伙伴与我探讨。
以上是本次的分享,感谢大家。
WebRTCon 2018 8折报名
WebRTC天然不具备服务端能力,如何实现高性能、稳定的服务端能力成为绕不过的话题。我们特别开设了“WebRTC服务端开发”专题,并邀请本文分享者袁荣喜担任出品人,一同与网易云、苏宁文创、触宝电话等技术大咖分享接入网关服务、协议适配、服务稳定性和行业应用。
如果你拥有音视频领域独当一面的能力,欢迎申请成为讲师,分享你的实践和洞察,请联系 speaker@livevideostack.com。更多详情扫描下图二维码或点击阅读原文。
本文分享自微信公众号 - LiveVideoStack(livevideostack)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。