免费下载案例集|20+数字化领先企业人才培养实践经验 了解详情
写点什么

百万用户同时在线的高并发直播弹幕系统是如何实现的?

  • 2020-02-24
  • 本文字数:3521 字

    阅读完需:约 12 分钟

百万用户同时在线的高并发直播弹幕系统是如何实现的?


导读:直播弹幕是直播系统的核心功能之一。如何迅速作出一个有很好扩展性的弹幕系统?如何应对业务迅速发展?相信很多工程师/架构师都有自己的想法。本文作者经历了直播弹幕从无到有,从小到大的过程,是对构建弹幕系统的经验总结。

直播弹幕指直播间的用户,礼物,评论,点赞等消息,是直播间交互的重要手段。


美拍直播弹幕系统从 2015 年 11 月到现在,经过了三个阶段的演进,目前能支撑百万用户同时在线。


本文比较好地诠释了根据项目的发展阶段,直播弹幕系统进行平衡演进的过程。这三个阶段分别是快速上线,高可用保障体系建设,长连接演进。

一、快速上线

消息模型

美拍直播弹幕系统在设计初期的核心要求是:快速上线,并能支撑百万用户同时在线。


基于这两点,我们的策略是前中期使用 HTTP 轮询方案,中后期替换为长连接方案。


因此在业务团队进行 HTTP 方案研发的同时,基础研发团队也紧锣密鼓地开发长连接系统。


直播间消息,相对于 IM 的场景,有如下几个特点:


  • 消息要求及时,过时的消息对于用户来说不重要;

  • 松散的群聊,用户随时进群,随时退群;

  • 用户进群后,离线期间(接听电话)的消息不需要重发。


对于用户来说,在直播间有三个典型的操作:


  • 进入直播间,拉取正在观看直播的用户列表;

  • 接收直播间持续发布的弹幕消息;

  • 自己发消息。


我们把礼物,评论,用户的数据都当做消息来看待。


经过考虑选择了 Redis 的 sortedset 存储消息,消息模型如下:


  • 用户发消息,通过 Zadd,其中 score 消息的相对时间;

  • 接收直播间的消息,通过 ZrangeByScore 操作,两秒一次轮询;

  • 进入直播间,获取用户的列表,通过 Zrange 操作来完成。


因此总的流程是:


  • 写消息流程是: 前端机 -> Kafka -> 处理机 -> Redis;

  • 读消息流程是: 前端 -> Redis。


不过这里有一个隐藏的并发问题:用户可能丢消息。



如上图所示,某个用户从第 6 号评论开始拉取,同时有两个用户在发表评论,分别是 10,11 号评论。


如果 11 号评论先写入,用户刚好把 6,7,8,9,11 号拉走,用户下次再拉取消息,就从 12 号开始拉取,结果是:用户没有看到 10 号消息。


为了解决这个问题,我们加上了两个机制:


  • 在前端机,同一个直播间的同一种消息类型,写入 Kafka 的同一个 partition;

  • 在处理机,同一个直播间的同一种消息类型,通过 synchronized 保证写入 Redis 的串行。


关于消息丢失的问题,总体原则: 消息按照消息号从小到大顺序写入,否则可能会丢消息。


写入消息的流程是:


1.发号器发号


2.写入 redis


如果有两个消息 A 和 B 同时做这个事情,我们期望的是:消息 A 得到 10 号,消息 A 写入 redis,消息 B 得到 11 号,消息 B 写入 redis。或者消息 B 得到 10 号,消息 B 写入 redis,消息 A 得到 11 号,消息 A 写入 redis。


但是如果出现:消息 A 得到 10 号,消息 B 得到 11 号,消息 B 写入 redis,消息 A 写入 redis。这样问题就来了,某个用户在消息 A 写入前把 11 号消息读走,那么他将再也不会读取到 10 号消息。


关于降级后,又回到可能丢消息的情况,这个其实是会的,但在延迟和丢消息之间,我们选择可能丢消息。相比一直允许可能丢消息,我们这种方式更好接受一些。


消息模型及并发问题解决后,开发就比较顺畅,系统很快就实现上线,达到了预先设定的目标。


上线后暴露问题的解决方案


上线后,随着消息量的逐渐增加,系统陆续暴露出三个比较严重的问题,我们一一进行了解决。


问题一:消息串行写入 Redis,如果某个直播间消息量很大,那么消息会堆积在 Kafka 中,消息延迟较大。


