首页 > 代码库 > 直播平台的相关技术(转载)
直播平台的相关技术(转载)
参考文章:
http://chenbowen.baijia.baidu.com/article/472127
http://blog.csdn.net/cabbage2008/article/details/50582899
大流量、高并发场景下,大型直播的技术挑战一般体现在如下几个方面:
视频流的处理、分发
播放质量保障
视频可用性监控
超大直播间实时弹幕及聊天互动
高性能消息通道
内容控制,如算法鉴黄、文本过滤
系统可用性、稳定性保障
本文将针对其中的一些技术细节,抽丝剥茧,希望通过些许文字的分析和介绍,能让大家有所启发。
视频直播
对于直播平台来说,为了保障各种网络环境下能够流畅的观看视频,需要将高清的输入流转换出多路不同清晰度的视频流,以支持不同网络条件下视频清晰度的切换,而由于不同的端所支持的协议及封装格式并不完全相同,比如无线端的HTML5页面可以很好的支持HLS协议,但是对于RTMP这类协议基本无能为力,而PC端为了降低延时,需要采用RTMP这一类流媒体协议。
因此,为了支持多终端(PC、Andriod、iOS、HTML5)观看,需要对输入流进行编码及封装格式的转换。转码完成之后,还需要对视频流进行分发,毕竟源站的负载能力有限,节点数有限,离大部分用户的物理距离远,对视频这一类十分占用带宽资源的场景来说,为了提高播放质量减少卡顿,需要尽量减少到用户的传输链路。
因此,通常的做法就是将视频流进行切片存储到分布式文件系统上,分发到CDN,或者是直接通过CDN进行流的二级转发,因为CDN离用户最近,这样才能保证直播内容对于用户的低延时,以及用户的最短路径访问。客户端对延时的要求,以及采用何种协议,决定了视频是否需要分片,分片的目的在于,通过HTTP协议,用户不需要下载整个视频,只需要下载几个分片,就可以播放,实际上直播与录播的技术是相通的,区别在于直播的流无法预测终结时间,而录播的视频流终止时间是已知的。
对延时要求没那么高的场景来说,客户端可以采用HLS协议,毕竟IOS、Andriod、HTML5等无线端应用能够很好的兼容和支持,且常用的静态资源CDN可以不做相应改造,就能支持,但是HLS协议的一大天生缺陷就是延时较高,视频内容在切片、分发、客户端下载的过程中耗费了很长时间。对于时效性要求非常高的场景,就需要采用RTMP、RTSP一类的实时流媒体协议,来降低延时,并且,为了降低源站的压力,需要CDN边缘节点来做流的转发,那么,CDN就必须得支持相应的流媒体协议,也就是通常所说的流媒体CDN。
由于直播流由主播上传,如何控制违法违规内容特别是黄色内容,成了十分棘手的问题。令人欣慰的是,随着技术的发展,算法对于黄色图像的识别准确率已经很高,基本达到可以在生产环境应用的程度,因此我们也尝试在视频流分发之前,对视频帧进行提取,并且将图像交给算法进行识别,当超过预设的阈值时,可进行预警或者关停直播间。经过一段时间的实践,取得了一定的效果,降低了人力成本,但不可避免的是图像识别算法时间复杂度高,吞吐率较一般算法低。
直播整个链路是比较长的,包含有接流、转码、切片、分发、客户端下载、播放等众多环节,链路上任何一个节点有问题,都可能导致视频不能播放。因此,关键节点的监控就十分重要,除此之外,还需要对整个链路的可用性进行监控,比如,针对HLS协议来说,可通过监控相应的m3u8索引列表有没有更新,来判断视频直播流是否中断。
当然,如需判断视频流中的帧有没有花屏、有没有黑屏,就更复杂了,况且,监控节点所访问的CDN节点与用户所访问的CDN节点可能并不在同一地域。当前中国的网络环境,特别是跨网段的网络访问,对于流媒体应用来说,存在较大的不可控因素,客户端网络接入环境对视频的播放有决定性影响。
因此,收集终端用户的播放数据质量数据进行反馈,及时进行视频清晰度切换, 显得特别重要。这些数据包括客户端的地域分布、播放卡顿信息、视频分片加载时间等等,根据这些信息反馈,可以较为全面的评估CDN节点部署是否合理,是否需要新增CDN节点,视频的转码参数对于不同机型的兼容性等等,及时进行调整以改善用户体验。
消息/弹幕
WEB IM应用及弹幕近年来有越来越火的趋势,是营销与气氛活跃的一种非常重要的手段。对于同时在线人数庞大的实时聊天互动、实时直播弹幕这一类场景来说,在保障消息实时性的前提条件下,将会面临非常高的并发压力。
举个例子来说,假设一个活跃的直播间有10w人同时在线,正在直播一场热门的游戏赛事,假设每秒钟每个人说一句话,将会产生10w条消息,也就是10w/s的消息上行QPS,而每条消息又需要广播给房间里面的每一个人,也就是说消息下行将成10w倍的放大,达到惊人的10w*10w=100亿/s的消息下行QPS,而这仅仅是一场直播的QPS,类似的直播可能有多场正在同时进行,对于消息通道来说,无疑将是一个巨大的挑战。因此,在系统设计的时候,首先要考虑的问题,就是如何降低消息通道的压力。
用户将信息投递到消息系统之后,系统首先对消息进行一系列的过滤,包括反垃圾、敏感关键词、黑名单等等,对于信息的过滤后面会详细介绍,此处暂且不表。为了避免系统被瞬间出现的峰值压垮,可先将消息投递到消息队列,削峰填谷,在流量的高峰期积压消息,给系统留一定裕度,降低因限流丢消息对业务产生的影响。
而后端始终以固定的频率处理消息,通过异步机制保障峰值时刻系统的稳定,这是一个典型的生产者—消费者模型。对于消息的消费端,则可采用多线程模型以固定的频率从消息队列中消费消息,遍历对应房间所对应的在线人员列表,将消息通过不同的消息通道投递出去。多线程增加了系统的吞吐能力,特别是对需要将消息一次性投递给几万上十万用户这样的场景,可以异步使用大集群并行处理,提高系统的吞吐能力。异步使后端的消息投递可不受前端消息上行峰值流量的干扰,提高系统稳定性。
除了采用消息队列异步处理之外,当房间人数太多,或者消息下行压力太大的情况下,还需要进一步降低消息下行通道的压力,这就需要采用分桶策略。所谓的分桶策略,实际上就是限制消息的传播范围,假设10w人在同一个房间聊天,每人说一句可能瞬间就会排满整个屏幕,消息在这种情况下基本没有可读性。
为提高信息的可读性,同时也降低下行压力,可将每10000人(具体每个桶的容量可以根据实际需求来调整,这里只举例)放一个桶,用户发送的消息,只在一个桶或者部分桶可见,用户按照桶的维度接收消息,这样一方面前端用户接收到的消息量会少很多(跟用户所处的桶的活跃度相关),并且一条消息也不用发送给所有用户,只发送给一个或者部分桶,以降低消息下行压力。
分桶的策略有很多,最简单粗暴的方式就是根据用户随机。首先根据房间的活跃程度,预估房间该分多少个桶,然后将用户通过hash函数映射到各个桶,随机策略的好处是实现非常简单,可以将用户较为均衡的分配到每个桶,但是会有很多弊端。首先,准确的预估房间的活跃用户数本身就比较困难,基本靠蒙,这将导致单个桶的用户数量过大或者偏小,太大就会增加消息链路的压力,而偏小则降低用户积极性,后期调整分桶数也会很麻烦,需要将全部用户进行重hash。
另外从用户体验的角度来考虑,在直播过程中,在线用户数正常情况下会经历一个逐渐上升达到峰值然后逐渐下降的过程,由于分桶的缘故,在上升的过程中,每个桶的人是比较少的,这必然会影响到弹幕的活跃度,也可能因此导致用户流失,而在下降的过程中,逐渐会有用户退出直播,又会导致各个桶不均匀的情况出现:
另一种方案是按需分桶,固定每个桶的大小,当现有的桶都满了之后,再开辟新的分桶,以控制每个桶的人数,使其不至于太多也不至于太少,这样就解决了之前可能出现的每个桶人数过少的问题,但是,有个问题将比之前的随机分桶更为明显,老的桶中不断有用户离开,人将逐渐减少,新开辟的桶将越来越多,如不进行清理的话,最后的结果仍然是分桶不均衡,并且会产生很多空桶,因此,就需要在算法和数据结构上进行调整:
通过一个排序list,每次将新增用户添加到人数最少分桶,这样可以让新用户加入最空闲的桶,以保持均衡,当桶满的时候,就不再添加新的用户,但是,当老用户离开的速度大大高于新用户进来的速度时,桶还是不均匀的,这时,就需要定期对分桶进行整理,以合并人数少的桶或者回收空桶,而合并的过程中,新用户又会不断的加入进来,并且,还需要保证消息发送时能读到正确的用户列表,在分布式高并发场景下,为了保证效率,有时候加锁并不是那么容易,这就有可能出现脏写与脏读,桶的整理算法将会非常复杂,有点类似于JVM中的内存回收算法。与大数据量高并发场景下的分库分表策略类似,实际上分桶策略也是一种取舍权衡与妥协,虽解决了原有下行通道压力过大的问题,也引入了新问题。首先,分桶改变了原本普通用户对于消息的可见性,一条消息只对于部分桶的用户可见,而非所有桶的用户,这样不同的桶内的用户看到的消息可能是不同的,另一个问题是,以上的分桶策略有可能导致“热门桶”和“冷门桶”效应出现,即可能将很多“吐槽达人”分配到同一个桶,导致该桶的氛围十分活跃,而其他不那么活跃的用户分配到一起,以致于出现“冰火两重天”的局面,从而影响产品体验。
当然,对于部分特殊的消息,如系统公告内容,或者是部分特殊角色(房间管理员、贵宾、授课的老师等等)所发送的消息,这一类消息需要广播给所有用户,这种情况下就需要系统对消息类型做区分,特殊的消息类型另作处理。
对于消息投递任务来说,需要知道消息将以什么方式被投递给谁,这样就需要动态地维护一个房间的人员列表,用户上线/下线及时通知系统,以便将用户添加到房间人员列表或者从房间人员列表中移除。用户上线十分简单,只需要在进入房间的时候通知系统即可,但对于下线用户的处理则有点折腾,为什么这么说呢?用户退出直播间的方式可能有多种,关闭浏览器tab、关闭窗口、关闭电源、按下home键将进程切换到后台等等,有的操作可能可以获取到事件回调,但也有很多种情况是无法获取事件通知的,这样就会导致人员列表只增不减,房间的人越来越多,消息投递量也随之增加,白白的浪费了资源。为解决这一问题,就需采用心跳。
心跳指的是客户端每隔一段时间向服务端汇报在线状态,以维持服务端的在线人员列表,当同时在线人数达到一个很大的量级(如百万级)的时候,每秒心跳的QPS也会变得非常之高,如何保障心跳的高效率、高吞吐就成了岑待解决的问题。首先是通信协议的选择,是HTTP协议,还是WebSocket,还是TCP协议或者其他。
HTTP协议的好处在于兼容性及跨终端,所有浏览器、Andriod、IOS的WebView,都能很好支持和兼容,在目前移动重于PC的大环境下,显得尤为重要,但是HTTP协议劣势也是显而易见的,作为应用层协议,单次通信所要携带的附加信息太多,效率低。WebSocket在移动端的场景下比较合适,但是运用在PC端,需解决众多老版本浏览器的兼容性问题,socket.io的出现则大大简化了这一原本非常复杂的问题。TCP协议在传统的客户端应用上使用较多,但是对于WEB应用来说,存在天然障碍。使用WebSocket和TCP协议的好处显而易见的,通信效率会比HTTP协议高很多,并且这两种协议支持双工通信。
另一个问题是后端存储的选择,该使用怎样的存储结构来存储在线人员列表这样的数据结构,以支撑这么高的并发读写。经过优化并且使用SSD的关系型数据库相较以往性能已经有了很大的提升,但是对于频繁变化的大量在线人员列表来说,持久化存储实际上是没太大意义的。因此,读写性能更高的内存存储,如memcache,Redis,可能是一种更现实的选择。memcache只能支持简单的KV对象存储,数据读写需要进行频繁的序列化和反序列化,但吞吐更高,而redis的好处在于丰富的数据类型,如Lists、Hashs、Sets,省去了序列化和反序列化操作,并且支持更高效的分页遍历及count操作:
消息通道
HTTP协议请求/响应式特性决定了它擅长处理浏览型业务,而对于需要与服务端进行频繁交互的即时通讯场景来说,则会显得十分蹩脚。在直播进行的过程中,用户可以对主播进行吐槽、评论,用户与用户之间也可以进行频繁的交流,需要有像弹幕、WEB IM这样的工具来支持,这种场景下,消息的实时性尤为重要。
要实现这类场景,最简单最粗暴的方式莫过于不断地轮询应用服务器,采用拉的方式读取弹幕以及用户的聊天内容,消息的实时程度取决于拉的频率,拉的过快,可能服务器无法承受,拉的频率过低,则消息的实时性跟不上。轮询的方式太过于粗暴,需要频繁的请求服务器,成本较高,并且用户更新信息的频率实时变化,很难选择比较合理的轮询时间,因为不同时间段用户发送消息的频率是有很大差异的,对于拉取的信息,客户端需要进行筛选和去重。因此,对于WEB端的即时交互应用,需要采用其他解决方案,如comit服务端推送,或者通过websocket来实现类似的场景。
comet又被称作为反向ajax(Reverse AJAX ),分为两种实现方式,一种是长轮询(long-polling)方式,一种是流(streaming)的形式。在长轮询的方式下,客户端与服务端保持HTTP连接,服务端会阻塞,直到服务端有信息传递或者是HTTP请求超时,客户端如果收到响应,则会重新建立连接,另一种方式是流的形式,服务器推送数据给客户端,但是连接并不关闭,而是始终保持,直到连接超时,超时后客户端重新建立连接,并关闭之前的连接。通过这两种方式,便可借用HTTP协议,来实现服务端与客户端的即时通讯。(注:comet,https://software.intel.com/zh-cn/articles/comet-java-realtime-system-essay)
而WebSocket是IETF所提出的一种新的协议,旨在实现浏览器与服务器之间的全双工(full-duplex)通信,而传统的HTTP协议仅能够实现单向的通信,即客户端发起的到服务端的请求,虽然comet在一定程度上可以模拟双向通信,但是通信的效率较低,且依赖特定的服务器实现。(注:WebSocket,https://tools.ietf.org/html/rfc6455)
为何说comet的通信效率会低于WebSocket呢,因为不管是comet的长轮询机制还是流机制,都需要在请求超时后发送请求到服务端,并且,长轮询在服务端响应消息之后,需要重新建立连接,这样又带来了额外的开销。
我们知道HTTP协议的Request Header中附带了很多信息,但是这中间包含的很多信息有些场景其实并不是必须的,这样就浪费了大量的带宽及网络传输时间。而WebSocket协议使得浏览器与服务器只需要一次握手动作,便形成了稳定的通信通道,两者之间就可以通过frame进行数据传递,而消息传递所发送的Header是也是很小的,这样就会带来效率的提升。
通过wireshark抓包可以做一个简单的测试对比, 假设服务端每秒推送50条消息给用户,每条消息的长度为10byte,对应不同的用户规模,采用websocket和comet两种不同的通信机制,所需要传递的字节数如图8所示,可见,随着用户规模及消息量的提升,采用websocket协议进行通信,将对通信效率带来数量级的提升,详细的测试过程可参见笔者博客。引入WebSocket协议,虽然原则上解决了浏览器与服务端实时通信的效率问题,相较comet这种基于HTTP协议的实现方式,能获得更好的性能,但同时也引入了一些新的问题。摆在首位的便是浏览器的兼容性问题,开发WEB应用程序的一个最头痛的问题就是多版本浏览器的兼容,不同浏览器产商对于协议的实现有各自的理解,并且,市面上还充斥着大量低版本不支持HTML5协议的浏览器,且这部分用户在中国还占有较大基数,因此在产品研发的过程中不得不予以考虑。得益于socket.io的开源,通过websocket、Adobe Flash Socket、long-polling、streaming、轮询等多种机制的自适应切换,帮我们解决了绝大部分浏览器(包括各种内核的webview)的兼容性问题,统一了客户端以及服务端的编程方式。对于不同平台的消息推送,我们内部也衍生了一些成熟的技术平台,封装了包括消息推送,消息订阅,序列化与反序列化,连接管理,集群扩展等工作,限于篇幅,这里就不多说了。
直播平台的相关技术(转载)