Skip to main content

24. Design Bullet Screen System 弹幕系统

info

本节将介绍弹幕系统的设计,包括其架构、关键组件以及实现细节。

aka Danmaku System

00 Resources

01 Background 背景

用户通过发送弹幕、送礼等,可以实时在直播画面上展现自己的想法、评论和互动内容,从而丰富了用户观看体验。可以类比的是:

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

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

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

  • 广播出和画面内容呼应的内容
  • 弹幕内容根据用户身份做特殊呈现 (给了钱的更显眼)
  • 弹幕触发其他业务,例如抽奖
  • 内容、频率要做管控
type Bullet struct {
ID string // 弹幕唯一ID,用于去重/追踪
UserId int // 用户ID
Nickname string // 用户昵称(展示用,可缓存)
Content string // 文本内容
Timestamp int64 // 绝对时间戳(发出时的毫秒)
Offset int // 相对直播/视频的时间(秒或毫秒,方便回放)

Extra *Extra // 样式 & 效果(颜色、位置、动画等)

// 扩展
RoomId int // 所属直播间/视频ID
Type string // 弹幕类型: text/gift/like/emoji/system
FontSize int // 字体大小
Color string // 颜色值,如 "#FF0000"
Position string // 位置: top/bottom/scroll
Device string // 发送设备信息 (web/ios/android)
IsVip bool // 是否会员/大航海
IsAdmin bool // 是否管理员/房管
IsPinned bool // 是否置顶/特殊弹幕
}

1.1 挑战和需求

直播弹幕是一个读写 QPS 要求都很高,假设一个直播间有 100w 用户同时在线观看,假设弹幕的提交频率为有 10000条/秒,那么需要每秒同时推送给在线用户的次数为 100w * 10000。由此可见,读请求的吞吐量需要远大于写请求,这点类似于 IM 实时聊天。

架构设计考虑以下几个场景:

  • 支持直播弹幕回放 -> 意味着需要持久化 DB
  • 用户进入直播间可以推送最新几秒的弹幕数据 -> 意味着需要多级缓存
  • 长连模式和短连模式可以做降级切换 -> 保证高可用
  • 尽可能地减少消息体的大小,节省带宽 -> 批请求 + 消息体压缩

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

02 系统架构

2.1 MVP 版本

为了不影响读写的性能,采用读写分离架构。

  • 写服务
    • 若不考虑历史弹幕可回放,可以直接使用 Redis 作为唯一存储。
    • 若考虑支持弹幕的回放,数据还是需要持久化,可以考虑使用 MySQL 或者 TiDB,暂且认为写入不是较大的瓶颈。
    • 如果有更高性能的写需求,HBase、OpenTSDB 等都可以解决问题。
  • 读服务
    • Redis 主要用于读缓存,缓存直播间最新的弹幕数据,采用直播间 ID 作为 Key。
    • 系统读服务最大 QPS = Redis 集群QPS。

Redis 存储结构选择 -> SortedSet

  • 提交弹幕: ZADD. score 设置为时间戳。进一步优化可以只存储时间的 delta 值,减少数据存储量。
  • 弹幕查询: ZRANGEBYSCORE 定时轮询弹幕数据。