Post

十万级直播间弹幕系统设计

十万级直播间弹幕系统的核心是"有损服务"思路——普通弹幕允许丢失、高价值消息零降级,通过削峰、分层、限流、多级降级保障系统稳定

十万级直播间弹幕系统设计

弹幕系统的核心思路

弹幕系统和 IM、Feed 流看着像,差异却很大:

维度IM (微信)弹幕
实时性秒级可接受百毫秒级
一致性必须送达允许丢失
历史消息必看不重要
核心目标不丢不错流畅、稳定

这种”允许丢、但要稳”的特性,决定了弹幕系统必须采用有损服务设计。

普通弹幕允许丢失、不全量送达,但必须保障系统整体稳定;高价值消息(付费、管理员、主播互动)单独走专属通道优先处理。整体围绕削峰、分层、限流、多级降级搭建高并发架构。


整体架构

%%{init: {'theme': 'dark', 'themeVariables': { 'fontSize': '14px', 'nodeBorder': '#666', 'lineColor': '#888', 'clusterBkg': 'transparent' }}%%
graph TB
    Client[用户端] -->|WS 长连接| Gateway[接入层 网关]

    Gateway -->|鉴权/限流/敏感词| Filter{合法?}

    Filter -->|是| MQ[消息队列 Kafka<br/>削峰缓冲]
    Filter -->|否| Drop[直接拦截]

    MQ --> Dist[分发服务<br/>过滤/聚合/采样]

    Dist -->|普通弹幕 采样| WSG1[长连接网关 1<br/>本地广播]
    Dist -->|高价值消息 全量| WSG1
    Dist -->|高价值消息 全量| WSG2[长连接网关 N<br/>本地广播]

    WSG1 --> U1[用户 A]
    WSG1 --> U2[用户 B]
    WSG2 --> U3[用户 C]
    WSG2 --> U4[用户 D]

    style Client fill:#42A5F5,color:#fff
    style Gateway fill:#FF9800,color:#fff
    style Filter fill:#9C27B0,color:#fff
    style MQ fill:#EF5350,color:#fff
    style Dist fill:#7E57C2,color:#fff
    style WSG1 fill:#26A69A,color:#fff
    style WSG2 fill:#26A69A,color:#fff
    style Drop fill:#78909C,color:#fff

四层结构:

  1. 入口层(网关):限流、鉴权、敏感词
  2. 消息层(MQ):削峰、缓冲
  3. 处理层(分发服务):过滤、聚合、采样
  4. 广播层(长连接网关):二级广播
  5. 客户端:兜底降级

一、入口层:限流 + 削峰

用户弹幕请求先经过接入层,完成:

  • 鉴权:登录态校验
  • 禁言校验:被禁言用户直接拦截
  • 频率限制:单用户每秒最多 N 条
  • 敏感词过滤:违规消息直接拦截

合法弹幕写入消息队列(Kafka / RocketMQ),不直接同步写数据库

💡 为什么走 MQ 而不是直写 DB?

十万级直播间的瞬时弹幕峰值可达几万 QPS,直写 MySQL 会瞬间打爆连接池和磁盘 IO。MQ 作为缓冲区,把瞬时洪峰拉平成后端能消化的稳态流量

graph LR
    Req[弹幕请求] --> Auth{鉴权/限流<br/>敏感词}
    Auth -->|合法| MQ[消息队列]
    Auth -->|非法| Block[拦截丢弃]
    MQ -->|异步消费| DB[(数据库)]

    style Auth fill:#9C27B0,color:#fff
    style MQ fill:#EF5350,color:#fff
    style Block fill:#78909C,color:#fff
    style DB fill:#42A5F5,color:#fff

常见 MQ 选型

MQ吞吐延迟适用场景
Kafka⭐⭐⭐⭐⭐日志、弹幕(高吞吐)
RocketMQ⭐⭐⭐⭐金融、订单
Pulsar⭐⭐⭐⭐云原生

弹幕这种对延迟敏感 + 高吞吐的场景,Kafka 是首选,百万级 TPS 不是问题。


二、消息层:过滤 + 聚合 + 采样

分发服务从队列拉取消息后,不会全量推送

消息类型处理方式
重复刷屏(同一用户相同内容)合并 / 降权
普通弹幕随机采样(保留 20% 左右)
付费弹幕(醒目留言)完整保留、优先推送
管理员通知完整保留、强制弹出
主播互动消息完整保留、高亮显示
graph TB
    MQ[消息队列] --> Dist[分发服务]

    Dist --> Filter1{消息类型}

    Filter1 -->|重复刷屏| Merge[合并/降权]
    Filter1 -->|普通弹幕| Sample[随机采样 20%]
    Filter1 -->|付费弹幕| Keep1[完整保留]
    Filter1 -->|管理员通知| Keep2[完整保留]
    Filter1 -->|主播互动| Keep3[完整保留]

    Merge --> WSG[长连接网关]
    Sample --> WSG
    Keep1 --> WSG
    Keep2 --> WSG
    Keep3 --> WSG

    style Dist fill:#7E57C2,color:#fff
    style Filter1 fill:#9C27B0,color:#fff
    style Sample fill:#FFA726,color:#fff
    style Keep1 fill:#EF5350,color:#fff
    style Keep2 fill:#EF5350,color:#fff
    style Keep3 fill:#EF5350,color:#fff

💡 为什么要采样?

人眼每秒能”看清”的弹幕约 5-10 条。即使你给用户推 1000 条,他根本看不过来。既然用户感知不到,不如在服务端就丢弃,省下来的带宽和渲染开销可以做更多事。

采样策略通常用令牌桶 / 滑动窗口

  • 每个房间 1 秒内最多推送 N 条
  • 超出的弹幕进入降级队列(延后或丢弃)

三、分发层:二级广播

1
中心分发服务 → 多台长连接网关 → 本地广播

为什么不中心直推?

如果 10w 用户都在线,中心分发服务要给 10w 个连接各发一遍消息,单机扛不住百万级下行流量

二级广播的好处:

角色职责数量
中心分发服务把整理好的批量推给每台网关少量(几十台)
长连接网关本地广播给维护的在线用户多台(几百~几千台)
graph TB
    Center[中心分发服务]

    Center -->|批量推送 1| G1[网关 1<br/>5000 连接]
    Center -->|批量推送 2| G2[网关 2<br/>5000 连接]
    Center -->|批量推送 N| GN[网关 N<br/>5000 连接]

    G1 -->|本地广播| U1[用户群 1]
    G2 -->|本地广播| U2[用户群 2]
    GN -->|本地广播| UN[用户群 N]

    style Center fill:#7E57C2,color:#fff
    style G1 fill:#26A69A,color:#fff
    style G2 fill:#26A69A,color:#fff
    style GN fill:#26A69A,color:#fff

假设:

  • 1 台网关维护 5000 个 WebSocket 连接
  • 10w 用户 → 需要 20 台网关
  • 中心服务只需推 20 次,不再是 10w 次

💡 二级广播的本质:把”一对多”的压力,沿网状拓扑分摊到边缘


四、客户端:兜底降级

终端设备不会无脑接收渲染弹幕,会根据自身性能动态调整:

  • 低端机:只渲染前 5 条,丢弃后续
  • 高性能设备:渲染 20-30 条
  • 网络差:合并多条一起渲染,减少 DOM 操作
graph TB
    Recv[收到弹幕] --> Check{设备性能/网络}
    Check -->|低端| Drop1[只渲染前 5 条]
    Check -->|中端| Drop2[渲染 10-15 条]
    Check -->|高端| Drop3[渲染 20-30 条]

    Drop1 --> Render[渲染]
    Drop2 --> Render
    Drop3 --> Render

    style Check fill:#9C27B0,color:#fff
    style Drop1 fill:#78909C,color:#fff
    style Drop2 fill:#FFA726,color:#fff
    style Drop3 fill:#66BB6A,color:#fff

💡 客户端降级是最后一道防线。即使服务端漏过去 1000 条弹幕,客户端也能自我保护


核心设计哲学

原则体现
有损服务普通弹幕可丢,业务目标不是 100% 送达
分层解耦4 层各司其职,单层故障不蔓延
优先级付费 > 管理 > 主播 > 普通
端到端降级服务端 + 客户端双重兜底
削峰填谷MQ 缓冲瞬时洪峰
二级广播拆分下行压力,规避百亿级流量

面试话术

弹幕系统本质是有损服务 + 优先级队列的设计。普通弹幕采样推送保证性能,高价值弹幕(付费/管理)全量推送保证业务。多级降级链路:入口限流 → MQ 削峰 → 采样合并 → 网关二级广播 → 客户端兜底。核心目标是系统可用性 + 核心消息零降级,而不是 100% 消息送达。


延伸思考

  • 实时性怎么保证? P99 延迟做到 200ms 以内怎么做?
  • 长连接怎么保活? WebSocket 心跳 + 重连机制
  • 网关怎么水平扩展? 一致性哈希做房间路由
  • 弹幕数据怎么存? 冷热分层(Redis 7天 + HDFS 归档)
  • 敏感词过滤怎么做? AC 自动机 + Trie 树,10w 词库性能如何?
  • 如何防刷? 设备指纹 + 行为分析 + 验证码三层
This post is licensed under CC BY 4.0 by the author.