解决办法:


  • 消息写入流程:前端机-> Kafka -> 处理机 -> Redis;

  • 前端机:如果延迟小,则只写入一个 Kafka 的 partion;如果延迟大,则这个直播的这种消息类型写入 Kafka 的多个 partion;

  • 处理机:如果延迟小,加锁串行写入 Redis;如果延迟大,则取消锁。


因此有四种组合,四个档位,分别是:


1.一个 partion,加锁串行写入 Redis, 最大并发度:1;


2.多个 partition,加锁串行写入 Redis,最大并发度:Kafka partion 的个数;


3.一个 partion,不加锁并行写入 Redis,最大并发度:处理机的线程池个数;


4.多个 partion,不加锁并行写入 Redis,最大并发度: Kafka partition 个数处理机线程池的个数。


  • 延迟程度判断:前端机写入消息时,打上消息的统一时间戳,处理机拿到后,延迟时间 = 现在时间 - 时间戳;

  • 档位选择:自动选择档位,粒度:某个直播间的某个消息类型。


问题二:用户轮询最新消息,需要进行 Redis 的 ZrangByScore 操作,redis slave 的性能瓶颈较大。


解决办法:


  • 本地缓存,前端机每隔 1 秒左右拉取一次直播间的消息,用户到前端机轮询数据时,从本地缓存读取数据;

  • 消息的返回条数根据直播间的大小自动调整,小直播间返回允许时间跨度大一些的消息,大直播间则对时间跨度以及消息条数做更严格的限制。


解释:这里本地缓存与平常使用的本地缓存,有一个最大区别,就是考虑了成本问题。


如果所有直播间的消息都进行缓存,假设同时有 1000 个直播间,每个直播间 5 种消息类型,本地缓存每隔 1 秒拉取一次数据,40 台前端机,那么对 Redis 的访问 QPS 是 1000 * 5 * 40 = 20 万。


成本太高,因此我们只有大直播间才自动开启本地缓存,小直播间不开启。


问题三:弹幕数据也支持回放,直播结束后,这些数据存放于 Redis 中,在回放时,会与直播的数据竞争 Redis 的 CPU 资源。


解决办法:


  • 直播结束后,数据备份到 MySQL;

  • 增加一组回放的 Redis;

  • 前端机增加回放的 local cache。


解释:回放时,读取数据顺序是: local cache -> Redis -> mysql。localcache 与回放 Redis 都可以只存某个直播某种消息类型的部分数据,有效控制容量;local cache 与回放 Redis 使用 sortedset 数据结构,这样整个系统的数据结构都保持一致。

二、高可用保障

  • 同城双机房部署

  • 双机房分为主机房和从机房,写入都在主机房,读取则由两个从机房分担。从而有效保证单机房故障时,能快速恢复。

  • 丰富的降级手段

  • 全链路的业务监控


高可用保障建设完成后,迎来了 TFBOYS 在美拍的四场直播,这四场直播峰值同时在线人数达到近百万,共 2860 万人次观看,2980 万评论,26.23 亿次点赞,直播期间,系统稳定运行,成功抗住压力。

三、使用长连接替换短连接轮询方案

  • 长连接整体架构图



详细说明:


  • 客户端在使用长连接前,会调用路由服务,获取连接层 IP,路由层特性:a.可以按照百分比灰度;b.可以对 uid,deviceId,版本进行黑白名单设置。黑名单:不允许使用长连接;白名单:即使长连接关闭或者不在灰度范围内,也允许使用长连接。这两个特性保证了我们长短连接切换的顺利进行;

  • 客户端的特性:a.同时支持长连接和短连接,可根据路由服务的配置来决定;b.自动降级,如果长连接同时三次连接不上,自动降级为短连接;c.自动上报长连接性能数据;

  • 连接层只负责与客户端保持长连接,没有任何推送的业务逻辑。从而大大减少了重启的次数,从而保持用户连接的稳定;

  • 推送层存储用户与直播间的订阅关系,负责具体推送。整个连接层与推送层与直播间业务无关,不需要感知到业务的变化;

  • 长连接业务模块用于用户进入直播间的验证工作;

  • 服务端之间的通讯使用基础研发团队研发的 tardis 框架来进行服务的调用,该框架基于 gRPC,使用 etcd 做服务发现。

长连接消息模型

我们采用了订阅推送模型,下图为基本的介绍:



举例说明,用户 1 订阅了 A 直播,A 直播有新的消息:


  • 推送层查询订阅关系后,知道有用户 1 订阅了 A 直播,同时知道用户 1 在连接层 1 这个节点上,那么就会告知连接层有新的消息;

  • 连接层 1 收到告知消息后,会等待一小段时间(毫秒级),再拉取一次用户 1 的消息,然后推送给用户 1。


