天天看点

P2P技术将直播带宽降低75%

实时直播经过去年的千播大战后已经成为互联网应用的标配技术,但直播平台的成本却一直居高不下,各个平台除了挖主播、挖网红以外,其背后高额的带宽费用也是他们最大的一块成本。现阶段直播技术在传输方面分为两块:CDN和连麦系统,CDN负责流媒体的分发传输,连麦系统负责解决同时多个主播间互动的实时通信传输问题。我们始终认为基于CDN+连麦系统的直播技术是一个高成本高消耗的技术,从各大直播平台纷纷亏损就验证了这一点。除了带宽成本,延迟问题也是现在直播技术一个硬伤。我们很早就意识到现在传统的直播技术是无法大规模进行在线教育互动直播,所以学霸君从2016年下半年就开始研发基于UDP和P2P技术的互动直播技术架构。整个系统的设计目标是:

1.      端到端延迟控制在秒级范围之内。

2.      在不影响视频质量的情况下尽力节省分发带宽。

整个基于P2P技术的分发架构在一个10W+直播平台上进行了9个月的测试和调优,初步达成了设计目标。那整个系统是怎么设计的?使用了那些技术来达成目标的?那接下来重点来说一说架构设计和技术细节。

2.P2P分发网络架构

         整个传输分发网络我们把连麦系统和分发系统合二为一,将分布式推流与边缘节点分发作为一套传输体系,通过服务之间的P2P通信和路由选择来实现连麦的最小时延,架构如下图:

图1

         整个传输分发网络分为三部分:推流部分、服务之间P2P和客户节点P2P。这个传输网络有一个系统锚点:假定推流者speaker推到Edge server上是不会发生丢包和延迟的,Edge server会通过服务间P2P快速将收到的流数据分发到其他的Edge server,而且在这个过程也不会发生延迟和丢包。为什么需要这样一个锚点?因为在客户节点的P2P网络需要保证流畅性和最小延迟,也就是要求所有的Edge server在最短时间周期内拥有的完整的数据,至于为什么要这样,后面我们在流补偿环节重点介绍。

         我将通过整个流数据传输过程来解析具体的技术细节,但在这之前首先要解决的就是媒体数据分片问题,所有的传输过程会基于分片(segment)来设计。

2.1媒体数据分片

         媒体数据分片是整个分发传输体系中最为基础的部分,我们在设计分片时主要考虑的是时延和消耗的问题,分片如果太大,传输的时延就会越高,例如:HLS。如果分片太细,网络中回馈报文就会很多,对P2P网络来说额外的消耗也是个问题。最后我们借鉴了RTP和FLV中的经验,采用按帧来做数据分片,这样做有以下几个好处:

ü  按帧分片延迟粒度小,可以在帧传输进行延时优化

ü  实现简单,与编解码器编码原则一致

ü  组合灵活,可以实现播放buffer无缝结合。

每一个分片称作为segment,用一个自增长的32位ID来表示唯一性,传输过程都是以这个ID为标示来确定数据的完整性。

2.2推流与连麦

         确定好了媒体分片就可以进行推流了,我们把推流和分发的路径合二为一,上麦者是将流数据segment推送到离自己最近的Edge server上,而不是推送到专门的连麦系统上。我们推流传输使用的是RUDP传输算法,这个RUDP是采用了类似BBR基于延迟和丢包来设计的拥塞算法,并且对报文做了拥塞丢弃,示意图如下:

图2

关于RUDP的细节可以参考我的另一篇文章《UDP是怎么实现可靠的》。至于为什么不采用RTP或者RTMP/TCP来推流,RTP虽然是基于UDP的,但需要通过RTCP和NACK来保证可靠性,需要设计拥塞算法,也需要对RTP进行改造扩展,而且还受RTP协议本身的限制,所以我们并没有直接采用RTP。对于RTMP/TCP设计是很简单,但在弱网环境延迟很大,而且容易引起重连,所以在设计之初就否定了。

