阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

弹幕系统更新的血与泪

  • 2019-11-13
  • 本文字数:3220 字

    阅读完需:约 11 分钟

弹幕系统更新的血与泪

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

早期弹幕系统

一、基本状况

1.由 PHP + Gateway 框架编写。


2.所有的 Client ID 存放在 Redis 里面。


3.最初由三台机器挂载在 LVS 系统后方提供服务。


4.使用多进程的方式,开启多个 worker 进程来处理消息传递内容。

二、存在的问题

1.内存占用量巨大,单机(4 核 8 G 配置)承受 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.同样数量的机器,可以承载更多的直播房间个数。



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

新的弹幕系统

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

1.单房间人数较高,依照我们公司直播情况,单房间 5 - 10 万人同时在线是会出现的。


2.由于直播内容等情况造成的某时间段用户暴涨。


3.需要尽可能实时到达,延迟过高的话会大大降低互动的实时性。


4.每一条消息,都要递送大量的长连接。


5.大量长连接的维护机制。


6.在运营的过程中,需要处理用户黑名单、IP 黑名单、敏感词等需求。

二、新的弹幕系统需求

1.由于内存的管理对于 PHP 来说算是一个短板,对于大并发且长时间稳定不需要经常更新维护的系统来说,并非最好的选择,因此选一门合适的语言是必须的。


2.分布式支持,可以快速的横向扩展,单房间人数可以支持到十万级别。


3.可以方便快捷的对系统进行第三方消息的发送(例如礼物信息、系统通知等)。


4.尽量使用本地内存管理来记录房间内客户端连接,剩下大量的数据交互和查询时间。


5.并发支持消息广播,提高广播效率。

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

1.选择当前正红且对高并发支持良好的 Golang 作为开发语言。


2.使用开发语言进行客户端连接的管理,且每台机器只管理自己收到的连接请求。


3.使用并发的房间内广播逻辑,同时对多人进行广播。

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

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

一、房间管理

