哔哩哔哩直播通用榜单系统

榜单系统的定位和业务价值

榜单遍布B站直播相关业务的各个角落,直播打赏、直播间互动、付费玩法、互动玩法、活动、主播PK、语聊房、人气主播排名、高价值用户排名、增值集卡、up主充电等等,在这众多的业务场景中,我们能看到各种各样的榜单。

榜单的存在,可以激发主播提升表演水平、提高表演质量的积极性,从而吸引更多的观众。观众也可以通过榜单展现的排名,了解其他人对主播的互动打赏情况,激励他更加积极地参与互动或打赏,从而获得认同感和存在感。通过榜单,主播又能获得更高的收益和更多的曝光流量。总之,榜单是一道连接平台、主播、观众的重要桥梁,对提升整个直播的良好氛围有着极大的作用。另外,用户上榜的规则是多样化的,确保消费打赏行为不会过度商业化,在引导观众理性消费和平台健康发展方面也起着积极的作用。

图片

业务概览

下面是榜单系统的产品业务架构图,虽然忽略了很多细节,但从此图可以对B站直播榜单系统的全貌有个初步的认识。

图片

系统介绍

在文章开头已经介绍到了直播业务中榜单的种类是非常多的。

从榜单成员角色看,有主播榜、用户榜、道具榜、房间榜、厅榜、公会榜;

从竞争排名的范围看,有全站榜、分区榜、活动赛季榜、直播场次榜;

从结榜时间粒度看,可以分为长期榜、季度榜、月榜、周榜、日榜、小时榜;

从具体场景上看,一些业务还会对榜单进行“赛道”划分,如地区榜、男生女生榜,“赛道”的含义由业务方自定义、自理解。

图片

由此可见,榜单系统所承载的业务不仅仅是场景多,而且接入形态各异。所以从接入易用性和开发效率上讲,对榜单系统的通用性和灵活性有着较高的要求。接来下我们就从榜单配置化、业务接入、面临的挑战等几个方面介绍一下榜单系统。

榜单配置

经过这么长时间的发展,榜单几乎成了每次业务迭代的伴生需求。很多时候每上线一个新的业务需求,就会同时上线一个甚至多个新的榜单,榜单系统因此就成了日常业务需求迭代的“基础组件”。

那我们如何快速配置上线一个新榜单呢?做法就是打开管理后台,配一配。

基础配置

图片

榜单ID会在新榜单配置时由系统自动生成,生成后不再改变。后续通过接口进行榜单数据的读取、更新时,都需要指定榜单ID。榜单ID是榜单系统中的“硬通货”。     

榜单名称就是业务标识,方便辨识。

榜单系统使用Mysql作为持久化存储,并使用redis kv作为榜单成员分数的缓存,而排行榜功能的实现则是依赖Redis zset的能力。排行榜的缓存TTL、榜单成员分数的缓存TTL根据需求设定,要兼顾考虑业务流量压力和缓存集群的内存压力。排行榜长度限制也是必须设置的,也就是要限制zset的长度,在满足产品需求的同时要避免在Redis存储节点中产生大key,了解redis核心线程模型和事件模型的人应该都知道Redis中出现大key的危害。

研发侧还可以根据实际业务情况来指定要使用的Mysql集群和Redis集群分别是哪一套,从而做出合理的存储、流量隔离。毕竟有的榜单业务的读写QPS可能都不过百,而有的业务单榜单的日常读取QPS可以超过10万。不同业务之间的榜单数据量级也是有很大差别的,有的业务榜单数据量比较大,但生命周期不长,可以为其分配单独的存储,制定不同的定期清理归档策略。

积分开始时间、积分结束时间应该不需要多解释了,其实就是榜单在线上的有效时间范围,多用在自动化加分且为定时上线的榜单中。