2.3server间的P2P

         因为整个传输分发网络是分布式的,由多个edge server组成,而且其他的连麦者可能在其他的edge server上。所以基于系统锚点,媒体数据分片到edge server上必须尽快的分发到其他的edge server上。最早我们采用的是统一用BGP server来中转,这样的耗费的BGP带宽很多,而且BGP server一旦异常,整个edge server之间的通信就中断了。其实大部分时间跨运营商之间的edge server之间延迟也没有想象的那么大,这可以考虑使用Edge server之间点对点通信来解决问题,所以我们设计了一个基于RUDP无窗口多路径的传输模型来进行edge server之间的通信,如下图:

图3

上图的通信模型是一个多路径并联通信模型,我们在RUDP发送前添加了一个路径路由表,这个路由表记录了各个路径的分发概率,RUDP每次向接收端发送包时会通过路由表中的概率来选取路径,那么确定路由表概率就是一个非常重要的事情。我们是通过RUDP实时的ACK反馈和路径实时ping探测来得到网络的状态(丢包,延迟,抖动),再将网络状态参数输入到逼近函数来确定各条路由的概率,这里有条原则:如果edge server之间直连的延迟和丢包足够小的情况下,直连通信路由的概率将会接近100%,如果某一条路由出现周期性断开或者延迟超过200ms,它的概率会接近0。以下是整个路由概率评估的过程示意图:

图4

3.P2P网络构建过程

         媒体流数据通过edgeserver间的P2P多路径传输网络到达各个edge server上,接下每个edge server需要将流数据分片下发到各个客户节点上,我们针对了上麦节点做了传输特殊处理让时延更小,过程和普通的RTC通信模型相似,这里就不赘述了。观看节点上分发采用的自组织P2P网络进行数据分发的,既然是通过P2P下发的,那么就要在客户节点群构建一个P2P网络,这个网络是怎么构建的?具体分为三步:连接、评估、分层。

3.1连接

         客户节点程序是运行在客户机上的,大部分客户节点都会在路由器或者NAT后面,他们之间要相互建立连接,必须穿越彼此的NAT和防火墙。虽然现在穿越NAT的方法,例如:STUN、ICE等。但穿越连通率始终是一个问题,如果穿越率太低,会让很多优质的节点资源得不到充分利用。在设计穿越方案时直连连通率放在第一位,我们通过修改STUN协议设计了一种基于端口多次猜测和尝试的穿越机制,先会通过类似STUN协议判断NAT类型、NAT端口变化规律、NAT是否有黑名单机制等信息,然后将这些信息存到辖区连接中的edge server中。当有伙伴节点来与它穿越,会交换彼此的这些信息,不同的排列组合会有不同的穿越策略,每一次穿越的过程和结果都会记录到我们的后台数据库,我们会周期将这些数据进行分析并调整协商穿越策略。如下图:

图5

         穿越完成后,节点之间就会进行连接握手和身份证书(关于为什么要证书后面细讲),建立通信联系和邻居关系。那么邻居关系是怎么动态变化的呢?

3.2邻居关系与评估

邻居问题

         连接一旦完成,节点与节点之间就成为邻居,彼此会进行状态交换和心跳,那么问题来了,一个直播系统有成千上万的节点参与,如果都两两相连的话光心跳通信就可以将客户节点的上传带宽占满。我们基于这个原则设计了一个节点LRU淘汰链表,链表中保持40个联系的邻居节点。但老的节点会退出,新的节点会加入,LRU会根据邻居与自己的通信状态来进行LRU新增和淘汰,原则如下:

就近原则,内网优先,同城同一营商网络次之。

周期性评测延迟和媒体分片命中率,末位劣汰。

当LRU列表中节点不足40个时会从备用节点列表中选取进行新的节点进行连接并加入到LRU中。

节点评估

         每个客户节点的计算能力、通信能力、网络分区等都不一样,这使得我们必须对每个节点做一个评价,对一个节点的评价分为两部分:邻居节点对自己的评价和自己对自己的评估。邻居评价主要是:

RTT

丢包率

请求命中率

通过这三个参数会对每个邻居计算出一个亲和力分值score,这个值会用于后面的分发选择。

评估自己主要是:

CPU、内存

网络类型,WIFI/4G/有线网络

上传带宽

