Photo by Mídia from Pexels
直播业务的主要成本就表现在于带宽上,尤其是在用户要求日益提高的未来,带宽带来的成本问题会越来越大。本文来自陌陌流媒体高级研发工程师白松灵在LiveVideoStack线上分享的演讲,详细剖析如何仅仅借助传统CDN技术,不依赖于分片服务器实现简单有效的P2P技术。
文 / 白松灵
整理 / LiveVideoStack
直播回放
https://www2.tutormeetplus.com/v2/render/playback?mode=playback&token=369633933ec94b36b8102f4e838325e4
大家好,我是来自陌陌的技术工程师白松灵,非常荣幸能与大家一起来了解学习直播P2P技术,我将从以下几个方面展开今天的分享。
1. 简要介绍
1.1 传统直播模式
传统的直播技术尤其是一些最简单的直播技术,其主要依赖于主播端推送的RTMP流,或者其他流媒体格式的流数据至CDN进行转化分发,在观众端一般使用HLS、RTMP等各种协议,这是一个常规的直播数据传输模式,在该模式下主要的成本消耗都体现在CDN上,主要依赖的也是直播的成本带宽;我们引入P2P最主要的目的就是节省带宽。对于一个直播公司来说,大部分的成本消耗都是针对于带宽,我们今天引入P2P技术,就是为了实现与CDN的解耦合,
与传统直播模式相同,推流设备将RTMP数据推入CDN,数据流不需要分片也不需要中间其他服务器的转发,观众可拉取http/flv或RTMP数据便可进行分享,观众之间进行的是p2p(peer to peer)传输;数据从推流设备传输到CDN之后,P2P节点也就是每个观众都可作为分享端(也可以说是一个Peer节点)的边缘节点,这一边缘节点可提供分享数据,从而有效避免流量资源在服务器上的浪费,降低整套服务器的运维成本。
1.2 架构简介
该方案和原本的一些直播传输技术并不相同,其并不依赖于CDN公司提供的P2P技术方案。一般来说P2P CDN方案会将原本的协议进行转协议处理,或是将原本的数据进行分片。我们主要是一家业务公司而非云服务提供商,并不能在CDN上做太多修改,于是我们希望绕过CDN直接在端上或使用其他解决方案来实现P2P传输;实现推流及拉流均匀,同时与CDN解耦合。
该方案不与CDN存在必然关系,而只需要CDN支持传统直播功能。同时推流与拉流均基于RTMP协议,不会对原本协议作出更改;并且这套技术不会给我们的平台整体带来太大影响,包括卡顿率、秒开、延迟原本播放质量等;可实现在控制成本的同时尽可能降低复杂度,不会对手机本身性能造成过多负担,找到实现P2P功能的捷径。
2. 架构介绍
2.1 服务器架构介绍
假设直播房间里有1000个观众,如果客户端使用CDN拉流那么就算1人1MB的带宽,核算后整体下行带宽消耗也是个不小的数字;如果使用P2P技术,那么相当于原本从CDN拉流的A用户会从B、C等更多用户那里获取数据,达到CDN的功能并使得A用户不再依赖服务器。
我们把每个接入P2P的观众看作是一个Node节点,当用户进入系统或进入直播间时,用户端的信息会被上传至DiscoverService用于Node的注册、认证、查询与分类,DiscoverService主要用于用户的发现、给用户提供P2P分享表单、对每个节点进行分类等;CentreService则会被用于所有P2P相关自定义信息的交互与统计P2P的状态等,例如后期对P2P网络进行分析统计,当然还有转播功能与未来可能会对一些策略进行分析等;StunService则主要用于对Node提供打洞网络穿透服务。
接下来我们将详细介绍StunService,也就是如何实现网络穿透与打洞。
现在用户所使用的直播网络普遍建立在IPV4之上,但这对于绝大多数设备来说是不够用的,因此我们希望引入StunService。StunService是NAT的UDP的简单穿越,是一种客户机-服务器的网络协议,由RFC 3489 定义。该协议定义了一些消息格式,大体上分为Request/Response。这个协议主要作用是可以在两个处于NAT路由器之后的主机之间建立UDP通信。它允许位于NAT后的客户端找出自己的公网地址,确定自己位于的NAT是哪种类型,以及NAT为这个客户端的本地端口所绑定的对外端口。
一个公有IP可能内部有很多个内网IP一起使用,这就会造成在同样网络下的两段不同网络,内网之间需要透过一个NAT网络否则无法直接通信;而每个用户并不知道自己的外网IP的,所以也无法通过服务器直接暴露公网IP而后客户端直接连接的方法实现连接,此时就需要在两个处于NAT路由器之后的主机之间建立UDP通信,也就是打洞机制。
打洞机制的原理很简单:一个P2P节点A与一个分享节点建立连接并且开始尝试打洞,打洞时P2PNode A穿过很多层NAT服务访问StunService,StunService会获取其一系列的IP接口。同样分享节点SuperNode B也会访问服务器并且获得一系列IP接口,之后二者相互交换IP地址,开始尝试多种组合的打洞连接。最终确定一个IP和一个端口可以成功建立连接,二者打洞连接成功。
接下来我们需要知道NAT有哪些类型,一般来说NAT分为以下4种:
- 完整锥型NAT(Full Cone NAT)
内网的ip或者port会映射到一个Nat的端口上,通过发送数据包给NAT上转换后的ip和端口之后,NAT会自动将数据包送到机器A。NAT 对发送给A的数据包来者不拒,不过滤。
- 受限锥型NAT(Restricted Cone NAT)
同样都会映射到一个NAT的端口上,但是只有当内网Endpoint1曾经发送过报文给外部主机ip才会接受,否则会拒绝。
- 端口受限型NAT(Port Restricted Cone NAT)
同样都会映射到一个NAT的端口上,但是只有当内网Endpoint1曾经发送过报文给外部主机ip和端口才会接受,否则会拒绝。
- 对称型NAT(Symmetric NAT)
每次传输都会映射新的端口
前面三种 NAT 是可以穿透的,但是,对称型NAT 无法穿透。
一般来说NAT存在保护机制,假设用户A在一个NAT下访问外网的一个端口或一个IP,可以直接向外访问;而如果从该外部端口访问内网则会被拒绝。
对于对称型NAT或者其他打洞不成功的网络,每一个P2PNode每次都会与多个可分享节点进行连接,由于进行了多组尝试,加大了打洞的成功率。并且为了确保播放的流畅性只有确保p2p数据通道建立成功并且有正确数据流入才会关闭cdn拉流;除此之外,我们并没加入TURN服务器,毕竟是服务器做转发同样消耗带宽。
打洞成功之后P2PNode可能会与其他多个分享节点建立连接并分享数据,如何在分享数据的时候保证数据对齐?
为了确保流播放的流畅,目前的解决方案是只保证一个连接是真正通信的而其他连接都是备用通信;A用户进行分享,会与B、C、D、E、F等建立连接而只会与B进行长时间的有效通信;当A发现B出现网络波动或不稳定,则会立刻切换到C或者其他更加高质量的链路;同时其他所有连接均保持存在,P2P端口都没有中断。
当然,这里同时建立连接的端口数量不能太多,一般是5~6个,因为如果端口太多则会导致占用NAT端口太多,也会引发一系列网络问题。
2.2 客户端架构简介
整套系统的重点是客户端的实现,与CDN完全解耦合。这里在播放器外部提供了一个中间层的概念,客户端以本地代理的方式,将原本的http播放地址提供给本地代理模块,代理会提供一个转换后的本地播放地址供播放器播放。其中代理模块的内部会确定p2p切换,对于播放器本身是不感知的。但这种方案在播放器内部并不安全,内部我们需要安全性更好的分享机制,
在播放器内部,我们会把P2P的分享节点分为以下三类:SuperNode、NormalNode以及P2pNode。P2P传输期间会在这三种模式之间不停切换,
- SuperNode:可分享节点
它会根据当前的网络状态,例如客户端的网络状况、播放时间等因素稳定之后,通知状态切换为可分享态。例如当用户稳定连接Wi-Fi或长时间处于直播间内,这些数据会被上报至list,也就是CenterService在DiscoverService那里进行备案已记录该节点可分享;当有另一个P2P用户进入直播间需要获取分享列表时,CenterService会基于SuperNode记录的这些可分享节点,提供给该用户一个节点列表。
- NormalNode:普通节点
目前不能提供分享不过可以向discover请求p2p连接的节点,例如用户使用上行带宽资源非常受限的移动网络,不能作为资源分享节点但可以正常获取数据。
- P2PNode:正在利用分享端数据播放的节点
以上三种节点之间可以相互切换,如果可分享节点的网络状况稳定,那么该节点会被提供给多人作为分享端;P2P节点会向discover服务器获取一个排序后的可分享节点表单,其中包括一些关键信息、IP或运营商信息等。;P2P节点会根据详细的排序列表,同时向这个表单中的多个节点进行连接,数量大约为5个,保证数据的完整。同一时间p2p节点只会与一个分享节点数据传输其他节点备用,如果网络稍微出现异常就会切换其他节点;以往的BT形式的P2P,A用户可能会向B用户索取一段切片好的数据,再向C用户索要一片数据并尝试拼在一起;如果拼接成功则播放,如果未成功则回源到CDN拉流。在我们的架构中,如果系统发现当前播放器的缓冲已经低于阈值就会切换回cdn拉流,当然这里会有一定的数据浪费。我们这套方案原本设计就是只依赖传统CDN服务器,不会包括分片、转码等仅是依赖于CDN或服务器工作。例如切片则主要使用DASH或HLS、边缘节点进行分片,主要基于现有P2P CDN解决方案,这里不再赘述。
P2P拓扑结构非常多,可能采用圆形或环形拓扑结构;但由于P2P是直播,对实时性要求非常高,所以如果还按照原本的那种环形结构则会造成数据的混乱。我们通常采用树形图形,但如果用户太多,在该结构下则会导致用户延迟变大,当然也不便于管理。
正常来说,CDN不会关注主播提供的RTMP时间戳,因为如果推流中断则需要重新推流,此时对于CDN来说RTMP时间戳可能会清零;所以RTMP中的时间戳不可靠,对于观众端来说则必须要有个依赖,P2P分享必须有对应的时间戳进行数据同步,这里我们引入了H.264、H.265中的拓展字段sei。
Sei字段并不是一个标准协议内部必须的东西,当NAL Unit Type为6时,其实就是把一个自定义时间戳添加到信息当中;这也是我们的一项缺点,也就是我们必须依赖于推流添加上这个字段,否则就无法实现.
一般来说sei会按顺序排列,具体序列如上图所示。为了确保P2P稳定性,GOP越小越好。这样就解决了用户A和B分享,A向B发送一个sei关键时间点,B就会给A提供其所想要的数据。这里并不采用BT的并行方式传播而是顺行方式,实际上sei就是一个顺序列表。A会和B连,B无法连接就与C连,C无法连接就与D连,以此类推。
A会确保尝试尽可能多的连接,如果尝试所有连接之后依然不成功那么则会切换到回源,当然这里我们会尽量避免切换回源CDN,因为质量优劣完全取决于回源,回源多则会造成P2P质量较低。
由于这里RTMP时间戳不再使用,所以我们会重新计算时间戳。相应的这套时间戳其实就是计算Δt的时间增量,根据增量与常用码率重新计算Δt给到播放器。sei只在视频上被使用而在音频上并没有,所以时间的间隔其实也是根据视频的Δt来计算的。数据在本地存储时会根据Sei中的时间戳对每组Gop进行最多8个gop的数据存储,每当提供分享时分享端会根据播放端提供的Sei时间戳进行查找如果有则进行分享没有则关闭连接。
上图展现了基本传播方式,首先p2p通过CenterService向DiscoverService获取可分享列表,打洞成功之后发起订阅请求,如果订阅成功才会建立私有信息的传输;分享列表判断Sei是否正确,并决定返回正确数据或者断开。
3. 分享节点的选择
选择分享节点时,CenterService与DiscoverServer交互并上报自身信息 。这些信息包括:
(1)满足分享节点上报信息:
- 网络条件允许(WIFI) 自评之后服务器评价;
- 在直播间内持续一定时间
(2)不满足分享节点了注销信息
- 退出直播间
- 网络情况不允许(数据长时间不够)
(3)心跳上传信息
- 更新自己的信息,测评作为分享节点的能力
上报后与DiscoverServer交互,获取在线的分享者列表 ,此时用户使用直播间ID检索,获取在线的分享者用户列表。根据上传信息打分,以一定排序规则获取这些信息如运营商信息、带宽、网络、CPU、内存、分享次数等;同时返回的用户列表个数支持可配置,通常在5个左右。
最后,分享节点的排序基于一定逻辑:首先,相同IP排在最优先的位置,接下来同城市运营商排在前,例如成都联通排在最前面;同城市的不同运营商次之,不同城市的不同运营商排在最后另外,我们会对不同运营商之间进行NAT穿透成功率的对比,同时建立连续打洞失败使用黑名单,当运营商不同时,我们会根据之前得到的成功率经验按照优先级来排序。
4. P2P技术融入直播场景
下图展示的就是P2P技术融入直播场景下某个直播间的分享信息,其中蓝色区域代表P2P拉流,绿色区域代表CDN拉流。对比详细分享率占比,随机某个较大的直播间在高峰期的分享率可达到 53.2% (图中是未使用p2p的占比),整体的带宽可以减少40%。
展望未来,我们希望进一步优化节点之间使用UDP传输优化带宽自适应或实现更加细致的网络评估,也会引入RTC的一些特性,如提前预测带宽以保证连接承载的时间更长;同时我们也希望用到更多的拓展协议,在未来拓展协议的更多可能。
点击“阅读原文”即可观看视频回放