抖音直播卖货和 B 站直播的弹幕技术实现方案
前言
本文将介绍抖音直播卖货和 B 站直播的弹幕技术,不涉及直播视频流媒体技术,只关注非视频内容(比如弹幕)。值得一提的是,本文不涉及大型直播技术,所讲案例仅作为面试思路参考。
代码下载
完整的案例代码可以从 这里 下载得到。
通信模型
常见的客户端与服务端通信模型有以下几种:
Push 模型(推送模型)
- 实时性最好,是典型的 “发布 - 订阅(Pub / Sub)” 模型(如 WebSocket、MQ 推送)。
- 需要维护长连接(如 TCP / WebSocket),服务器需持续占用连接资源。
- 适用场景:
- 实时性要求极高(行情、IM、通知推送)
- 客户端数量可控
- 服务端数据变化频繁
Pull 模型(拉取模型)
- 客户端主动定时请求服务端获取数据(轮询)。
- 优点:
- 实现简单
- 服务端无连接压力(无状态)
- 缺点:
- 实时性较差(取决于轮询间隔)
- 可能产生大量无效请求(数据没变也请求)
- 适用场景:
- 实时性要求不高
- 客户端数量较多(容易横向扩展)
Long Polling 模型(长轮询模型)
- 本质是对 Pull 的优化:客户端发起请求后,当服务端发现没有数据,不会立即返回响应结果,而是 “挂起等待”
- 当数据发生变化或超时后服务端再返回响应结果,客户端再发起下一次请求(伪实时 + HTTP 兼容)
- 优点:
- 实时性接近 Push
- 无需维护真正的长连接(比 WebSocket 简单)
- 缺点:
- 服务端需要维护大量挂起请求(线程 / 连接压力)
- 实现复杂度高于普通轮询
- 适用场景:
- 需要较高实时性,但不方便使用 WebSocket
- 兼容性要求高(HTTP 场景)
- 实现方式:
- Long Polling 本身并不依赖 epoll 等 I/O 多路复用技术,它只是应用层的一种通信模型
- 但在高并发场景下,由于 Long Polling 会产生大量挂起的线程 / 连接,服务端通常会结合 epoll 等 I/O 多路复用技术来提高连接处理效率,避免线程资源被耗尽
技术选型说明
弹幕系统属于强实时、高并发、服务器主动推送场景,因此最适合使用 Push 模型(通常基于 WebSocket 实现);Long Polling 只作为兼容或降级方案,而 Pull 模型基本不适用(中小型系统除外)。
业务分析

设计思路

问题一:用户首次进入某个直播间后,如何拉取弹幕数据?
- 按照业务做如下约定,用户进入某个直播间后,默认只拉取最新的前 5 条弹幕,因为不可能呈现全部弹幕数据,这类似分页显示。
- 记录当前用户拉取弹幕数据的时间,并存入 Redis,提供给用户下次拉取弹幕数据使用。
问题二:用户持续观看直播时,如何将弹幕实时推送给用户?
- 直播客户端(APP)通过定时器周期性地拉取弹幕数据,比如每隔 5 秒钟请求一次直播服务。
- 直播服务器接收到请求后,采用时间范围的拉取,查询用户上次拉取弹幕数据的时间到当前请求时间内产生的弹幕数据。
- 记录当前用户拉取弹幕数据的时间,并存入 Redis,提供给用户下次拉取弹幕数据使用。
问题三:应该用什么样的数据结构来存放高并发实时的弹幕数据?
- 使用 Redis 的 ZSet 数据类型来存放弹幕数据,具体来说就是每个直播间的弹幕数据都存放在一个独立的 ZSet 中。
- ZSet 是不可重复的有序集合,可以根据指定的
score进行排序,而且还可以很方便地获取指定分数范围内的元素。
案例代码
数据库表
1 | CREATE TABLE `t_barrage` ( |
配置信息
1 | # MySQL |
基础代码
- 常量类
1 | public class Constants { |
- Redis 配置类
1 |
|
- 实体类
1 |
|
- Mapper 类
1 | public interface BarrageMapper extends BaseMapper<Barrage> { |
核心代码
- 任务调度类,模拟直播间内不同的用户发送弹幕
1 |
|
- 业务接口
1 | public interface BarrageService { |
- 业务实现类
1 |
|
- 控制器类
1 |
|
测试结果
数据校验
- 在 MySQL 数据库中,查询到的部分数据如下

- 在 Redis 中,获取房间号为 100 的直播间的最新 5 条弹幕数据,语法:
ZREVRANGE key start stop [WITHSCORES] 通过索引区间返回有序集中指定区间内的成员,其中成员的位置按分数值从大到小来排序
1 | zrevrange room:100 0 4 withscores |
1 | 1) "{"@class":"com.clay.scene.entity.Barrage","id":15,"roomId":100,"userId":76521,"content":"发送弹幕: 1\t5","createTime":["java.util.Date",1725093606766]}" |
- 在 Redis 中,获取房间号为 100 的直播间在某个时间段内的弹幕数据,按分数(时间戳)从小大到排序
1 | zrangebyscore room:100 1725097605 1725097614 WITHSCORES |
1 | 1) "{"@class":"com.clay.scene.entity.Barrage","id":241,"roomId":100,"userId":70800,"content":"发送弹幕: 3\tByM","createTime":["java.util.Date",1725097609115]}" |