score位数切分配置与上方的榜单排序方式是有关联的。在实际需求中,会有诸如“分数相同时先获得此分数的排在前面”、“分数相同时粉丝勋章等级高的排在前面”等要求的排序策略。我们知道,Redis zset使用双精度浮点数( a double 64-bit floating point number)来表示成员的排序权重分值,而double类型的最大有效位数为16位。所以我们可以指定n位整数部分表示真实的业务分数,(16-n)位小数部分辅助二级排序。在某一时刻(记时间戳为ts)调用通用接口进行分数更新时,根据配置计算最终分值的方式有三种:

  1. 按时间正序排序。取入参score作为整数部分,取(999999999-ts)且按位数截断的结果作为小数部分。

  2. 按时间倒序排序。取入参score作为整数部分,取ts且按位数截断的结果作为小数部分。

  3. 自定义排序。取入参score作为整数部分,取入参subscore作为小数部分,即整数部分、小数部分都由业务计算。

最后再将整数部分和小数部分进行字面拼接作为zset成员分值。如果有榜单成员的最终分值还是相同时,就遵循zset的内部实现,按照榜单成员key的二进制字典序进行排列了。

榜单维度配置

在榜单上线之前,还需要配置榜单的加分维度、分榜的时间粒度等。

图片

通用维度配置中的维度项,是基于直播业务中的常用维度进行预设,勾选了加分维度就意味着此榜单会横向进行分榜。可以举例说明:

  1. 若不勾选任何选项,则没有横向分榜的需求,即全站一张榜。比如主播人气榜,是全站所有的主播的人气排名,只需要一个榜单实例。

  2. 若勾选了主播,则表示针对每个主播会有一个榜单的实例。比如主播人气应援榜,是给某一特定主播助力人气值的所有用户的排名。

  3. 若勾选了主播、场次ID两个选项,则表示针对每个主播的每一个直播场次会有一个榜单的实例,比如主播进行的每次一次直播的打赏榜单,就可以这么实现。

  4. 拓展维度,是当预设的维度不能够满足逻辑需求时,留给业务方自定义加分维度时使用的,业务方自理解。进行榜单更新、榜单读取时,业务方自己使用统一的算法算出维度值。

若配置了时间维度,每个榜单实例会根据配置的时间粒度再进行纵向分榜,时间范围为自然时间,标识就是自然时间段的起始时间。比如选择了自然天,则每天的0点时会自动切换到新的一天的榜单。实现方式也不复杂,就是根据每次加分行为的时间戳,算出当前时刻归属于哪个自然时间段,就会知道往哪个分榜上加分。榜单的读取亦然,根据请求中指定的时间戳也能算出应该读取哪一个分榜。

可以想到,每次针对榜单系统的写入、读取,都会有一个根据时间戳进行格式化计算时间的操作,而时间的格式化计算又比较耗费性能。所以这里有个优化点,我们可以将自然时间的起止时间戳和它对应的格式化结果进行缓存,不必每次都进行一次计算,只有当时间超出了缓存的有效范围时才进行重新计算,在一些开源的高性能日志库中也有类似的做法。

自动化加分配置

顾名思义,自动化加分就是当有特定的事件发生时,配置了此自动化加分的榜单都会被触发更新。

图片

图片

自动化加分的由来

直播相关的业务代码中,大量使用了MQ订阅上游业务消息,若每一次业务接入都要单独注册一遍消费者、实现一遍消费逻辑代码,会做大量的相似性工作。所以为了提升开发效率,降低各个业务方接入MQ的成本,部门内实现了一个行为系统,所有的上游消息都由行为系统统一订阅消费,然后调用下游各业务方的RPC接口进行行为事件投递。当然这里引入了另一个不可忽视的问题,但在本文不作重点讨论。

图片

榜单服务作为行为系统的下游,当有行为事件发生时,榜单RPC接口被调用,行为事件会扇出到所有打开了对应开关的榜单上。这样业务方只需要在管理后台配置一个榜单,就可以在需要的地方直接展示榜单了,还是非常方便的。

当然,不是所有的业务都适合自动化加分的链路,它们需要在自己的业务层逻辑中计算好分数,再主动调用榜单系统的通用接口进行榜单更新。

具体的加分流程的逻辑后文再介绍。

过滤规则配置