节点会周期性计算这两类参数,通过一个网络QOS收敛函数来计算节点能力和对邻居QOS策略。

3.3节点分层

         节点评估最终的目的是让有能力的节点成为超级节点(super node)来分担edge server的分发压力。那么一个节点成为超级节点的条件是什么呢?有以下几个条件:

有足够的上传带宽,4G和弱WIFI下不能成为超级节点

有空闲的CPU和内存,计算能力不够的低端移动设备不能成为超级节点

对邻居通信友好,不是通信孤岛

得到edge server的任命,和edgeserver之间通信顺畅。

超级节点如果性能衰减了怎么办?答案是会退化成普通节点,因为节点评估是周期性实时进行的,如果发现节点性能衰减,edge server会让其退化。

         既然任命了超级节点,那么超级节点是怎么工作的?每一个超级节点在被任命时都会分配到一个分组ID,edge server会根据自己辖区超级节点数量进行分组,每个分组有多个超级节点组成,分组内的超级节点群负担自己分组的媒体分片分发。例如:有5个超级节点分组,这时单位周期内有1 ~ 20个segment,那么第一个分组负责1、6、11、16编号的segment分发,以此类推第二组负责2、7、12、17 …。这样做是防止单个超级节点失效造成网络,增强P2P分发的稳定性。示意图如下:

图6

4.P2P流媒体分发过程

         通过上面的P2P网络构建过程我们知道整个P2P网络其实是一个分层有向图分发网络,那么具体是怎么进行流数据分发的呢?也分三步:先推(push)、后拉(pull)、再补偿,下面来仔细解释是怎么实现的。

4.1push

         在介绍超级节点时有提到会根据segment ID将数据推到对应的超级节点分组群上,超级节点收到这些segment后怎么要怎么处理呢?按照P2P设计的原理应该将数据转发到其他分组的超级节点或者普通节点上,但是如果都这样推有可能会造成网络发送冗余而消耗过多的带宽。为了解决这个问题设计了一个预先订阅机制,原理就是每个P2P客户节点会根据自己缓冲区最大的segment ID来进行预订,提前预订10秒以后的媒体数据分片,预订请求是根据节点评估出来的亲和力值score来做权衡随机请求,收到这些请求的超级节点会将预订的分片请求信息保存下,等到edge server推送到这个分片到这个超级节点,它就会无条件转发这些被预订的报文给发起预订的节点,如下图所示:

图7

从上图中可以看出以下几个原则:

从edge server到所有节点路径最多两层,这样做是为了控制链路延迟

不同分组supernode之间会相互订阅对应分组的segment

普通node只会向super node发起订阅

4.2pull

         数据segment通过预先点阅的方式进行push推送到各个客户节点,但网络是会丢包,super node也有可能会中途退出,这样就会造成最终的节点发生丢包,那丢包了我们怎么办?我们设计一个向邻居拉取缺失分片的机制,大致的流程如下:

1.      节点周期性检查丢失分片的信息和收到分片的信息,构建一个gossip协议向邻居交换缓冲区信息

2.      节点收到邻居的gossip信息,将对方拥有的分片信息记录到本地

3.      本地根据记录邻居的分片信息查找自己丢失的分片,通过邻居亲和力值score进行权衡随机选取邻居,并向选取的邻居发起pull请求

4.      收到邻居拉取分片请求,将分片发往请求的节点。

整个步骤会周期性尝试多次拉取,示意图如下:

图8

         这里值得一说的是因为会周期性交换缓冲区的gossip信息,这意味着缓冲区的gossip信息越小越好,我们设计了一个类似bloom filter来描述gossip信息,不仅可以减小gossip报文的数据大小,而且比对速度也很快。

4.3补偿

         因为P2P的客户节点是不稳定的,有可能某个segment通过拉取多次还是没有收到,这个segment又临近播放位置,那么缺失这个segment的节点会直接向edge server请求补偿让其尽快传送这个分片,这样做的目的是防止因为P2P通信造成丢包的卡顿。这也就是说每个edge server需要拥有所有分片数据,也是系统锚点。流程入下图:

图9

