弹幕系统更新的血与泪

移动开发
16年是直播浪潮兴起的元年,许多互联网公司的业务都开始涉足直播内容模块。我目前所在公司接手的第一份工作,就是直播业务中的弹幕系统优化。随着公司直播业务的变化,弹幕系统从最初的版本到后来优化了三四个版本,这个过程大概持续了一年的时间,本文将从我司早期的弹幕系统开始给大家介绍整个更新过程的“血与泪 ”。

16年是直播浪潮兴起的元年,许多互联网公司的业务都开始涉足直播内容模块。我目前所在公司接手的***份工作,就是直播业务中的弹幕系统优化。随着公司直播业务的变化,弹幕系统从最初的版本到后来优化了三四个版本,这个过程大概持续了一年的时间,本文将从我司早期的弹幕系统开始给大家介绍整个更新过程的“血与泪 ”。

早期弹幕系统

一、基本状况

  1. 由PHP + Gateway框架编写
  2. 所有的Client ID存放在Redis里面
  3. 最初由三台机器挂载在LVS系统后方提供服务
  4. 使用多进程的方式,开启多个worker进程来处理消息传递内容

二、存在的问题

  1. 内存占用量巨大,单机(4核8G配置)承受500左右的Client就会达到内存上限
  2. 每次发送消息的时候,每台机器都需要从Redis里面拿取对应房间的所有Client ID;并发高时,Redis的单进程处理效率和内网带宽就成为瓶颈 。
  3. 单机的并发处理能力被消息处理的worker进程数量限制。同时开启过多的进程,也是对系统资源的格外浪费。
  4. 单房间超过2000人的时候,消息的延迟有可能会达到1分钟左右,这是极其严重的问题。

三、临时改造

由于需要解决的问题比较紧迫,所以快速做了一些逻辑上的改变和业务层面的取舍:

  1. 对Redis的实例进行了拆分,使用了双机,单机4实例的方式,分散了Redis的压力
  2. 对消息处理worker进程的逻辑做了一些修改,限制了单位时间内进行广播的消息数量,多余的消息会被丢弃 。
  3. 对于已经完成了直播进入点播状态的房间,额外启用了另外一套弹幕系统来进行分流。
  4. 单个房间切成多个房间进行消息处理。

四、改造之后的效果

  1. Redis压力大幅度降低
  2. 单机IO性能压力降低
  3. 同样数量的机器,可以承载更多的直播房间个数

[[214239]]

但是,根本问题并没有得到解决。在临时解决压力问题之后,我们需要花一些时间来重新对弹幕系统进行分析,按照分析后的需求,对新的弹幕系统进行重构。

新的弹幕系统

一、新弹幕系统面临的挑战

  1. 单房间人数较高,依照我们公司直播情况,单房间5 – 10万人同时在线是会出现的。
  2. 由于直播内容等情况造成的某时间段用户暴涨。
  3. 需要尽可能实时到达,延迟过高的话会大大降低互动的实时性。
  4. 每一条消息,都要递送大量的长连接。
  5. 大量长连接的维护机制。
  6. 在运营的过程中,需要处理用户黑名单、IP黑名单、敏感词等需求。

二、新的弹幕系统需求

  1. 由于内存的管理对于PHP来说算是一个短板,对于大并发且长时间稳定不需要经常更新维护的系统来说,并非***的选择,因此选一门合适的语言是必须的。
  2. 分布式支持,可以快速的横向扩展,单房间人数可以支持到十万级别。
  3. 可以方便快捷的对系统进行第三方消息的发送(例如礼物信息、系统通知等)。
  4. 尽量使用本地内存管理来记录房间内客户端连接,剩下大量的数据交互和查询时间。
  5. 并发支持消息广播,提高广播效率。

三、新弹幕系统版本的改造方法

  1. 选择当前正红且对高并发支持良好的Golang作为开发语言。
  2. 使用开发语言进行客户端连接的管理,且每台机器只管理自己收到的连接请求。
  3. 使用并发的房间内广播逻辑,同时对多人进行广播。

新弹幕系统改造的相关经验

下面先对一个模块细节进行分析,然后进一步分析模块上层的调度逻辑。