在榜单系统的数据处理流程中,集成了通用化的过滤配置模块,具体配置页面见下图。

图片

过滤规则是随着业务发展扩展出来的,适用面相对窄一点。一级、二级分区过滤表示只有当行为事件发生在特定的直播分区才会进行加分,否则不加分。取反则是反向过滤。

若行为事件是用户购买礼物消息,还可以指定只有特定的商品才给此榜单加分,在一些活动中常用。

特殊榜单加分则是为特殊的业务逻辑hook,可以理解为在通用化链路中为特殊业务放置了一个回调的钩子,也是在活动中常用。也可以理解为在通用化链路中耦合了一些业务逻辑,现在已经不推荐常规业务使用了。

数据存储

结合基础设施情况及自身的业务特点,在数据存储选型上,榜单系统了分别选择了mysql和redis作为持久化存储系统和缓存系统。特别地,也依赖了redis的zset提供的能力来实现排行榜列表。榜单系统在存储结构设计上,是和榜单加分维度配置的设计紧密关联、相互对应的。

MYSQL存储

通用榜单系统的mysql存储表的结构也是统一的,最主要的一些字段如下:

CREATE TABLE `some_rank` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',`rank_id` int(11) NOT NULL DEFAULT '0' COMMENT '榜单ID',`type_id` varchar(100) NOT NULL DEFAULT '' COMMENT '子榜标识',`item_id` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '榜单成员标识',`score` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '榜单成员积分',// ......
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='xxx榜单数据表'

对于id、rank_id、score、rank、score这些字段,从名字及注释可以非常容易的理解其含义,就没必要多做解释了。我们把重点放在type_id、item_id这两个字段上。

type_id字段类型是verchar,其值的计算是和榜单“维度项”配置是对应的。代码实现中封装了固定逻辑,根据勾选的加分维度的字段name的字典序进行拼装组合,得出type_id字段的值。榜单的加分维度固定后,算法结果也是相对稳定的。

比如某个业务榜单的加分维度配置如下:

图片

那么在计算type_id时,就按照“${锚定的子榜起始时间点}_${主播uid}_${其他}”的字符串形式进行拼接。锚定的子榜起始时间点会根据加分或查询请求中的timestamp进行计算。

举例,某次加分Request请求体及字段注释如下:

{"rank_id": 12345, //榜单ID"item_id": 110000653, //榜单列表的一个成员ID,此请求传入的为用户的UID"score": 1980, //本次请求所增加的分数值"dimensions": { //加分维度必要的参数"ruid": 110000260, //主播的UID,也就是加分通用维度中“主播”选项对应的参数及value"timestamp": 1713165315, //加分行为的时间戳,用于锚定按时间切分的子榜// 其他可能需要的维度参数}// 其他必传参数,如必要的幂等key、业务标识等等
}

此加分行为携带的时间戳是1713165315,即2024-04-15 15:15:15,而时间维度配置为1个自然月,那么本次加分行为归属的自然月的起始时间就是2024-04-01 00:00:00,时间戳即1711900800,所以本次加分请求所计算的type_id就是“1711900800_110000260”,item_id字段所传的值即用户的uid。

那么,本次加分行为可以解读为:用户(uid:110000653)在主播(uid:110000260)本月的榜单上贡献了1980分,对应的数据行(主要字段)就如下所示。

图片

rank_id、type_id、item_id就是数据行的一个主键。

同理,假使此榜单配置的时间维度为1自然日,那么本次加分行为归属自然日的起始时间就是2024-04-15 00:00:00,时间戳即1713110400,那么本次加分请求所计算的type_id就是“1713110400_110000260”。

由此我们也可以看出,每一个单独的加分请求都要求传递一个行为发生的业务方时间戳,送礼加分行为携带的就是用户送礼时的实时时间戳,弹幕加分行为携带的就是用户发弹幕时的实时时间戳。这样,即使出现了偶发的网络抖动、MQ消息延迟等异常情况,也不会出现“昨天的送礼却给今天的子榜加了分”的事情发生,哪怕出现了消费游标重置、MQ rebalance等,当然这也需要幂等保证不会重复加分。