这个流程大部分情况下没有问题,但如果同一时刻大部分客户节点都缺失某几个segment分片时,会有大量的补偿请求到edge server上会造成网络风暴。我们在应对这个问题设计了个稀缺评估和拒绝服务的机制。这个机制当单位时间内太多个补偿请求到达edge server会拒绝自己能承受之外的请求,只重发承受范围之内的分片。而且这个过程还会对补偿请求做稀缺评估,如果某个分片大部分节点都没有,它会主动将这个分片通过super node群再推送一次。

4.4缓冲buffer与时延控制

         通过上面的三个阶段可以将所有数据segment分发到每个客户节点上,但客户节点需要一个缓冲buffer来配合这个三个阶段和本地的播放,buffer如果缓冲时间过长,会引起不必要的延迟,如果过短会造成卡顿和三个阶段不完整。为此我们设计了一个三阶段buffer动态缓冲区,入下图所示:

图10

来解释下上图的各个区间的意思:

          Push区间:因为分片是通过不同的supernode推送过来的,那么必然会造成一定的抖动,所以在buffer最开始的头上会有一个jitter缓冲阶段,直到第一个邻居节点gossip信息中有这个分片push位置结束,这个阶段一般100 ~ 300ms。

          Pull区间:分片时序进入pull区间后,会周期性检查丢失的分片,根据gossip掌握的邻居进行权衡拉取,会进行3次尝试,每一次尝试时间是本地节点与邻居之间的RTT值。3次失败进入补偿区间。

          补偿区间:分片时序进入补偿区间后,也会周期检查丢失的分片,根据丢失的分片ID直接向edge server请求拉取,尝试4次,每次尝试时间一个本地节点与edge server之间的RTT。如果4失败进行waiting状态,等待邻居gossip或者edge server主动推送。

          过期区间:被播放后的分片会放到这个过期区间中而不是立即删除掉,为什么呢?因为每一个节点的播放时间点不同,有可能本地播放的分片正是其他节点丢失的分片,有可能其他节点会通过pull来拉取,所以我们会把播放后的分片放在过期区间3秒后再删除。

4.5秒开问题

         上面分发的三个阶段和buffer控制解决了流持续分发和播放延迟控制问题,但现阶段所有的直播技术必须要有秒开,其实P2P分发在解决秒开比单纯的server CDN转发来的更加简单。秒开就是用户进入直播间时瞬间能看到主播的视频图像,那秒开的宗旨是新进入的客户节点要求服务端边缘节点从视频的上一个GOP关键帧开始发送数据,客户节点再根据视频编码器从这个GOP关键帧0等待加速播放即可 。我们在P2P分发网络中新进入的节点会收Edgeserver的上一个GOP关键帧分片ID,客户节点根据这个ID从各个邻居中快速拉取整个GOP分片数据,而不是单纯的让Edge server来发,秒开的速度平均缩短了100 毫秒。

5.P2P内容授权

         直播分发技术除了传输分发以外,还需要考虑内容防盗和授权,P2P系统中更加需要考虑系统安全性。我们引入了CA证书和双端协商加密方案来保证链路的合法性。大致的做法是每个合法的节点单元(edge和所有的客户节点)会向CA发起合法校验,如果检验通过,CA会根据节点的ID、节点RSA公钥、授权起始时间、授权终止时间等信息利用CA的RSA进行证书生成。每个拿到证书的节点单元需要和其他的节点进行通信,会先交换证书,校验对方证书的合法性,让后利用证书中的RSA公钥加密一个对称加密算法的KEY返回给证书方,证书方收到加密的KEY会用RSA私钥解密得到对称加密的KEY,这样双方就完成了合法性校验并利用这个交换的KEY进行报文加解密通信了。流程如下图:

图11

6.线上数据对比

         上面的技术分析只是帮助读者这理解这个系统的运作机理,除了这个以外,当然需要公布下线上数据来佐证下系统可行性,下图是一个10万+在线使用了这套P2P的直播平台线上对比数据。我们在同一个edge server上的同一个直播间对象中,把一半的用户节点关闭P2P,一半的用户开启P2P,来观察一天中同一个Edge server上这两部分用户群的带宽消耗情况。