Spiga

B站架构设计8:弹幕系统架构设计

2021-11-27 21:04:45

一、弹幕概述

1. 弹幕的几个概念

  • 弹幕模式 mode:

    • 从右向左(从左到右)滚动的弹幕

    • 底部(顶部)弹幕

    • 代码弹幕

    • 脚本弹幕

  • 弹幕池 pool:

    • 普通弹幕池

    • 字幕弹幕

    • 特殊弹幕

    • 推荐弹幕

    • 被up主屏蔽的弹幕

    note: 普通弹幕池弹幕长度限制300,特殊弹幕池无长度限制

  • 弹幕上限 maxlimit:

    每个视频要展示的弹幕数,根据视频时长设置,普通弹幕 + 保护弹幕,以及推荐弹幕最大展示 maxlimit 条,字幕弹幕、特殊弹幕不受限

    常规值:100,300,500,1500,3000,6000,8000,黑科技值:16000,32000

2. 弹幕的几个特点

  • 实时性要求不高:
    • 弹幕文件不像其他业务需要实时性的加载
    • 实时性弹幕可以通过弹幕广播实现
  • 一次性加载:
    • 和评论业务不同,弹幕需要在视频点击播放后一次性加载,comment.bilibili.com/cid.xml
    • 弹幕文件比较大
  • 用户相对敏感,有些用户比较关注自己发过的弹幕是否存在

3. 弹幕的现状

  • 数据总量:100亿+
  • 单表记录:7000万+,且按照 cid 范围分表,导致数据分布严重不均
  • 单个视频弹幕数:500w+
  • 单个视频展示条数:极个别 1w+,常规 3000~8000,且一次性返回
  • 单条弹幕大小:长度无上限,可以通过文本上传
  • 单个弹幕文件超过 10M http://comment.bilibili.com/4279031.xml
  • 数据库慢查询多如狗

二、老弹幕系统架构

1. 架构图

2. 老系统刷新逻辑

  • dmCachecheck 通过扫描 dm_index 中的标志位来找到所有视频分批弹幕数
  • 通过databus发送稿件弹幕数到 Stat-T

3. 老系统弹幕缓存

  • 何时刷新缓存

    • dm_needupdate: memcache缓存刷新标志
    • dm_cacheupdate: memcache+redis缓存刷新标志
  • 由于弹幕 xml 超过 1M,缓存如何存放,由于 memcache 对于大于1M文件的存取存在一些问题,因此生成缓存后将大于1M的 xml,放入NFS文件系统

  • 优先从哪里获取缓存,老的弹幕系统在弹幕的索引 dm_index 表中增加 childpool 字段标识应该优先从哪里获取缓存,childpool=2:文件系统

  • 标示数据来源

    • header("X-Cache: HIT from fc-cluster")
    • header("X-Cache: HIT from mc-cluster")
    • header("X-Cache: HIT from md-cluster")

三、重构的几个手段

1. 数据库设计

  • 重要业务单独实例,单独数据库

    将弹幕数据库实例和其他服务实例分开;服务内部将重要和非重要业务分开

  • 索引和内容分开

    将经常变更的数据放入索引表,如状态、属性、弹幕池;将静态数据数据放入内容表,如弹幕内容、模式、字号、颜色;

  • 通过分表减小单表数据量

    将分表策略改为按照 cid 分表,索引表以及内容表分别分表1000张,从原来单表7000w到目前的300w

  • 特殊弹幕内容单独存一张表

  • 通过顶队列保证尽量少的候选集

2. 顶队列实现

鉴于单个视频弹幕候选集超过200w的情况,可以通过实现一个 redis 顶队列来保证候选集尽可能的小,redis sortset 实现如下:

  • key:普通弹幕,保护弹幕,字幕弹幕,特殊弹幕,推荐弹幕
  • field:fmt.Sprintf(“%d.%d”, 弹幕属性, 弹幕id)
  • member:弹幕id

每次新增完之后,返回当前队列长度,将超过maxlimit的弹幕状态从正常改为隐藏状态。

每次删除一条弹幕后,则从数据库获取一条最近被隐藏的一条弹幕从隐藏改为正常

3. 智能弹幕

智能弹幕定义:大数据根据用户针对弹幕的历史行为学习,给每个用户关注的弹幕进行弹幕打分,在智能弹幕中该分值称为flag,这样每个用户看到的同一条弹幕的分值不同。

默认分值:

  • 根据历史行为推荐默认 flag 等级
  • 返回更多的弹幕条数保证打开智能弹幕前后尽量小的 diff

根据视频时长,分段加载弹幕

  • 根据视频的时长分为6分钟一段,节省流量,减少接口压力

智能弹幕的实现:

智能弹幕协议设计:弹幕 flag 信息内容长度(uint32)| 弹幕 flag 信息(json)| gzip{ xml 文本(xml)}

智能弹幕缓存:

  • 一级缓存mc
    • 存每个视频分段的弹幕
    • 缓存每个视频的时长
  • 二级缓存redis,结构sortset
    • key:
      • 普通弹幕: i_type_oid_cnt_n
      • 特殊弹幕: s_type_oid
      • 顶弹幕队列:q_oid
    • score: 弹幕 id
    • member: 弹幕 id
  • 二级缓存redis,结构hashset
    • key: c_oid
    • field: 弹幕 id
    • value: 单条 xml

4. 几个优化点

  • 去除非必要逻辑,提升性能

    不再解析大数据返回的弹幕id,而是直接拼装协议返回

  • 稿件弹幕数优化,通过稿件服务拿到某个 aid 下的所有 cid,累加弹幕数后发给 Stat-T

  • 弹幕实时性

    • 新增弹幕后直接追加 memcache
    • 弹幕广播接入 dm-job + goim-chat
  • memcache 并发锁,该做法目前属于比较 low 的做法,建议会改为 single flight方式;

  • singleflaght 并发锁

  • http code 304 实现