Redis缓存

Redis缓存榜单列表时,zset的key也由rank_id、type_id参与构成,这样加分、查询也都会根据时间戳锚定到正确的key。这里不再赘述。

榜单更新

介绍完了如何快速配置上线一个榜单,那么榜单配置完成之后,业务代码又是如何对榜单进行更新的。下面提供一张榜单加分的流程图作为说明。

图片

流程图展示了加分的大概步骤和简要的说明,这里再就几个关键的点需要注意。

自动化加分时,如果有多个榜单都打开了对应的自动化加分开关,那么一次行为触发对多个榜单进行更新。如果出现某个榜单更新失败,内部会自动针对这个榜单进行重试。

如果上游MQ重置了游标或触发rebalance,消息不会因为被重复消费而导致重复加分。

有一些榜单对用户的感知实时性要求较高,业务代码中会根据需要,依赖长连系统的能力将最新的榜单数据广播到端上,这样用户在不重新进房或者不主动刷新榜单的情况下也能看到最新的榜单数据。

业务挑战

本文开头就提到了榜单系统应用场景的广泛,也就意味着榜单系统的读写流量是非常高的。对一些榜单,观众能及时看到上榜或排名的变化是很有必要的,特别是消费打赏行为。榜单在平台运营中也扮演着重要的角色,比如对主播、用户的激励发放、活动结果的奖励结算,有不少是依赖其在榜单上的排名作为依据。

因此榜单数据的准确性、系统的健壮性、接口的高效性也是重要的健康指标。

写入性能

就目前来讲,榜单写入出现的性能瓶颈,多是来自自动化加分链路中用户互动行为事件,比如持续观播、点赞、发弹幕,这类行为一般是持续、海量的,且相对于用户的消费打赏行为来说对体验不敏感。

还有第二种情况就是当有大型赛事或活动时,就会出现单点热门直播间,大量的写入会造成存储分片(redis、mysql)的吞吐压力,可能会出现写入超时导致数据积压或者最终丢失,部分场景可能会出现重试雪崩。

针对第一种情况,榜单系统引入了一层”慢队列“。将互动行为事件的加分请求进行预处理后,不直接更新至存储,而是投递到慢队列中。慢队列MQ接入公司的异步事件处理平台,利用平台聚合能力,将相同写入维度的数据聚合之后再写入存储,大大降低存储压力。

图片

* 图片来自异步事件处理平台说明文档

对于第二种情况,我们多是采用降级策略,这类直播间很多时候并不需要展示不必要的榜单,可以针对整个榜单进行降级,不写入不展示即可。也可以针对某些事件行为进行降级,比如当命中赛事直播间时,互动行为将降级为不加分。

读取性能

榜单的读取流量基本都打到redis集群,通过redis集群是可以扛住日常压力的。较多出现读取性能瓶颈的依然是一些热门直播间,日常大v直播、大型赛事直播、大型晚会或活动直播等,这种情况下往往是对redis集群中的单个node压力过大。对于高热直播间,我们通过增加二级内存缓存的方式进行了解决。

  • 接入CDN热门房间SDK。这是公司内的一个公共组件,通过这个组件提供的配置化能力,我们可以知道当前直播间是否判定为热门直播间。如果达到了热门直播间的阈值,系统会在内存中将榜单进行缓存,降低对redis集群中单个存储node压力。

  • 开发了热点探测组件。通过对榜单zset的访问情况进行记录,将热点zset key进行内存级缓存。

  • 配置化强制热门房间。必要时手工指定某些直播间为热门直播间,强制进行内存缓存。

加入二级缓存肯定会带来一定实时性的牺牲,但都会在业务可接受的范围内进行。

现状下的架构

这里引入一个内部的简化版架构示意图,仅供参考。

图片

后续规划

代码质量的进一步提升

