弹幕是什么
看过直播的人都难免会注意到屏幕上方飘过的留言,以用户端输入为主,很多视频中也有这种内容的呈现形态。
如果要更细致地理解直播弹幕,用类比法,可以是:
- 一种“群”消息,将某个用户 (也有系统) 产生的消息实时广播给全体“群成员”
- 这个群动辄数百万成员
- 这个群是临时的 (进了某个直播间,一会儿又退出了)
- 消息基于直播间,进直播间才能收到消息
- 这种群消息可分布在多端。APP、Web、H5
- 这种消息阅后即焚,因为和视频画面呼应才有意义 (当然服务端会存档)
简单点讲,弹幕消息就是一种群消息,所以技术界是将弹幕归类为 IM(即时通讯) 范畴,它背后是实时消息。弹幕是实时消息中能被用户看到的内容,还有一部分是系统消息,用来主动触发业务逻辑、行为等。
在直播界,弹幕是一个举足轻重的角色,作为视觉和信息的直接载体,它身上承接了极重的业务形态:
- 广播出和画面内容呼应的内容
- 弹幕内容根据用户身份做特殊呈现 (给了钱的更显眼)
- 弹幕触发其他业务,例如抽奖
- 内容、频率要做管控
这需要有一个中心化的弹幕业务节点,来处理业务逻辑,构造特色化的内容,然后让 IM 系统吐出。所以从系统角度来看,弹幕系统其实是业务系统+IM系统
合作而成的。架构上 IM 系统要和业务解耦,IM 系统只知道将一堆数据吐给对应的用户。
业务系统只是一些繁杂的逻辑组合,并无特别之处。解下来我们将目光转到实时消息如何到达用户
这个问题上来。
挑战
弹幕的主要有两个挑战:
- “群成员”太多,而且数量不稳定,一份消息产生后,需要实时广播给所有成员,消息体量会被放大百万、千万倍。
- 群成员分散各地、跨运营商、跨平台。消息稳定性难保证。
其实这也是 IM 的主要挑战。
解决方案
纵观国内解决方案,跳得最高、资料最丰富的是goim,在业界的接纳度较高,也是用在 bilibili 生产环境的方案。
在一头扎进 goim 之前,我们先来简单梳理一下 IM 的需求及可能的模块,在软件工程目标下做一次 pre 构想,再来看 goim 是如何实现这些目标。
弹幕可见的模块有:
- 连接管理层。处理和用户连接、传输数据给指定的目标用户。
- 业务逻辑层。黑名单 IP、黑名单用户、附加信息、在线数统计等等
- 数据统计层。哪些房间有多少人,某个用户连接情况等等
- 数据处理层。接收消息请求,并转发到连接层
当用户体量小时,以上模块用一个简单的单体服务也可以实现,把逻辑写在一起,不用管什么层级。但是当用户规模大、稳定性要求高、成本敏感时,就需要拥抱软件工程的目标:
一般技术事故都是两个原因,一是外部流量洪峰;二是内部变化,例如改了代码、触发了部署。稳定性和迭代是矛盾的,那该如何尽可能既要熊掌又要鱼呢?
- 根据易变性、伸缩性、业务等拆分模块,让各自独立,通过网络交互,解耦合。
- 通过系统架构设计保证,当流量洪峰来临,只需要简单扩容就可应对。
例如业务逻辑层
就是经常变化的,代码改动概率很大。这时候业务逻辑代码的变化、部署在理论上不能影响到连接管理层
部分。相反连接管理部分一旦稳定变化会很少,但其伸缩需求大,要经常扩缩容,它的部署过程也不能影响其他模块。
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 调度,根据地区、运营商选择用户最适合的节点
结构如下图:
这种结构让服务发现变得难以实施,退而求其次可能需要通过配置文件写死各个服务地址,各个服务实现信号量检测热更新配置,或者对外只暴露代理地址。同时,跨公网调用风险极高,需要一大堆防火墙等策略保证安全。
参考资料
相关话题