type RoomInfo struct {     RoomID         string      //房间ID     Lock           *sync.Mutex //房间操作锁     Rows           []*RowList  //房间多行Slice     Length         uint64      //当前房间总节点数     LastChangeTime time.Time   //最后一次更新时间 } type RowList struct {     Nodes []*Node //节点列表 }
复制代码


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


type Node struct {     RoomID       string     ClientID     int64     Conn         *websocket.Conn     UpdateTime   time.Time     LastSendTime time.Time //最后一次发送消息时间     IsAlive      bool     DisabledRead    bool//是否已经被关闭了发言权限}
复制代码


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


Rows 的本质就是一组事先设定了长度的 Node Slice。发送消息的时候,每一组 slice 使用一个协程来顺序发送。同一房间内的链接,就可以依照 slice 分组进行并发发送。 发送的时候,会使用锁将整个房间锁住,以防止并发情况下同一连接混入两条信息。

二、消息管理

var messageChannel map[string]chan nodeMessage func init() {     messageChannel = make(map[string]chan nodeMessage) } func sendMessageToChannel(roomId string, nm nodeMessage) error {     //如果房间不存在,创建一个房间     if c, ok := messageChannel[roomId]; ok {         c <- nm     } else {         //创建房间通道         messageChannel[roomId] = make(chan nodeMessage, 1024)         messageChannel[roomId] <- nm         //创建房间实例         roomObj := &RoomInfo{}         roomObj.RoomID = roomId         roomObj.Rows = make([]*RowList, 0, 4)         roomObj.Lock = &sync.Mutex{}         //创建新的协程来监控房间         go daemonReciver(messageChannel[roomId], roomObj)         go timerForClean(messageChannel[roomId])         //如果是大厅的话,启动大厅清理协程         if roomId == "" {             go CleanHall(roomObj)         }     }     return nil }
复制代码


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

三、服务器管理

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

四、守护协程们管理

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


1.消息发送协程:每个房间配备一个,从 channel 里面获取到要发送到本房间的消息,然后在并发调用各个 RowList 的发送消息机制。


2.房间整理协程:因为会有连接断开、房间更换等修改 Node 状态的行为,所以定期会有房间整理协程来进行节点整理,删除当前房间无关的节点等以提高消息的发送效率。


五、测试相关

运行环境: 云主机 8 核 16 G 实例


操作系统: Centos 7(未进行系统优化或参数调整)


测试内容: 单机建立 15000 websocket 连接,并且发送消息,进入指定房间(所有连接进入同一房间)。一个客户端进入房间,发送一条消息,经过敏感词处理、IP 和用户黑名单处理,然后被广播到所有节点。


测试结果如下:


CPU 占用: 保持在 5% 以下


内存占用: 2GB(包括操作系统本身开销)


网络占用: 峰值 10Mb/s 左右


发送效率: 15000 节点广播,100ms - 110ms 左右。


根据测试结果计算:


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

六、更多分享

我目前正在尝试把完成这套弹幕系统的基本功能开源出来。已经提取出来了一部分,感兴趣的读者可以通过链接查看。


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


本文转载自公众号 UCloud 技术(ID:ucloud_tech)。


原文链接:


https://mp.weixin.qq.com/s/CXp6Q2pxo3Pc7yQg4gM1vw


2019-11-13 11:57769

评论

发布
暂无评论
发现更多内容

测试Java初学者建议

FunTester

iOS MachineLearning 系列(5)—— 视频中的物体运动追踪

珲少

有奖征文丨【玩转Cloud Studio】第二季来啦!

CODING DevOps

Cloud Studio 云端IDE 在线编程 有奖征文 活动推荐

耗时72天!终于把GitHub上热度最高的Java面试八股文整理出来了,涵盖多家大厂面试真题

架构师之道

Java 面试

从“捐赠”到“接受捐赠”,这背后是openEuler的两次蜕变

Geek_2d6073

法大大发布数智化签约管理平台,赋能企业高效增长

人称T客

linux设置虚拟IP

linux大本营

Linux 网络 IP地址

用友自主研发企业商用版TimensionDB时序数据库重磅发布!

用友BIP

数据库 用友iuap 用友技术大会 升级企业数智化底座

大淘宝技术斩获NTIRE 2023视频质量评价比赛冠军(内含夺冠方案)

阿里巴巴大淘宝技术

视频 NTIRE

c++单例模式的所有面经

linux大本营

设计模式 单例模式 C++

智汇昌平,数赢未来——宝德京产自主创新服务器正式下线

Geek_2d6073

Cloud Studio 一个好用的在线编程工具

CODING DevOps

开发 部署 Cloud Studio 云端IDE 在线编程

人工智能训练数据集:基础与发展

来自四九城儿

读《分布式商业》有感

后台技术汇

分布式 三周年连更

软件测试/测试开发丨Linux 常用高频命令

测试人

Linux 软件测试 自动化测试 测试开发

设计模式天花板,详解23种设计模式+7大设计原则

小小怪下士

Java 程序员 设计模式

“前端已死”还是“娱乐至死”?做个清醒的前端

这我可不懂

前端 低代码

作为前端你还不懂MutationObserver?那Out了

不叫猫先生

JavaScript 前端 三周年连更 MutationObserver

少年与阿童木:一场软件竞技赛背后的智能未来

脑极体

机器人 华为云

低代码是开发的未来,还是只能解决边角问题的鸡肋?

引迈信息

前端 后端 低代码 JNPF

极客时间「大师课·深度剖析 RocketMQ5.0」上线啦,欢迎免费领取!

Apache RocketMQ

云原生 消息队列

ThingsBoard 前端项目内置部件开发

echeverra

thingsboard

在 Kubernetes 中实施零信任的七条准则

NGINX开源社区

nginx Kubernetes

为什么老有人想让我们“程序员”失业? | 社区征文

不叫猫先生

人工智能 程序人生 ChatGPT 三周年征文

阿里内部微服务架构秘籍:SpringCloudAlibaba全彩版笔记开源

采菊东篱下

编程 微服务

招商基金数字化转型下的研发管理|标杆案例

万事ONES

人工智能时代来临,殊不知低代码早已出手

加入高科技仿生人

人工智能 低代码 数智化 数智融合

人脸识别:城市公共交通

百度开发者中心

人工智能 人脸识别

百度与用友网络签署战略合作

百度开发者中心

智能制造 文心一言

Wallys/QSDK/IPQ4019 and IPQ4029 chipsets support 20 km remote transmission

Cindy-wallys

IPQ4019 ipq4029

软件测试/测试开发丨uiautomator2 自动化测试工具使用

测试人

软件测试 自动化测试 测试开发 uiautomator

弹幕系统更新的血与泪_文化 & 方法_大妖_InfoQ精选文章