任何系统的设计,都脱离不了实际的适用场景,需要在各方面进行折衷取舍。现状下的榜单系统其实就是“边跑边换轮子”的产物,比如还有很多业务逻辑还耦合在通用链路中。之前有过一些分层设计理念,也试图引入更合理的设计模型来增强可扩展性、可维护性,但都并没有完全的落地,需要继续推进。

日志治理

目前榜单系统的日志量非常大,最多时每天将近20T。其中不乏噪音日志、含义重复、大结构输出等不合理的做法,需要进行梳理治理。

存储治理

  1. 老旧数据、过期数据较多,有很多不再使用的榜单遗留的配置、存储表、无TTL缓存等,且mysql数据没有自动过期的能力,可以建设自动化告警、定制化归档的能力。

  2. 虽有集群隔离,但有的业务榜单在代码中通过hard code交叉使用多个集群,所谓的“核心”、“非核心”数据隔离存储的优势也丧失,总体感觉使用有些混乱。

  3. 缺少快捷的可视化、业务体感能力,想要查看榜单系统目前存储集群的使用情况,需要分别翻看多个mysql、redis集群的多个平台的监控,如容量监控、访问流量监控等等,然后再汇总评估。

榜单系统新架构

基于目前榜单系统的一些痛点

  • 更合理的架构分层、领域间更清晰的边界划分;榜单内核加业务层支撑,可扩展、可测试的自洽闭环。

  • 加分行为处理的实时性、数据一致性的优化提升。

  • 更加科学的存储选型。

-End-

作者丨芳苇

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/837324.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

腐烂的橘子BFS

题目: 腐烂的橘子 在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一: 值 0 代表空单元格; 值 1 代表新鲜橘子; 值 2 代表腐烂的橘子。 每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子…

使用VSCode撰写Latex文档

参考资料: 如何使用VSCode编写Latex? 概要 先安装texlive,然后安装VSCode. 我这里步骤是全的,但说的不那么细。 只介绍VSCode中的配置方法。 VSCode配置步骤 1. 安装LaTex Workshop插件 2. 配置Latex编译环境 将下列配置粘入settings.j…

TNNLS:Fast Self-Supervised Clustering With Anchor Graph论文阅读

1 Abstract 由于避免了使用通常在现实世界中不足的标记样本,无监督学习被视为在聚类任务中的快速和强大策略。然而,直接从原始数据集进行聚类会导致高计算成本,这限制了其在大规模和高维问题上的应用。最近,基于锚点的理论被提出…

基于uniapp+vue3+ts小程序项目实战之项目初始化

🚀 作者 :“二当家-小D” 🚀 博主简介:⭐前荔枝FM架构师、阿里资深工程师||曾任职于阿里巴巴担任多个项目负责人,8年开发架构经验,精通java,擅长分布式高并发架构,自动化压力测试,微服务容器化k…

长难句打卡5.14

This is now a question for Gloria Mackenzie, an 84-year-old widow who recently emerged from her small, tin-roofed house in Florida to collect the biggest undivided lottery jackpot in history. 翻译:这是84岁的孤寡老人歌莉娅 麦肯齐当前所面临的问题…

Linux系统搭建Gitlab开源仓库管理系统并实现公网环境访问本地私有库

文章目录 前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名 7. 测试访问二级子域名 前言 GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具&#xf…

geotrust ov泛域名证书2990

Geotrust是一家正规的CA证书颁发机构,致力于为个人以及企事业单位开发者提供安全可靠的数字证书产品,维护了个人博客网站、企业官网、商城网站以及银行等金融网站的数据安全,营造了一种健康的网络环境。今天就随SSL盾小编了解Geotrust旗下的O…

OSU micro-benchmarks安装测试指导

OSU micro-benchmarks安装测试指导 OSU micro-benchmarks工具介绍 OSU Micro benchmark工具是由Ohio State University提供的MPI(Message Passing Interface,消息传递接口)通信效率评测工具。该工具旨在通过执行不同模式的MPI操作&#xff…

linux fdisk 银河麒麟操作系统 v10 磁盘分区和挂载 详细教程

