参考文章:html
http://chenbowen.baijia.baidu.com/article/472127前端
http://blog.csdn.net/cabbage2008/article/details/50582899html5
大流量、高并发场景下,大型直播的技术挑战通常体如今以下几个方面:
视频流的处理、分发
播放质量保障
视频可用性监控
超大直播间实时弹幕及聊天互动
高性能消息通道
内容控制,如算法鉴黄、文本过滤
系统可用性、稳定性保障java
本文将针对其中的一些技术细节,抽丝剥茧,但愿经过些许文字的分析和介绍,能让你们有所启发。mysql
视频直播
ios
对于直播平台来讲,为了保障各类网络环境下可以流畅的观看视频,须要将高清的输入流转换出多路不一样清晰度的视频流,以支持不一样网络条件下视频清晰度的切换,而因为不一样的端所支持的协议及封装格式并不彻底相同,好比无线端的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
消息/弹幕
ajax
WEB IM应用及弹幕近年来有愈来愈火的趋势,是营销与气氛活跃的一种很是重要的手段。对于同时在线人数庞大的实时聊天互动、实时直播弹幕这一类场景来讲,在保障消息实时性的前提条件下,将会面临很是高的并发压力。
举个例子来讲,假设一个活跃的直播间有10w人同时在线,正在直播一场热门的游戏赛事,假设每秒钟每一个人说一句话,将会产生10w条消息,也就是10w/s的消息上行QPS,而每条消息又须要广播给房间里面的每个人,也就是说消息下行将成10w倍的放大,达到惊人的10w*10w=100亿/s的消息下行QPS,而这仅仅是一场直播的QPS,相似的直播可能有多场正在同时进行,对于消息通道来讲,无疑将是一个巨大的挑战。所以,在系统设计的时候,首先要考虑的问题,就是如何下降消息通道的压力。
redis
用户将信息投递到消息系统以后,系统首先对消息进行一系列的过滤,包括反垃圾、敏感关键词、黑名单等等,对于信息的过滤后面会详细介绍,此处暂且不表。为了不系统被瞬间出现的峰值压垮,可先将消息投递到消息队列,削峰填谷,在流量的高峰期积压消息,给系统留必定裕度,下降因限流丢消息对业务产生的影响。
然后端始终以固定的频率处理消息,经过异步机制保障峰值时刻系统的稳定,这是一个典型的生产者—消费者模型。对于消息的消费端,则可采用多线程模型以固定的频率从消息队列中消费消息,遍历对应房间所对应的在线人员列表,将消息经过不一样的消息通道投递出去。多线程增长了系统的吞吐能力,特别是对须要将消息一次性投递给几万上十万用户这样的场景,能够异步使用大集群并行处理,提升系统的吞吐能力。异步使后端的消息投递可不受前端消息上行峰值流量的干扰,提升系统稳定性。
除了采用消息队列异步处理以外,当房间人数太多,或者消息下行压力太大的状况下,还须要进一步下降消息下行通道的压力,这就须要采用分桶策略。所谓的分桶策略,实际上就是限制消息的传播范围,假设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)的兼容性问题,统一了客户端以及服务端的编程方式。对于不一样平台的消息推送,咱们内部也衍生了一些成熟的技术平台,封装了包括消息推送,消息订阅,序列化与反序列化,链接管理,集群扩展等工做,限于篇幅,这里就很少说了。