如果是大直播间(订阅用户多),那么推送层与连接层的告知/拉取模型,就会自动降级为广播模型。如下图所示:



我们经历了客户端三个版本的迭代,实现了两端(Android 与 iOS)长连接对短连接的替换,因为有灰度和黑白名单的支持,替换非常平稳,用户无感知。

四、总结与展望

回顾了系统的发展过程,达到了原定的前中期使用轮询,中后期使用长连接的预定目标,实践了原定的平衡演进的原则。


从发展来看,未来计划要做的事情有:


针对机房在北京,南方某些地区会存在连接时间长的情况。我们如何让长连接更靠近用户;


消息模型的进一步演进。


作者介绍:


王静波,毕业于西安交通大学,曾任职于网易和新浪微博,微博工作期间负责开放平台业务和技术体系建设。2015 年 9 月加入美图,就职于架构平台部,目前负责部分核心业务和基础设施的研发工作,包括弹幕服务、Feed 服务、任务调度和质量监控体系等。十余年的后端研发经历,拥有丰富的后端研发经验,对于构建高可用、高并发的系统有较多实践经验。欢迎通过 wjb@meitu.com 跟他交流。


本文转载自美图技术公众号。


原文链接:https://mp.weixin.qq.com/s/oO09mIGk5gunAsoRUcp2xg


2020-02-24 19:153766

评论 1 条评论

发布
用户头像
你好,消息写入前端机是用什么通信方式?
2024-03-22 16:17 · 广东
回复
没有更多了
发现更多内容

【Flutter 专题】26 易忽略的【小而巧】的技术点汇总 (四)

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 10月月更

“对比Excel”系列再添新成员,手把手教你用Python实现报表自动化!

博文视点Broadview

云算力挖矿分币系统软件开发内容(案例)

博睿数据智能监测终端全面升级,计算能力强大、用户体验真实性高

博睿数据

搞懂异地多活,看这篇就够了

Kaito

架构 高可用 后端 容灾 异地多活

云算力矿机租赁挖矿系统软件开发资料(案例)

使用myloader恢复数据教程

Simon

MySQL

【架构实战营】模块九作业

Abner S.

#架构实战营

矿机挖矿系统软件开发详情(快速上线)

云挖矿分币系统软件开发资料(源码)

鉴释人物丨专访解决方案负责人卜祥敏:直击业务痛点,赋能客户高效业务逻辑

鉴释

解决方案 业务逻辑 静态代码分析

Source Map在前端监控中的应用和实践

爱奇艺技术产品团队

大前端

Redis大集群扩容性能优化实践

vivo互联网技术

数据库 redis 性能优化 slots

你了解微服务的超时传递吗?

万俊峰Kevin

微服务 go-zero 超时 Go 语言 微服务调用链

区块链挖矿系统开发公司(现成源码)

APISIX 成为 Apache 项目两周年!

API7.ai 技术团队

开源社区 API网关 Apache APISIX

网络协议之:加密传输中的NPN和ALPN

程序那些事

网络协议 程序那些事 ALPN NPN

敏捷开发你必须知道的7件事

华为云开发者联盟

敏捷开发 软件开发 团队 Agile PM

IPFS矿机分币系统开发模板(现成)

带你上手全新版本的Webpack 5

华为云开发者联盟

JavaScript json 打包 webpack 模块

【LeetCode】 LRU 缓存机制Java题解

Albert

算法 LeetCode 10月月更

云原生时代的强强联合:EMQ 映云科技正式加入 AWS 合作伙伴计划

EMQ映云科技

AWS mqtt emq

算力挖矿系统开发内容(现成案例)

爱奇艺ZoomAI获CCF科学技术奖科技进步杰出奖,技术创新焕新老片,助力经典传承

爱奇艺技术产品团队

2021年9月云主机性能评测报告

博睿数据

现成区块链挖矿系统源码开发

python 头等对象之一,python 函数那些不一般的用法

梦想橡皮擦

10月月更

使用 Apache APISIX 进行集中式身份认证及进阶玩法

API7.ai 技术团队

开源 身份认证 API网关 Apache APISIX

云算力挖矿系统开发公司(源码案例)

现成矿机挖矿系统开发模板

你真的会使用数据库的索引吗?

华为云开发者联盟

索引 查询 聚集索引

百万用户同时在线的高并发直播弹幕系统是如何实现的?_行业深度_王静波_InfoQ精选文章