分享 直播 -- 弹幕系统简介

early · March 08, 2020 · Last by Qwaz1314 replied at November 07, 2020 · 14880 hits
Topic has been selected as the excellent topic by the admin.

弹幕是什么

看过直播的人都难免会注意到屏幕上方飘过的留言,以用户端输入为主,很多视频中也有这种内容的呈现形态。

如果要更细致地理解直播弹幕,用类比法,可以是:

  • 一种“群”消息,将某个用户 (也有系统) 产生的消息实时广播给全体“群成员”
  • 这个群动辄数百万成员
  • 这个群是临时的 (进了某个直播间,一会儿又退出了)
  • 消息基于直播间,进直播间才能收到消息
  • 这种群消息可分布在多端。APP、Web、H5
  • 这种消息阅后即焚,因为和视频画面呼应才有意义 (当然服务端会存档)

简单点讲,弹幕消息就是一种群消息,所以技术界是将弹幕归类为 IM(即时通讯) 范畴,它背后是实时消息。弹幕是实时消息中能被用户看到的内容,还有一部分是系统消息,用来主动触发业务逻辑、行为等。

在直播界,弹幕是一个举足轻重的角色,作为视觉和信息的直接载体,它身上承接了极重的业务形态:

  • 广播出和画面内容呼应的内容
  • 弹幕内容根据用户身份做特殊呈现 (给了钱的更显眼)
  • 弹幕触发其他业务,例如抽奖
  • 内容、频率要做管控

这需要有一个中心化的弹幕业务节点,来处理业务逻辑,构造特色化的内容,然后让 IM 系统吐出。所以从系统角度来看,弹幕系统其实是业务系统+IM系统 合作而成的。架构上 IM 系统要和业务解耦,IM 系统只知道将一堆数据吐给对应的用户。

业务系统只是一些繁杂的逻辑组合,并无特别之处。解下来我们将目光转到实时消息如何到达用户这个问题上来。

挑战

弹幕的主要有两个挑战:

  • “群成员”太多,而且数量不稳定,一份消息产生后,需要实时广播给所有成员,消息体量会被放大百万、千万倍。
  • 群成员分散各地、跨运营商、跨平台。消息稳定性难保证。

其实这也是 IM 的主要挑战。

解决方案

纵观国内解决方案,跳得最高、资料最丰富的是goim,在业界的接纳度较高,也是用在 bilibili 生产环境的方案。

在一头扎进 goim 之前,我们先来简单梳理一下 IM 的需求及可能的模块,在软件工程目标下做一次 pre 构想,再来看 goim 是如何实现这些目标。

弹幕可见的模块有:

  • 连接管理层。处理和用户连接、传输数据给指定的目标用户。
  • 业务逻辑层。黑名单 IP、黑名单用户、附加信息、在线数统计等等
  • 数据统计层。哪些房间有多少人,某个用户连接情况等等
  • 数据处理层。接收消息请求,并转发到连接层

当用户体量小时,以上模块用一个简单的单体服务也可以实现,把逻辑写在一起,不用管什么层级。但是当用户规模大、稳定性要求高、成本敏感时,就需要拥抱软件工程的目标:

  • 独立可扩展
  • 健壮性
  • 高迭代效率
  • 鲁棒性 [1]

一般技术事故都是两个原因,一是外部流量洪峰;二是内部变化,例如改了代码、触发了部署。稳定性和迭代是矛盾的,那该如何尽可能既要熊掌又要鱼呢?

  • 根据易变性、伸缩性、业务等拆分模块,让各自独立,通过网络交互,解耦合。
  • 通过系统架构设计保证,当流量洪峰来临,只需要简单扩容就可应对。

例如业务逻辑层就是经常变化的,代码改动概率很大。这时候业务逻辑代码的变化、部署在理论上不能影响到连接管理层部分。相反连接管理部分一旦稳定变化会很少,但其伸缩需求大,要经常扩缩容,它的部署过程也不能影响其他模块。

goim 简介

梳理完目标,我们来看一下 goim 是如何设计,如何实现上面的目标。 goim 抽象出了四大模块:

  • comet,用户连接,支持多协议,通过扩容可支撑海量用户
  • job, 消息转发,将弹幕等消息转给 comet,最后到达用户。实现为消息队列
  • logic,业务逻辑处理、投递消息给 job,无状态
  • router,用户会话管理,数据统计。(最初 router 的意义 [2],bilibili 最新实践中统计用 redis 实现,router 合并到了 logic。本文为科普暂保留 router 这个概念)
  • 依赖消息队列、服务发现

整体结构如下图:

各层通过 rpc 或消息队列交互。各层可以直接扩容,新的地址会通过服务发现通知到各层。看似系统设计的很复杂没有必要,其实就像有些人反对微服务或容器化一样,根本问题是他的系统的规模不够大,对稳定性、研发效率要求不高。到一定规模后,细化拆分、分而治之是唯一的路,但也得寻找适合当前的架构,网上有很多自己改的例子 [5]。 (更详细的资料建议查阅 goim 资料。)

