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

early · March 08, 2020 · Last by Qwaz1314 replied at November 07, 2020 · 14620 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

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

Reply to lanzhiheng

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

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

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

Reply to yfractal

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

Reply to lanzhiheng

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

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

Reply to yfractal

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

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

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

Reply to early

应该是 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.