高并发弹幕系统架构设计
高并发弹幕系统架构设计
技术复杂度
对于一个直播间(头部主播):
- 在线人数100w
- 弹幕频率 1000条/秒
那么服务端推送频率就是:100w * 1000条/秒 = 10亿条/秒
如果是N个直播间,那么推送频率是:10N亿条/秒
方案对比:拉 vs 推
(客户端)拉模式
(服务端)推模式:基于websocket协议,可以看Web端通信-websocket协议(已整理)了解ws协议。
语言技术选型
NodeJS:
- 单线程模型。需要遍历非常多的用户集合,性能有限,不适合做推送。
C/C++:
- tcp通讯、ws协议实现成本高,需要diy
Go:
- 多线程,推送性能高
- 基于协程模型,能实现高并发
- websocket是标准库,无需要其他社区库
技术难点和解决方案
内核瓶颈与优化
优化方案:
服务端将一秒内的n条消息,合成一条消息推送
实现思路:弄个消息队列,每隔1秒来扫描它,然后将消息队列中积累的数据依次发送给客户端
合并后的效果,每秒的推送次数只等于在线连接数
锁瓶颈与优化
优化方案(GoLang下,可以利用多线程模型提高遍历性能):
- 大锁拆小锁,不上全局锁:将用户连接打散到多个用户集合中,每个集合有自己的锁
- 多线程并发推送多个用户集合,提高遍历性能,避免锁竞争
- 读写锁取代互斥锁:多个推送任务都可以同时获取某个用户集合的读锁,然后遍历它们进行推送
CPU瓶颈
优化方案:
- json编码前置:不用每次推送都推送json格式,在推送前,将json进行编码,之后的推送用编码后的数据,避免百万次重复编码
- 消息合并前置:N条消息合并成一条消息后,大包数据只需要编码一次
分布式架构
单机架构图:
单机瓶颈:
分布式架构图(这图画的是真丑):
gateway网关集群:就是前面实现的服务,他们负责将ws数据推到对应的连接上。
logic逻辑集群:用来接收自家客户端业务发来的ws数据,然后将他们广播给网关集群。
业务方和连接:业务方连接连的是gateway服务,用来接收数据;但是发送ws数据走的logic服务。
总体思想:接收消息和推送消息分为2个集群,不放在一个服务中。
弹幕系统前端设计
核心点
- 使用等待队列来存储弹幕消息,定时消费
- 使用Pool池化技术来避免多余dom节点浪费
- 对于过多的消息,直接丢弃
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
interface IDomContainer {
dom: HTMLElement // DOM 结构
text: string // 弹幕信息
}
class DM {
private waitQueue: string[];
private queueSize: number;
private interval: number;
private domPool: IDomContainer[]; // domPool
private poolSize: number;
constructor() {
this.waitQueue = [];
this.queueSize = 10000; // 防止内存被撑爆
this.interval = 100;
}
add(text: string) {
if (this.waitQueue.length >= this.queueSize) {
return;
}
this.waitQueue.push(text);
}
time() {
setInterval(() => {
if (!this.waitQueue.length) {
return;
}
const consumeList = this.waitQueue.splice(0, 10);
consumeList.forEach(item => this.render(item));
}, this.interval);
}
render(text: string) {
if (this.domPool.length >= this.poolSize) {
return;
}
if (!this.domPool.length) {
const c = {
dom: document.createElement('div'),
text,
};
this.domPool.push(c);
}
const c = this.domPool.pop() as IDomContainer;
this.drawDanMuEle(c);
this.recover(c);
}
drawDanMuEle(container: IDomContainer) {
// 向dom节点,绘制弹幕内容(text)
const { dom, text } = container;
// 省略其他处理,比如dom节点的屏幕动态
}
recover(container: IDomContainer) {
container.text = '';
this.domPool.push(container);
}
}
参考
本文由作者按照 CC BY 4.0 进行授权







