设计一个弹幕系统

本文系统设计一个高并发的视频弹幕系统:弹幕模型与发送/展示链路、WebSocket 长连接与推送通道、弹幕渲染轨迹(车道算法)与流量控制、弹幕审核过滤与内容安全、弹幕持久化与回放、热点视频的扇出优化、弹幕统计,并给出架构图、数据表、伪代码与量级估算。

弹幕是视频平台最热闹的互动形态:用户在视频的某一秒看到一行滚动的评论从屏幕滑过,一瞬间的「在场感」让千万人像一起看片。但从系统设计视角,弹幕是高并发的实时写 + 高扇出的实时推 + 需要内容安全过滤 + 需要持久化回放的综合系统。本文按照系统设计面试的标准答题结构,设计一个支持千万级在线的视频弹幕系统。

一句话:弹幕系统的核心是「发的秒级送达 + 千万人同时可见」——写路径要扛住高峰弹幕洪峰,读路径要毫秒级推送到海量在线观众,两者靠 WebSocket 长连接与分片扇出串联。

一、需求澄清与量级估算

1.1 需求澄清

面试官给出题目「设计一个弹幕系统」后,先通过提问明确边界:

  • 弹幕发送:用户在视频播放过程中发送弹幕,是文本还是支持表情/颜色/字号?
  • 展示模式:滚动弹幕(从右到左)、顶部固定、底部固定,都要吗?
  • 时效性:弹幕需要「实时」还是「同步到回放」?回放多久可见?
  • 实时规模:多少用户同时在线的热门视频?(峰值决定扇出架构)
  • 内容安全:弹幕是否要过滤(敏感词/广告/刷屏)?审核通过前能否展示?
  • 统计:是否要弹幕量、弹幕时段热力等统计?

明确假设(面向面试的合理假设):

需求项假设
弹幕形态文本弹幕,支持颜色/字号,滚动 + 顶部固定
时效发送到展示 < 1 秒;回放实时拉取历史弹幕
规模全站在线 1000 万观众,热门视频单视频同时 100 万人在线
内容安全先发后审(异步过滤),敏感词拦截,刷屏限频
统计分钟级弹幕量、时段热力
持久化弹幕需回放,视频生命周期内可查

1.2 量级估算

指标估算值推导
同时在线观众1000 万全站峰值
热门视频同屏100 万单视频最高并发
弹幕发送峰值5 万条/秒高能时刻(名场面/直播回放)
单条弹幕扇出100 万份一屏观众都看到
全站推送总量数十亿条/秒峰值弹幕 × 扇出
弹幕持久化日 2 亿条覆盖全量视频

一句话:5 万条/秒写入 × 100 万扇出 = 推送总量数十亿条/秒,弹幕是「写得多、推得更多」的系统——扇出必须靠长连接通道分组与广播,绝不能逐用户建消息。

二、高层架构设计

  ┌─────────────────────────────┐   ┌───────────────────────────┐
  │ 观众端(播放器)              │   │ 视频 / 直播平台             │
  │ 发弹幕 / 收弹幕 / 渲染轨迹    │   │ 视频元数据 / 播放会话       │
  └─────────────┬───────────────┘   └─────────────┬─────────────┘
                │ 发送请求(WS)                      │ 拉取回放弹幕
  ┌─────────────▼──────────────────────────────────▼─────────────┐
  │                     弹幕接入网关(WebSocket 集群)              │
  │      长连接管理 / 心跳保活 / 房间分组 / 连接配额与限频           │
  └──────┬───────────────────────────────┬───────────────────────┘
         │ 新弹幕事件                      │ 房间推送
  ┌──────▼─────────┐              ┌────────▼──────────────────┐
  │  弹幕写服务     │              │  实时推送服务(扇出)        │
  │  - 内容过滤     │              │  - 房间订阅表 / 连接映射    │
  │  - 敏感词/限频  │              │  - 分片广播 / 批量下行     │
  │  - 持久化(异步) │              │  - 连接池直推/推流网关      │
  └──────┬─────────┘              └────────┬──────────────────┘
         │                                  │
  ┌──────▼─────────┐              ┌────────▼────────────────┐
  │  存储层         │              │  历史弹幕服务           │
  │  Redis:房间    │              │  分页/时间窗口拉取      │
  │  弹幕流/计数     │              │  回放缓存              │
  │  MySQL/HBase: │              └─────────────────────────┘
  │  弹幕明细/热力  │
  └────────────────┘