接下来回答几个问题:

  • 一条弹幕消息的链路是什么? 如上图红色 (1)~(6) 所示。
  • 用户如何连接到 comet 节点? 当用户进房间之后,通过接口拉取 comet 可用节点,直接连接,这个接口可以根据用户的 IP 等随机吐出 comet 节点,实现负载均衡。
  • 用户是随机散在所有 comet 节点上的,同一个房间里的用户也是散在所有 comet 节点上,此时房间弹幕如何发给每一个用户? 对于房间弹幕,job 会从 router 中获取对应房间 id 的用户散落在哪些 comet 节点上,将信息同时转发给所有 comet 节点,每个 comet 会将消息发给房间内的用户。
  • comet 上如何管理用户? 用户连接 comet 后,会创建 channel 对象,管理连接。comet 上会构建多个 Bucket,通过 RoomId 取余找到目标 Bucket,此举为了降低锁竞争。Bucket 中有一个 Room 对象是 Hash,通过 RoomId 哈希取值,将该 channel 指针追加到对象尾部,遍历这个对象就可以找到对应房间里所有用户。同时 Bucket 有 Channel 哈希对象,将 channel 放入哈希中,可便捷找到对应用户。[3][6]
  • 系统能否保证必达? 不能,该方案未提供消息存放能力和用户拉取能力,推拉结合才能保证必达。而进房间这个事件有较大的临时性,房间弹幕必达需求也不够强烈。

网络问题

行文到此,弹幕系统已经简要理清楚了,但这样的系统会有掉线率高、不稳定的问题,需要在网络结构上做调整。[4]

comet 和用户之间的长连接如果随意地通过公网连接,会因为地域、运营商的差异导致连接质量,为了解决这种问题,需要上 CDN 级的手段,例如:

  • comet 需要同时部署到几大运营商的机房内
  • comet 需要部署到多个区域
  • 除 comet 外的部署到一个或多个中心机房,中心机房和各运营商 comet 所在机房需要有高质量的网络通道
  • comet 节点的选取依靠服务商的 DNS 调度,根据地区、运营商选择用户最适合的节点

结构如下图:

这种结构让服务发现变得难以实施,退而求其次可能需要通过配置文件写死各个服务地址,各个服务实现信号量检测热更新配置,或者对外只暴露代理地址。同时,跨公网调用风险极高,需要一大堆防火墙等策略保证安全。

参考资料

相关话题

early in 直播 (下) --- 业务结构简介 mention this topic. 08 Mar 17:18
xiaoronglv mark as excellent topic. 08 Mar 18:10

IM 最蛋疼的模块,就是那个 ~router~ 负责长链的模块

处理消息的服务有很多方法减压,广播消息不是多吗,那咱就上 cassandra。还可以把写扩散变成度扩散。消费不过来,丢消息队列慢慢来。

~router~ 负责长链的模块 却必须广播,创建一条消息广播 n 次。消息队列慢慢消费?不存在的,消费慢了 IM 还不成邮件了。

也有很多办法,比如限制讨论组大小,据我所知,网易内部 IM,最大群组是限制是 500,微信也限制了群的大小。不过还真不清楚钉钉大群是不是可以随便创建的。

但大群也还是要搞,就是麻烦,比如钉钉就把大群拆成了若干小群。然后还有限流、限频、服务降级打了一套组合拳。

恩,还要这玩应是有状态的!

API 服务是无状态的(有状态的丢给存储),水平扩展,重启大法,玩的六六的。

重启一台 router?恩,用户要重连的,搞不好还要重连几次的。。。

~router~连连服务 就不好这么搞。Phoenix 的 pub/sub 为了水平扩,就把一条消息发给所有服务,然后再有服务转发到对应的链接。

http 网络抖了,服务器一点感觉也没有。但 ws、tcp 不一样,网络一抖,马上跟着抖,简直是网络质量监控器。

Reply to yfractal #2

Mike 大大也研究过那边的源码了吗👍 ?我理解的服务端推送不管是我们常用的 ActionCable 还是这里说的弹幕应该都是广播的机制吧?基本上都是通过 ws 来推送,服务重启断开了基本都是需要重连的吧?

没。。。我们组是搞 IM 的,所以对这个比较了解。

恩,是的。ActionCable 更像个通知推送系统。做实时的东西就很吃力了。

都会重连,因为有重连、timeout 机制,有的时候会重连多次。

Reply to yfractal #4

我只做过简单的推送系统,实时的东西就没什么经验了,听着就比较复杂。我理解就是一般而言如果是 Web(类 ActionCable 那种)断开了我们就随他了,用户刷页面再重连就完了。你们那些做实时的是不是要经常维护个类似定时器的东西?客户端监测到断开要重新发请求建立连接?

恩,有个 timer,一段时间连不上,就胡重连。ActionCable 我记得也是有重连的。

IM 很多信息,都是走的长连接,长连断了,相当于 app 不可用了。

Reply to yfractal #2

你们 router 的定位是什么?如何设计的,为啥要广播。

我问了一下 bilibili 实际使用,本文中 Router 的设计有些直接废掉了,有些换成了 redis,将 Router 逻辑直接合到 logic。 因为有状态的 Router 一旦挂掉非常麻烦,必须要做高可用,数据还得做强一致性。

其他节点挂掉后,直接重新拉起来就行,影响不大。

Reply to early #7

应该是 comet,不是 router,我看到“有状态”,理解错了,尴尬了。。。

我们的和 ActionCable 差不多,维护用户连接、router、讨论组和人的关系。

如果只是存储关系的话,redis 应该就够了。

最新回覆都是 11 天前⋯⋯

网络一抖,重联时鉴权就够吃一壶的了

弹幕显示性别!

学习了··

学习了😁

early in 直播 -- 如何解决高并发下的热点挑战 mention this topic. 15 May 13:09
early in 直播 (中) -- 核心流程梳理 mention this topic. 15 May 13:13
early in 直播 (上) -- 底层逻辑浅析 mention this topic. 15 May 13:13
You need to Sign in before reply, if you don't have an account, please Sign up first.