十万级直播间弹幕系统设计
十万级直播间弹幕系统的核心是"有损服务"思路——普通弹幕允许丢失、高价值消息零降级,通过削峰、分层、限流、多级降级保障系统稳定
弹幕系统的核心思路
弹幕系统和 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
四层结构:
- 入口层(网关):限流、鉴权、敏感词
- 消息层(MQ):削峰、缓冲
- 处理层(分发服务):过滤、聚合、采样
- 广播层(长连接网关):二级广播
- 客户端:兜底降级
一、入口层:限流 + 削峰
用户弹幕请求先经过接入层,完成:
- 鉴权:登录态校验
- 禁言校验:被禁言用户直接拦截
- 频率限制:单用户每秒最多 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 词库性能如何?
- 如何防刷? 设备指纹 + 行为分析 + 验证码三层