Sunofmylife!Talkischeap,Showmethecode!追风赶月莫停留,平芜尽处是春山~
大流量、高并发场景下,大型直播的技术挑战一般体现在如下几个方面:
本文将针对其中的一些技术细节,抽丝剥茧,希望通过些许文字的分析和介绍,能让大家有所启发。
视频直播
直播整个链路是比较长的,包含有接流、转码、切片、分发、客户端下载、播放等众多环节,链路上任何一个节点有问题,都可能导致视频不能播放。因此,关键节点的监控就十分重要,除此之外,还需要对整个链路的可用性进行监控,比如,针对HLS协议来说,可通过监控相应的m3u8索引列表有没有更新,来判断视频直播流是否中断。
当然,如需判断视频流中的帧有没有花屏、有没有黑屏,就更复杂了,况且,监控节点所访问的CDN节点与用户所访问的CDN节点可能并不在同一地域。当前中国的网络环境,特别是跨网段的网络访问,对于流媒体应用来说,存在较大的不可控因素,客户端网络接入环境对视频的播放有决定性影响。
消息/弹幕
图2:消息的投递与消费
用户将信息投递到消息系统之后,系统首先对消息进行一系列的过滤,包括反垃圾、敏感关键词、黑名单等等,对于信息的过滤后面会详细介绍,此处暂且不表。为了避免系统被瞬间出现的峰值压垮,可先将消息投递到消息队列,削峰填谷,在流量的高峰期积压消息,给系统留一定裕度,降低因限流丢消息对业务产生的影响。而后端始终以固定的频率处理消息,通过异步机制保障峰值时刻系统的稳定,这是一个典型的生产者—消费者模型。对于消息的消费端,则可采用多线程模型以固定的频率从消息队列中消费消息,遍历对应房间所对应的在线人员列表,将消息通过不同的消息通道投递出去。多线程增加了系统的吞吐能力,特别是对需要将消息一次性投递给几万上十万用户这样的场景,可以异步使用大集群并行处理,提高系统的吞吐能力。异步使后端的消息投递可不受前端消息上行峰值流量的干扰,提高系统稳定性。
除了采用消息队列异步处理之外,当房间人数太多,或者消息下行压力太大的情况下,还需要进一步降低消息下行通道的压力,这就需要采用分桶策略。所谓的分桶策略,实际上就是限制消息的传播范围,假设10w人在同一个房间聊天,每人说一句可能瞬间就会排满整个屏幕,消息在这种情况下基本没有可读性。
分桶的策略有很多,最简单粗暴的方式就是根据用户随机。首先根据房间的活跃程度,预估房间该分多少个桶,然后将用户通过hash函数映射到各个桶,随机策略的好处是实现非常简单,可以将用户较为均衡的分配到每个桶,但是会有很多弊端。首先,准确的预估房间的活跃用户数本身就比较困难,基本靠蒙,这将导致单个桶的用户数量过大或者偏小,太大就会增加消息链路的压力,而偏小则降低用户积极性,后期调整分桶数也会很麻烦,需要将全部用户进行重hash。另外从用户体验的角度来考虑,在直播过程中,在线用户数正常情况下会经历一个逐渐上升达到峰值然后逐渐下降的过程,由于分桶的缘故,在上升的过程中,每个桶的人是比较少的,这必然会影响到弹幕的活跃度,也可能因此导致用户流失,而在下降的过程中,逐渐会有用户退出直播,又会导致各个桶不均匀的情况出现:
图三:“分桶策略”之“随机Hash分桶”
图4:分桶策略之“按需分桶策略”
当然,对于部分特殊的消息,如系统公告内容,或者是部分特殊角色(房间管理员、贵宾、授课的老师等等)所发送的消息,这一类消息需要广播给所有用户,这种情况下就需要系统对消息类型做区分,特殊的消息类型另作处理。
对于消息投递任务来说,需要知道消息将以什么方式被投递给谁,这样就需要动态地维护一个房间的人员列表,用户上线/下线及时通知系统,以便将用户添加到房间人员列表或者从房间人员列表中移除。用户上线十分简单,只需要在进入房间的时候通知系统即可,但对于下线用户的处理则有点折腾,为什么这么说呢?用户退出直播间的方式可能有多种,关闭浏览器tab、关闭窗口、关闭电源、按下home键将进程切换到后台等等,有的操作可能可以获取到事件回调,但也有很多种情况是无法获取事件通知的,这样就会导致人员列表只增不减,房间的人越来越多,消息投递量也随之增加,白白的浪费了资源。为解决这一问题,就需采用心跳。
HTTP协议的好处在于兼容性及跨终端,所有浏览器、Andriod、IOS的WebView,都能很好支持和兼容,在目前移动重于PC的大环境下,显得尤为重要,但是HTTP协议劣势也是显而易见的,作为应用层协议,单次通信所要携带的附加信息太多,效率低。
WebSocket在移动端的场景下比较合适,但是运用在PC端,需解决众多老版本浏览器的兼容性问题,socket.io的出现则大大简化了这一原本非常复杂的问题。
TCP协议在传统的客户端应用上使用较多,但是对于WEB应用来说,存在天然障碍。
使用WebSocket和TCP协议的好处显而易见的,通信效率会比HTTP协议高很多,并且这两种协议支持双工通信。
消息通道
comet又被称作为反向ajax(ReverseAJAX),分为两种实现方式,一种是长轮询(long-polling)方式,一种是流(streaming)的形式。
为何说comet的通信效率会低于WebSocket呢,因为不管是comet的长轮询机制还是流机制,都需要在请求超时后发送请求到服务端,并且,长轮询在服务端响应消息之后,需要重新建立连接,这样又带来了额外的开销。
通过wireshark抓包可以做一个简单的测试对比,假设服务端每秒推送50条消息给用户,每条消息的长度为10byte,对应不同的用户规模,采用websocket和comet两种不同的通信机制,所需要传递的字节数如图所示。
可见,随着用户规模及消息量的提升,采用websocket协议进行通信,将对通信效率带来数量级的提升,详细的测试过程可参见笔者博客。
引入WebSocket协议,虽然原则上解决了浏览器与服务端实时通信的效率问题,相较comet这种基于HTTP协议的实现方式,能获得更好的性能,但同时也引入了一些新的问题。摆在首位的便是浏览器的兼容性问题,开发WEB应用程序的一个最头痛的问题就是多版本浏览器的兼容,不同浏览器产商对于协议的实现有各自的理解,并且,市面上还充斥着大量低版本不支持HTML5协议的浏览器,且这部分用户在中国还占有较大基数,因此在产品研发的过程中不得不予以考虑。得益于socket.io的开源,通过websocket、AdobeFlashSocket、long-polling、streaming、轮询等多种机制的自适应切换,帮我们解决了绝大部分浏览器(包括各种内核的webview)的兼容性问题,统一了客户端以及服务端的编程方式。
对于不同平台的消息推送,我们内部也衍生了一些成熟的技术平台,封装了包括消息推送,消息订阅,序列化与反序列化,连接管理,集群扩展等工作,限于篇幅,这里就不多说了。