直播弹幕是直播系统的核心功能之一。如何快速打造一个可扩展性好的弹幕系统?如何应对业务的快速发展?相信很多工程师/建筑师都有自己的想法。本文作者,美拍架构师王静波,经历了直播弹幕系统从无到有,从小到大的过程,总结了弹幕系统搭建的经验。直播弹幕是指直播间内的用户、礼物、评论、点赞等消息,是直播间重要的互动手段。从2015年11月至今,美拍直播弹幕系统经历了三个阶段的演进,目前支持百万用户同时在线。这篇文章更好的解释了直播弹幕系统根据项目的发展阶段均衡进化的过程。这三个阶段分别是快速上线、高可用保障体系建设、长连接演进。快速启动消息模型美拍直播弹幕系统在设计初期的核心需求是:快速启动,能够支持百万用户同时在线。基于这两点,我们的策略是在前中期使用HTTP轮询方案,在中后期换成长连接方案。因此,业务团队在开发HTTP方案的同时,基础研发团队也在紧锣密鼓地开发长连接系统。与IM场景相比,直播间消息有以下特点:消息必须及时,过时的消息对用户来说不重要。松散群聊,用户可以随时加入和离开群。用户加入群组后,离线期间的消息(接听电话)无需重发。对于用户来说,在直播间有3个典型的操作:进入直播间,拉取正在观看直播的用户列表。接收直播间不断发布的弹幕信息。给自己留言。我们将礼物、评论和用户数据视为新闻。经过考虑,选择了Redis的sortedset来存储消息。消息模型如下:用户通过Zadd发送一条消息,其中score为消息的相对时间。接收直播间消息,使用ZrangeByScore操作每两秒轮询一次。进入直播间,获取用户列表,通过Zrange操作完成。因此,整体流程为:消息写入流程为:前端机->Kafka->处理器->Redis。读取消息的流程是:前端->Redis。但是这里有一个隐藏的并发问题:用户可能会丢失消息。如上图,一个用户从6号评论开始拉取,同时有两个用户在发表评论,分别是10号和11号评论。如果第11条评论先写了,用户就拉走了第6、7、8、9、11条,下次用户拉消息时,从第12条开始拉,结果是:用户没有看到第10条消息。为了解决这个问题,我们增加了两个机制:在前端机器上,同一个直播间的同类型消息写入Kafka的同一个分区。在处理器上,通过synchronized保证同一个广播房间的同类型消息串行写入Redis。消息模型和并发问题解决后,开发会比较顺利,很快系统就会上线,达到预定的目标。解决上线后暴露的问题上线后,随着消息量的逐渐增加,系统陆续暴露出三个严重问题,我们一一解决。问题1:消息串行写入Redis。如果直播间消息量大,消息会堆积在Kafka中,消息延迟会很大。解决方案:消息写入流程:前端机->Kafka->处理器->Redis。前端机:如果延迟小,只写一个Kafkapartion;如果延迟很大,这种直播消息会写入多个Kafkapartition。处理器:如果延迟小,将锁串行写入Redis;如果延迟较大,则取消锁定。所以有四种组合,四个档位,分别是:一个分区,加锁串行写入Redis,***并发度:1、多分区,加锁串行写入Redis,最大并发数:Kafka分区数。一个partion在不加锁的情况下并行写入Redis,最大并发为:处理器的线程池数。多分区,无锁并行写入Redis,最大并发数:Kafka分区数,处理器线程池数。判断延迟程度:前端机写消息时,在消息上打上统一的时间戳。处理器收到后,延迟时间=当前时间-时间戳。档位选择:自动档位选择,粒度:某个直播间的某个消息类型。问题2:用户轮询***消息,需要进行Redis的ZrangByScore操作,redisslave的性能瓶颈比较大。解决方案:本地缓存,前端机每隔1秒左右拉取直播间的消息,当用户从前端机轮询数据时,从本地缓存中读取数据。回帖条数根据直播间大小自动调整。小型直播间允许回传时间跨度较大的消息,而大型直播间对时间跨度和消息条数的限制更为严格。解释:这里的本地缓存和通常的本地缓存有很大区别,就是考虑成本问题。如果缓存所有直播间的消息,假设同时有1000个直播间,每个直播间有5种消息,本地缓存每1秒拉取一次数据,前端机器有40台,则访问Redis的QPS为1000*5*40=200,000。成本太高,所以我们只在大型直播间自动开启本地缓存,小型直播间不开启。问题三:弹幕数据也支持播放。直播结束后,数据存储在Redis中。播放时会和直播数据竞争Redis的CPU资源。解决方法:直播结束后,将数据备份到MySQL。添加一组Redis用于播放。前端机增加本地缓存用于播放。说明:播放时,读取数据的顺序为:本地缓存->Redis->mysql。localcache和replayRedis都可以只存储某类直播的部分数据,有效控制容量;localcache和replayRedis使用sortedset数据结构,使得整个系统的数据结构是一致的。高可用保障同城双机房部署,分为主机房和从机房。写入在主机房完成,读取由两个从机房共享。这样可以有效保证当单个机房出现故障时,能够快速恢复。丰富的降级方式,业务全链路监控,高可用保障搭建完成后,TFBOYS在美拍迎来四场直播。这四场直播的峰值人数同时达到近百万,累计观看人数2860万,评论2980万,点赞26.23亿,直播期间系统稳定运行,成功顶住压力。使用长连接替代短连接轮询方案长连接整体结构详解:在使用长连接之前,客户端会调用路由服务获取连接层的IP。路由层的特点:a.可按百分比灰度化;为uid、deviceId、version设置黑名单和白名单。黑名单:不允许持久连接;白名单:允许持久连接,即使关闭或不在灰度范围内。这两个特性保证了我们长短连接切换的顺利进行。客户端的特点:A.同时支持长连接和短连接,可以根据路由服务的配置来决定;b.自动降级,长连接3次同时连接失败,自动降级为短连接;C。自动上报长连接性能数据。连接层只负责维护与客户端的长连接,没有任何推送业务逻辑。从而大大减少重启次数,从而保持用户连接的稳定性。推送层存储用户与直播间的订阅关系,负责具体的推送。整个连接层和推送层与直播间的业务无关,不需要感知业务的变化。长连接业务模块用于用户进入直播间的验证。服务器之间的通信使用基础研发团队开发的tardis框架来调用服务。该框架基于gRPC并使用etcd进行服务发现。对于长连接消息模型,我们采用订阅推送模型。下图是基本介绍:比如用户1订阅了A直播,A直播有新消息:推送层查询订阅关系后,知道用户1订阅了A直播,知道即用户1在连接层1的节点上,则通知连接层有新消息。连接层1收到通知消息后,会等待一小段时间(毫秒),然后重新拉取用户1的消息,再推送给用户1。如果是大型直播间(有很多订阅者),推送层和连接层的通知/拉模型会自动降级为广播模型。如下图所示:我们经历了客户端三个版本的迭代,实现了两端(Android和iOS)长连接对短连接的替代。因为支持灰度和黑白名单,替换非常流畅,用户没有感知。总结与展望回顾系统的发展历程,实现最初的目标,前中期使用polling,中后期使用long-termconnections,践行最初的均衡进化原则。从发展的角度来看,未来计划的事情是:北京的机房,南方部分地区连接时间会比较长。我们如何让长连接更贴近用户。消息模型的进一步演化。王静波,毕业于西安交通大学,曾就职于网易、新浪微博。微博工作期间,负责开放平台业务和技术体系建设。2015年9月加入美图,就职于架构平台部。目前负责部分核心业务和基础设施的研发,包括弹幕服务、Feed服务、任务调度、质量监控系统等。十余年后端研发经验,丰富的后端研发经验,更有搭建高可用、高并发系统的实战经验。欢迎通过wjb@meitu.com与他交流。