整体拆为五个模块:

  1. 接入网关:WebSocket 长连接集群,管理千万级在线连接,按视频房间分组。
  2. 弹幕写服务:接收弹幕、内容过滤、限频、异步持久化。
  3. 实时推送服务:把新弹幕扇出到房间内所有连接,分片广播 + 批量下行。
  4. 历史弹幕服务:回放时按时间窗口/分页拉取。
  5. 存储:Redis(房间实时弹幕流、在线人数)、HBase/MySQL(持久化明细、热力)。

2.1 为什么选 WebSocket 长连接

弹幕是「双向、低延迟、高频」互动,推送通道的选择决定了扇出能力:

轮询(HTTP 短连接):延迟高、连接成本高,千万在线不可行
SSE:单向推送,不支持上行 → 上行还得另开通道
WebSocket:双向长连接、头部开销小、服务端可主动推 → 唯一务实选择

连接管理:
  播放器进入视频 → 建连并注册到「房间」→ 心跳保活
  连接配额:单网关可挂的连接上限 → 网关水平扩展支撑千万级

一句话:WebSocket 长连接让「推」成为可能——服务端能在弹幕产生的一瞬间把消息推进千万个已建立的信道,而不是让千万用户轮询拉取。

三、核心组件设计

3.1 弹幕模型与发送链路

弹幕是「视频维度的带时间戳短消息」:

CREATE TABLE danmaku (
  danmaku_id   BIGINT PRIMARY KEY,     -- 雪花ID,全局递增
  video_id     BIGINT,                 -- 所属视频
  user_id      BIGINT,
  content      VARCHAR(512),
  ts_ms        INT,                    -- 视频时间轴(毫秒)
  color        VARCHAR(8),
  mode         TINYINT,                -- 1滚动 2顶部固定 3底部固定
  status       TINYINT,                -- 0待审 1展示 2屏蔽 3删除
  created_at   DATETIME
);
CREATE INDEX idx_video_ts ON danmaku (video_id, ts_ms);
发送链路:
  观众输入 → 发送请求(带 video_id + ts_ms + content)
  ① 限频:同用户 N 秒内最多 M 条(防刷屏)
  ② 内容过滤:敏感词拦截 + 语义模型判定(命中即不展示)
  ③ 分配 ID:雪花ID(保证全局有序)
  ④ 推送:写 Redis 房间流 + 扇出到房间连接
  ⑤ 持久化:异步批量落库(回放用)
  ⑥ 统计:递增房间弹幕计数(热力)

3.2 房间分组与扇出

扇出是弹幕系统吞吐的命门。**房间(video_id)**是扇出单位,一个房间一条弹幕推给房间内所有连接:

房间模型:
  Redis: room:{video_id}:online → 该房间连接(或连接所属网关)集合
  Redis: room:{video_id}:stream → 实时弹幕流(滚动窗口,最近 N 条)

扇出路径(一次弹幕的旅程):
  写服务 → Redis stream 追加
  → 房间订阅(网关集群按房间分发)
  → 网关本地批量下行到本网关上的连接
  → 播放器收到渲染

关键优化:
  - 按「网关分组」分发,而不是逐用户:一个网关只收一份再本地广播
  - 下行批量:同一网关多连接同帧合并写(提高吞吐)
  - 缓冲削峰:网关下行队列缓冲,慢消费者不阻塞快的
;; 伪代码:按网关分组扇出
(defn fanout [room-id danmaku]
  (let [gateways (redis/smembers (str "room:gw:" room-id))]
    (doseq [gw gateways]
      ;; 每个网关一份消息,网关本地广播给其连接
      (push-to-gateway gw {:room room-id :danmaku danmaku}))))

;; 网关侧:本地批量下行
(defn gateway-broadcast [conn-mapping msg]
  (doseq [batch (partition-all 1000 conn-mapping)]   ; 每批 1000 连接
    (async/send-all! batch msg)))                    ; 合并写同帧

3.3 弹幕渲染:车道算法与流量控制

弹幕渲染在客户端,但系统设计要保证「不重叠、不卡屏」。车道算法是关键:

车道(Lane)模型:
  屏幕高度均分 N 条滚动车道(如 12 条)
  每条弹幕进入一条车道,从左往右匀速移动
  车道占用:弹幕宽度 + 速度 → 预计离开时间 → 车道选择

分配策略:
  ① 顺序轮转(简单):按发送顺序轮流进车道 → 可能头尾相接
  ② 密度感知(更好):优先进入「当前负载低」的车道 → 减少重叠
  ③ 限流保护:同屏弹幕数超阈值 → 丢弃低频权重弹幕(不影响主体验)