1查看 未加载的磁盘 fdisk -l 2 开始分区 fdisk /dev/vdb #查看分区 #新建分区和保存 3 格式化和挂载 fdisk -l mkfs.xfs /dev/vdb1 #查看uuid blkid /dev/vdb1 mkdir /data vi /etc/fstab UUID209daa-fb1c-48f2-bf5e-e63f38cb8a /data xfs defaults 0 0 #加载下 mo…

【bug记录】Vue3 Vant UI 中 van-popup 不弹出

原因:语法使用错误,使用了 Vue 2 的语法 Vue3语法: Vue2语法:

【JavaEE 初阶(六)】网络编程

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你了解更多网络知识 目录 1.前言2.浅谈网络2.1基本知识2.2.OSI与TCP/IP 3.网络编程3.1TCP与UDP区别3.2UDP网路编程…

四川易点慧电商抖音小店:优势尽显,引领电商新潮流

在当下这个信息爆炸、消费模式日新月异的时代,电商行业正在经历一场前所未有的变革。四川易点慧电商抖音小店凭借其独特的优势,成功吸引了大量消费者的目光,成为电商领域的一股新势力。 四川易点慧电商抖音小店的最大优势在于其强大的品牌影…

Vue3实战笔记(19)—封装菜单组件

文章目录 前言一、封装左侧菜单导航组件二、使用步骤三、小彩蛋总结 前言 在Vue 3中封装一个左侧导航菜单组件是一项提升项目结构清晰度和代码可复用性的关键任务。这个过程不仅涉及组件的设计与实现,还需考虑其灵活性、易用性以及与Vue 3新特性的紧密结合。以下是…

如何恢复删除的文件?收好6个恢复策略!

“我经常在操作电脑时可能会有误删文件的情况发生,如果我不小心删除了重要的文件,应该使用什么方法来恢复它们呢?求解答!” 在使用电脑时,我们可能一个手滑就误删了重要的文件。当文件删除后,如果没有掌握相…

win10安装mysql8.0+汉化

一、官网安装 MySQL 1. 在mysql官网进行下载页面 2. 下滑页面,选择 MySQL community download 3.下载windows版本 4.选择第二个download 5.不用登陆,no thanks,just start my download. 6.下载 二、安装 1. 双击安装 2. 选 Full->next 3…

depcheck检查项目中未被使用的依赖

depcheck是一个用于分析项目中依赖项的工具,可以查看:每个依赖项是如何使用的,哪些依赖项是无用的,以及哪些依赖项在package.json 1、安装 npm install -g depcheck # 必须全局安装2、可配置文件.depcheckrc(不配置 直…

开源模型应用落地-CodeQwen模型小试-集成langchain(四)

一、前言 通过学习代码专家模型,开发人员可以获得高效、准确和个性化的代码支持。这不仅可以提高工作效率,还可以在不同的技术环境中简化软件开发工作流程。代码专家模型的引入将为开发人员带来更多的机会去关注创造性的编程任务,从而推动软件…

【轮转数组】力扣python

1.python切片 这里nums[:]代表列表 class Solution:def rotate(self, nums: List[int], k: int) -> None:nlen(nums)nums[:]nums[-k%n:]nums[:-k%n] 2.边pop边push 0代表插入的位置 class Solution:def rotate(self, nums: List[int], k: int) -> None:nlen(nums)fo…

花趣短视频源码淘宝客系统全开源版带直播带货带自营商城流量主小游戏功能介绍

1、首页仿抖音短视频 ,关注 ,我的 本地 直播 可发布短视频 可录制上传 2、商城页面 广告位、淘口令识别、微信登录、淘宝登录、淘宝返佣、拼多多返佣、京东返佣、唯品会返佣、热销榜、聚划算、天猫超市、9.9包邮、品牌特卖、新人攻略 、小米有品、优惠加…

不知摄像机网段IP地址?别担心,这里有解决之道

在数字化、智能化的今天,摄像机作为安全监控和日常记录的重要工具,其应用越来越广泛。然而,在实际使用中,我们可能会遇到一些问题,比如忘记了摄像机的网段IP地址,这往往会让我们感到头疼。那么,…