一、房间管理

 

  1. type RoomInfo struct {  
  2. RoomID string //房间ID  
  3. Lock *sync.Mutex //房间操作锁  
  4. Rows []*RowList //房间多行Slice  
  5. Length uint64 //当前房间总节点数  
  6. LastChangeTime time.Time //***一次更新时间  
  7.  
  8. type RowList struct {  
  9. Nodes []*Node //节点列表  

由于每个房间都有自己的ID,客户端建立连接之后,就会被放到一个大厅房间里面。接着,客户端自己提交RoomID上来,连接会被重新连接到对应的房间里面。 每个连接在建立之后,都会被包装成一个Node,放到Rows里面。

 

  1. type Node struct {  
  2. RoomID       string  
  3. ClientID     int64  
  4. Conn         *websocket.Conn  
  5. UpdateTime   time.Time  
  6. LastSendTime time.Time //***一次发送消息时间  
  7. IsAlive      bool  
  8. DisabledRead    bool//是否已经被关闭了发言权限  

每一个Node中,都有一个IsAlive来表示连接是否成功。如果连接断开,或者因为其他原因强制停止服务的话,会修改此标记状态。然后由定时的处理机制将此连接关闭并从内存中清除。 Rows的本质就是一组事先设定了长度的Node Slice。

发送消息的时候,每一组slice使用一个协程来顺序发送。同一房间内的连接,就可以依照slice分组进行并发发送。 发送的时候,会使用锁将整个房间锁住,以防止并发情况下同一连接混入两条信息。

二、消息管理

 

  1. var messageChannel map[string]chan nodeMessage  
  2. func init() {  
  3. messageChannel = make(map[string]chan nodeMessage)  
  4.  
  5. func sendMessageToChannel(roomId string, nm nodeMessage) error {  
  6. //如果房间不存在,创建一个房间  
  7. if c, ok := messageChannel[roomId]; ok {  
  8.  
  9. else {  
  10. //创建房间通道  
  11. messageChannel[roomId] = make(chan nodeMessage, 1024)  
  12. messageChannel[roomId]  
  13. //创建房间实例  
  14. roomObj := &RoomInfo{}  
  15. roomObj.RoomID = roomId  
  16. roomObj.Rows = make([]*RowList, 0, 4)  
  17. roomObj.Lock = &sync.Mutex{}  
  18. //创建新的协程来监控房间  
  19. go daemonReciver(messageChannel[roomId], roomObj)  
  20. go timerForClean(messageChannel[roomId])  
  21. //如果是大厅的话,启动大厅清理协程  
  22. if roomId == "" {  
  23. go CleanHall(roomObj)  
  24.  
  25.  
  26. return nil  

以上是关于弹幕信息传递的一部分代码。 首先,每一个房间,都有自己的消息通道,所有的这些通道根据RoomID为key,记录在一个叫做messageChannel的map里面。 每次收到消息的时候,都直接把消息丢到channel里面,就可以了。(后面由守护协程来处理)如果没有房间通道的话,就建立房间的通道channel,并启动每个房间的一系列协程。

三、服务器管理

这里的方案比较简单,其实就是建立一个上一层的聊天室即一个房间,所有的服务器都会主动连接到这里,每一个服务器收到的信息,就会在这个房间里面广播到别的机器去。

四、守护协程们管理

守护协程处理很多琐碎的事情,保证房间内信息的正常分发以及房间连接的正常管理。各个守护协程的功能如下:

  1. 消息发送协程:每个房间配备一个,从channel里面获取到要发送到本房间的消息,然后在并发调用各个RowList的发送消息机制。
  2. 房间整理协程 :因为会有连接断开、房间更换等修改Node状态的行为,所以定期会有房间整理协程来进行节点整理,删除当前房间无关的节点等以提高消息的发送效率。

[[214240]]

五、测试相关

  • 运行环境:云主机8核16G实例
  • 操作系统:Centos7(未进行系统优化或参数调整)
  • 测试内容:单机建立15000 websocket连接,并且发送消息,进入指定房间(所有连接进入同一房间)。一个客户端进入房间,发送一条消息,经过敏感词处理、IP和用户黑名单处理,然后被广播到所有节点。

测试结果:

  • CPU占用:保持在5%以下
  • 内存占用:2GB(包括操作系统本身开销)
  • 网络占用:峰值10Mb/s左右
  • 发送效率:15000节点广播,100ms – 110ms左右。

根据测试结果计算:

完全可以在8核16G的机器上,实现无压力运行50K并发,峰值接近60 – 70K的处理能力。

六、更多分享

我目前正在尝试把完成这套弹幕系统的基本功能开源出来。已经提取出来了一部分,当前的地址为:https://github.com/logan-go/roomManager,感兴趣的读者可以通过链接查看。

小结

弹幕系统给视频直播/点播增加了更多内容的互动娱乐性质,从最初的A站B站发展到现在各主流视频网站APP。如何健康高效的管理弹幕系统,也是当下视频行业需要重视的一门技术活。

责任编辑:未丽燕 来源: U刻
相关推荐

2017-12-20 12:32:26

弹幕系统更新

2014-08-01 14:32:29

创业90后创业

2018-04-11 15:42:04

开源项目姿势

2012-12-24 09:18:22

iOSUnity3D

2017-05-11 22:43:53

2015-07-16 12:37:28

弹幕

2019-01-02 16:47:46

Golang弹幕

2019-01-02 16:38:37

Golang弹幕

2019-01-02 16:50:30

Golang弹幕

2011-01-14 16:51:44

Linux内核

2019-11-20 18:47:26

物联网OTA软件

2019-03-29 09:01:39

弹幕微博Python

2010-04-28 15:38:54

2010-12-23 14:17:14

Web 2.0

2018-05-08 09:00:00

系统更新

2016-08-27 20:40:02

直播弹幕

2023-01-04 09:29:03

线程业务代码

2018-03-15 11:06:51

区块链钻石加密

2010-07-30 14:15:51

RIP路由器

2012-06-18 15:21:38

Facebook竞价广告
点赞
收藏

51CTO技术栈公众号