当前位置: 首页 > 科技观察

弹幕系统更新的血与泪

时间:2023-03-16 16:38:47 科技观察

< titlesplit >2016年是直播浪潮兴起的元年,众多互联网公司开始涉足直播内容模块。我现在的公司接手的第一份工作是直播业务中弹幕系统的优化。随着公司直播业务的变化,弹幕系统从最初的版本到后来的版本优化了三四个版本。这个过程持续了大约一年。本文将从我们早期的弹幕系统开始介绍整个更新。过程的“血与泪”。前期弹幕系统1.基本情况由PHP+Gateway框架编写。所有客户端ID都存储在Redis中。最初在LVS系统后面挂载三台机器提供服务。使用多进程的方式,开启多个worker进程来处理消息的传递。内容2.问题内存占用很大。单机(4核8G配置)可以承载500个左右的客户端,会达到内存限制。每发送一条消息,每台机器都需要从Redis中拉取对应房间的所有客户端。ID;高并发时,Redis的单进程处理效率和内网带宽成为瓶颈。单机的并发处理能力受限于处理消息的worker进程数。同时开启太多的进程也是对系统资源的额外浪费。当单间超过2000人时,消息延迟可能会达到1分钟左右,这是一个极其严重的问题。3、临时改造由于急需解决的问题,很快做了一些逻辑上的改变和业务层面的取舍:拆分Redis实例,采用双机单机4实例,分散Redis一些改变了消息处理worker进程的逻辑,限制了单位时间内广播的消息数量,多余的消息会被丢弃。对于已经完成直播进入点播状态的房间,额外启用另一套弹幕系统进行分流。单个房间被切片成多个房间进行消息处理。4、改造后的效果。Redis的压力大大降低。降低单机IO性能压力。同样数量的机器可以承载更多的直播间。但是,根本问题并没有解决。在暂时解决压??力问题后,我们需要花一些时间重新分析弹幕系统,并根据分析的需求重构新的弹幕系统。新弹幕系统1、新弹幕系统面临的挑战单间人数比较多。根据我们公司的直播情况,单间会有5-10万人同时在线。由于直播内容等原因,导致某段时间内的用户量暴涨。它需要尽可能实时到达。如果延迟太高,交互的实时性会大大降低。对于每条消息,都必须传递大量的长连接。一种针对大量长连接的维护机制。在运营过程中,需要处理用户黑名单、IP黑名单、敏感词等需求。2、对新弹幕系统的要求因为内存管理是PHP的短板,对于大并发和长期稳定不需要频繁更新维护的系统来说不是最佳选择,所以选择合适的一门语言是必须的。分布式支持可以快速横向扩展,单机房人数最多支持10万人。可以方便快捷的向系统发送第三方消息(如礼品信息、系统通知等)。尽量使用本地内存管理记录房间内的客户端连接,留下大量的数据交互和查询时间。并发支持消息广播,提高广播效率。3.新弹幕系统版本的改造方式选择目前流行的支持高并发的Golang作为开发语言。使用开发语言管理客户端连接,每台机器只管理自己收到的连接请求。使用并发室内广播逻辑同时向多人广播。新弹幕系统改造的相关经验下面的部分将首先分析一个模块的细节,然后进一步分析模块上层的调度逻辑。1.房间管理typeRoomInfostruct{RoomIDstring//房间IDLock*sync.Mutex//房间操作锁Rows[]*RowList//房间多行SliceLengthhuint64//当前房间总节点数LastChangeTimetime.Time//***更新时间}typeRowListstruct{Nodes[]*Node//节点列表}由于每个房间都有自己的ID,所以客户端建立连接后,会放在一个大厅房间。然后,客户端自己提交RoomID,连接就会重新连接到对应的房间。每个连接建立后,都会被打包成一个Node,放在Rows中。typeNodestruct{RoomIDstringClientIDint64Conn*websocket.ConnUpdateTimetime.TimeLastSendTimetime.Time//上次发送消息的时间IsAliveboolDisabledReadbool//发言权是否已经关闭}每个Node都有一个IsAlive表示连接是否成功。如果连接断开,或者由于其他原因强制停止服务,该标志的状态将被修改。然后通过定时处理机制关闭连接并从内存中清除。Rows的本质是一组预设长度的NodeSlices。发送消息时,每组切片使用协程顺序发送。同一房间的连接可以按照切片分组并发发送。发送时,会用一把锁锁住整个房间,防止并发情况下两条消息混入同一个连接。2.消息管理varmessageChannelmap[string]channodeMessagefuncinit(){messageChannel=make(map[string]channodeMessage)}funcsendMessageToChannel(roomIdstring,nmnodeMessage)error{//如果房间不存在,则创建房间ifc,ok:=messageChannel[roomId];ok{c}else{//创建房间频道messageChannel[roomId]=make(channodeMessage,1024)messageChannel[roomId]//创建房间实例roomObj:=&RoomInfo{}roomObj.RoomID=roomIdroomObj.Rows=make([]*RowList,0,4)roomObj.Lock=&sync.Mutex{}//创建一个新协程来监控房间godaemonReciver(messageChannel[roomId],roomObj)gotimerForClean(messageChannel[roomId])//如果是一个lobby,startHallcleaningcoroutineifroomId==""{goCleanHall(roomObj)}}returnnil}以上是弹幕信息传递的部分代码。首先,每个房间都有自己的消息通道,所有这些通道都以RoomID为key记录在一个名为messageChannel的map中。每次收到消息,只要把消息扔到频道里,就可以了。(后面由守护协程处理)如果没有房间通道,则创建一个房间通道通道,并为每个房间启动一系列协程。3、服务器管理这里的解决方案比较简单。其实就是在上层建一个聊天室,也就是房间。所有服务器都会主动连接到这个房间,每个服务器收到的信息都会广播给这个房间里的其他人。机去。4.守护协程管理守护协程处理很多琐碎的事情,保证房间内信息的正常分发和房间连接的正常管理。各个daemon协程的作用如下:消息发送协程:每个房间都配备一个,从channel中获取要发送给房间的消息,然后并发调用各个RowList的消息发送机制。房间整理协程:因为会出现掉线、换房等行为修改Node状态,所以会有房间整理协程周期性的清理节点,删除与当前房间无关的节点等,提高消息发送的效率。5、测试相关运行环境:云主机8核16G实例操作系统:Centos7(未进行系统优化和参数调整)测试内容:单机建立15000个websocket连接,发送消息进入指定房间(所有连接进入同一个房间)。客户端进入房间,发送消息,消息经过敏感词、IP、用户黑名单处理后广播给所有节点。测试结果:CPU使用率:保持在5%以下内存使用率:2GB(包括操作系统开销)网络使用率:峰值约10Mb/s发送效率:15000个节点广播,约100ms–110ms。根据测试结果计算:在8核16G机器上完全可以无压力跑50K并发,峰值处理能力接近60-70K。6.更多分享我目前正在尝试开源这个弹幕系统的基本功能。摘录了一部分,目前地址为:https://github.com/logan-go/roomManager,感兴趣的读者可以通过链接查看。总结弹幕系统为视频直播/点播增加了更多的互动娱乐内容,从最初的A站、B站到现在的主流视频网站APP。如何健康高效的管理弹幕系统,也是当前视频行业需要关注的技术课题。