客户端渲染约束:
  单屏弹幕上限:如 50 条(超限按优先级丢弃)
  弹幕字号/速度:统一基准,避免视觉噪声
  暂停/跳转:seek 后从该时刻历史弹幕重新灌入

要点:弹幕「不重叠」本质是车道调度问题——服务端控制「发多少」与「发谁的」,客户端控制「怎么排」,两端配合才能在大弹幕量下保持可读性。

3.4 内容安全过滤

弹幕是实时内容,过滤要「快」且「不阻塞展示」:

两级过滤:
  ① 同步轻过滤(毫秒级):敏感词精确/变体命中 → 直接拦截不展示
  ② 异步重过滤:语义模型 + 人工抽审 → 命中后撤回/隐藏该弹幕
  限频:同用户高频发送触发冷却(防刷屏)

工程要点:
  敏感词库:分级词库(高危直接拦,中危进重过滤)
  过滤结果落库:status 字段驱动展示/屏蔽切换
  撤回机制:已展示弹幕被判定违规 → 下发「撤回指令」删除客户端缓存

3.5 历史弹幕与回放

观众进入视频瞬间看不到已发生的弹幕,需要拉历史:

冷启动拉取:
  进房间 → 立即拉「最近 N 条弹幕」(redis room stream)→ 快速铺屏
  再按需拉「从视频开头到当前时刻」的分页历史(覆盖 ts_ms 窗口)

回放数据路径:
  Redis 只存最近窗口(滚动),全量历史走持久化存储
  分页查询:video_id + ts_ms 窗口 + 分页游标(idx_video_ts 索引)
  seek 场景:按目标时刻 ts 分页拉取该窗口弹幕
;; 伪代码:历史弹幕分页
(defn fetch-history [video-id from-ts cursor limit]
  (SELECT * FROM danmaku
   WHERE video_id = ? AND ts_ms >= ? AND status = 1
   AND danmaku_id < ?              -- 游标分页(ID 有序)
   ORDER BY ts_ms, danmaku_id
   LIMIT ?))

四、深入权衡

4.1 Redis 房间流 vs 全量扇出

方案机制适用
Redis Stream/List 房间流写一次,房间成员各自消费回放、冷启动
网关直接扇出写一次,网关按分组广播实时推送热路径

结论:实时推送走「网关分组扇出」(低延迟热路径),回放冷启动走「Redis 房间流」(一次写入多消费)——两者互补,而不是互相替代。

4.2 弹幕持久化选型

存储优势短板
HBase(宽表)海量追加写、按 key 高效扫运维复杂
MySQL 分库分表事务、索引成熟写入瓶颈、需分片
Kafka 桥接数仓高吞吐缓冲实时查询需再落地

结论:弹幕是「高写低更新」的追加型数据,首选 HBase 或「Kafka 缓冲 + MySQL/HBase 落地」组合;面试侧重讲清「写入要扛 5 万/s,查询是按 video+ts 的范围扫描」即可。

4.3 内容过滤的实时性 vs 准确性

先发后审带来「违规弹幕可能已展示一瞬间」的风险,全审(先审后发)又会拖慢实时性:

方案:分层策略
  - 高危词/广告:同步拦截(零展示延迟风险)
  - 一般内容:先展示 + 异步重过滤 + 撤回指令
  - 重大活动/直播:降级为先审后发(保安全牺牲体验)

一句话:弹幕过滤的取舍是「同步拦高危、异步纠一般、重大场景降级全审」——用撤回机制把异步过滤的失误补救回来,兼顾实时与安全。

五、总结

弹幕系统的技术主线是写得进、推得出、画得开、回得去:写路径用「限频 + 敏感词拦截 + 雪花 ID + 异步持久化」扛住 5 万条/秒的发送洪峰;推路径用「WebSocket 长连接 + 房间分组 + 网关级扇出 + 批量下行」让一条弹幕在毫秒内抵达百万观众;渲染用「车道算法 + 单屏上限」保证不重叠不卡屏;回放用「Redis 房间流冷启动 + 持久化分页历史」。内容安全以「同步拦高危、异步重过滤 + 撤回」守住底线。弹幕系统的本质,是把「一人一句话」放大成「千万人共享的在场感」,而系统设计要做的,就是让这份热闹来得快、来得准、来得干净。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「design」更多文章

  1. 设计一个权限系统(RBAC + ABAC)
  2. 设计一个分布式任务调度系统
  3. 设计一个内容审核系统