B站评论系统的多级存储架构

1.  背景

评论是 B站生态的重要组成部分,涵盖了 UP 主与用户的互动、平台内容的推荐与优化、社区文化建设以及用户情感满足。B站的评论区不仅是用户互动的核心场所,也是平台运营和用户粘性的关键因素之一,尤其是在与弹幕结合的情况下,成为平台的标志性特色。

在社会热点事件发生时,评论区的读写流量会急剧增加,直接影响业务运行,对用户体验、内容创作和社区文化等多个方面产生负面影响,所以评论服务的稳定性至关重要。

评论系统对缓存命中率要求非常高,一旦发生缓存失效,大量请求会直接访问 TiDB,如果 TiDB 出现问题,将导致评论服务不可用。所以评论需要构建一套可靠的容灾系统,并具备自动降级能力,以提升评论服务的整体稳定性。

图片

图片

2. 架构设计

评论系统架构主要依赖 Redis 缓存和 TiDB 存储,列表接口中依赖多种排序索引,如点赞序、时间序、热度序等,这些索引通过 Redis 的 Sorted Set 数据结构进行存储。

在大评论区中查询这些排序索引,当 Redis 缓存 miss 时需要回源 TiDB 查询,因数据量过大、查询耗时较长,慢查询会占用大量 CPU 和内存,进而导致其他查询的延迟或阻塞,严重影响整个 TiDB 的吞吐量和性能。例如查询点赞序排序索引:

SELECT id FROM reply WHERE ... ORDER BY like_count DESC LIMIT m,n

为了避免 TiDB 故障导致评论服务不可用,我们希望建立一套新的存储系统,解决 TiDB 单点故障问题。该系统不仅为业务提供容灾能力和自动降级通道,还能在大流量查询场景下提供更优的查询性能,从而提升整体评论服务的稳定性。

基于 B站自研的泰山 KV 存储(Taishan),我们搭建了「多级存储架构」,整体设计方案的核心思路包括:

  • 将排序索引的存储从「结构化」转为「非结构化」

  • 将排序索引查询从「SQL」转换为性能更高的「NoSQL」

  • 通过「写场景的复杂度」来换取「更优的读场景性能」

图片

3. 存储设计

 存储模型

在评论的业务场景中,我们抽象了两种数据模型:排序索引(Index)、评论物料(KV)

抽象数据模型

TiDB 模型

Taishan 模型

说明

Index

Secondary Index

Sorted Set

排序索引,例如点赞序、时间序的排序索引

KV

Primary Key & Row

Key-Value

包含元数据、内容等必要的评论物料

下图以按点赞序排序的前 10 条评论为例,展示了使用 Index + KV 模型实现的具体思路。在更复杂的推荐排序场景中,依然可以通过此模型来实现。具体流程是:首先通过排序索引召回一批评论 ID,再通过推荐算法对这些 ID 进行重排,最终根据重排后的 ID 获取评论详情并返回给用户。

图片

将领域对象的存储建模划分为 Index 和 KV 两种模型,可以利用不同的底层存储结构来分别优化查询、扫描、排序、分页等场景。使用 Redis 或 Memcache 作为缓存构建 KV 模型,提升查询性能,使用 Redis 的 Sorted Set 构建 Index 模型,支持增量数据实时更新排序索引,并提供极高效的分页查询性能。在关键词搜索场景,可采用 ElasticSearch 作为检索索引,避免在原始数据库上进行低效的遍历操作;

基于 KV 作为唯一事实表,采用同步全量数据和实时捕获增量数据的方法,将原始数据转换为下游索引表。这样可以灵活构建定制化的排序索引,以应对多变的评论业务需求。同时该方案在不影响原有业务逻辑和存储资源的情况下,实现了业务、代码和数据的解耦。

如果索引的定义和实现不再局限于源数据库的原生索引,而是扩展到应用逻辑,并在其他存储上自行维护物化视图,这必然会带来额外的理解和维护成本,同时引入一致性的难题。然而考虑到评论业务的数据量级和复杂度,该方案的整体优势仍然大于劣势。所以我们需要新的存储方案,既支持基本的 Index 和 KV 模型,又能满足高性能、可用性和扩展性等方面的需求。

数据类型

我们期望将数据类型从 SQL 转向 NoSQL,因为 NoSQL 提供了更灵活的数据模型,意味着更可解释的执行计划和更高的优化潜力。例如 Taishan 查询 Redis Sorted Set 的 P999 耗时约 10ms,查询 KV 的 P999 耗时约 5ms,这种高效的查询性能对评论业务尤为重要。

相比 TiDB,Taishan 不支持 ACID 事务、二级索引等功能,提供的能力更为精简。基于之前的经验和问题,有时候“less is more”反而能带来更高的可用性。以下是评论业务在使用 TiDB 时遇到的一些问题:

  1. MVCC 机制:TiDB 的事务实现基于 MVCC 机制,当新写入的数据覆盖旧数据时,旧数据不会被删除,而是以时间戳区分多个版本,并通过定期 GC 清理不再需要的数据。在热门评论区中,频繁更新点赞数时,排序索引的 MVCC 历史版本过多,导致 TiDB 的读写性能下降。相比之下,不支持 MVCC 的 Taishan 在查询排序索引时能提供更高效、更稳定的性能;

  2. 分片策略:TiDB 不直接支持完全自定义的分片策略,而 Taishan 支持哈希标签(hash tags),可以在 Key 中使用大括号 { } 指定参与哈希计算的部分。这样多个 Key 可以使用相同的 ID 进行分区路由,确保同一评论区的评论位于同一分片。在批量查询评论时,这能大幅降低扇出度,减少长尾耗时的影响。而对于可能出现热点的场景,Taishan 可以选择不使用哈希标签,从而打散请求,为性能要求高的评论业务提供更大的优化空间。

基于现有评论在 TiDB 中的存储结构和索引设计,以时间序和点赞序为例,列举 Taishan 的数据模型如下:

图片

4.  数据一致性

从 TiDB 的结构化数据转变为 Taishan 的非结构化数据,目前缺乏现成的同步工具,需要业务自行实现数据同步。然而数据同步过程中可能出现数据丢失、写入失败、写冲突、顺序错乱和同步延迟等问题,导致数据不一致。由于评论业务对数据一致性要求较高,我们需要一套可靠的数据同步方案,确保两者之间的数据一致性。

重试队列

针对写失败的问题,我们通过引入重试队列来解决,将写失败的请求放入队列中进行异步重试,确保数据不会因暂时性问题而永久丢失。引入重试队列可能会导致写并发产生数据竞争,进而引发数据最终不一致。虽然可以通过 CAS(Compare-and-Swap)来解决数据竞争问题,确保“读取-修改-写回”操作的原子性,但这也可能带来乱序问题。

图片

乱序问题

由于写数据有多个场景来源,包括 binlog 同步、重试队列,这些并发写操作导致数据错误,此外 MQ 消息因 rebalance 可能会被重新消费,导致消息回放,所以数据同步过程中不仅需要保证幂等性,还必须确保消息的顺序性:

例如并发写的场景,评论 A 被点赞两次,点赞数(like_count)为 2,TiDB 会生成两条 binlog 数据:

  1. 第一条数据 binlog_0 中,like_count = 1,由于网络原因写入失败,数据被转入重试队列进行异步处理。

  2. 第二条数据 binlog_1 中,like_count = 2,写入成功,评论 A 的 like_count 更新为 2,符合预期。

  3. 然而,重试队列继续处理 binlog_0,由于无法保证两个写操作的顺序,写入后 like_count 被更新为 1,导致数据不一致。

回退问题

在消息回放场景中,假设评论 A 被点赞三次,点赞数(like_count)为 3,TiDB 会生成三条 binlog 数据 [a, b, c]。正常情况下,这三条数据会被顺序消费并处理。如果在消费过程中发生 rebalance,导致消息回放,这三条数据会被重新消费,从而导致点赞数出现短暂的数据回退。

版本号

为避免乱序和回退问题导致的数据不一致,我们引入了版本号机制,每次评论数据变更时,版本号会递增。

UPDATE reply SET like_count=like_count+1, version=version+1 WHERE id = xxx

在 CAS 写操作时,将 binlog 数据中的 version 值与 Taishan 中数据的 version 值进行比对。如果 binlog 中的 version 值大于或等于当前数据的 version 值,则执行更新;否则认为该数据为过期数据,予以丢弃。

图片

 对账系统

根据 CAP 理论,在保证可用性(Availability)和分区容忍性(Partition)之后,分布式系统无法完全保证一致性(Consistency)。尽管引入了重试机制、CAS 和版本号机制,但由于网络调用的不可避免失败,评论数据之间难免会出现长期或短期的不一致状态。一旦发生不一致,需要有一套对账机制来及时发现并修复这些不一致的数据。

实时对账

通过 TiDB 的 Binlog 事件驱动,使用延迟队列延迟 n 秒后消费, binlog 数据关联查询 Taishan 数据,并对比两者的数据。对于发现的异常数据,进行通知并触发数据修复。

离线对账

利用 TiDB 和 Taishan 的数仓离线数据,进行 T+1 数据对比,验证数据的最终一致性。

图片

5.  降级策略

评论业务对可用性的要求非常高,尤其是在高并发、实时性强、用户互动频繁的场景下。通过搭建多级存储架构,我们能够在 TiDB 故障时自动降级到 Taishan,确保评论服务持续正常运行,我们的目标是实现每个请求的自动降级。

每次请求时,首先尝试从主存储获取数据。当主存储服务返回错误或长时间无响应时,降级到次要存储服务获取数据。在设计降级策略时,通常采用串行或并行方式,分别影响系统的响应时间和复杂性,而且整体耗时不能超过上游的超时限制,否则降级无效。

降级策略

优势

劣势

串行

简单

耗时长,容易整体超时

并行

耗时短

多1倍的请求,浪费资源

串行策略无法满足评论业务对响应时间的要求,而并行策略则可能浪费资源。所以我们选择了「对冲策略」(Hedging Policy)。在主节点请求超时后,我们会发起一个延迟x毫秒「备份请求」(backup request)到次节点。如果主节点返回成功,则直接返回结果,否则等待次节点的响应,优先选择主节点的结果。通过根据主次节点的耗时特性设置合理的延迟阈值,我们在整体响应时间和资源消耗之间达到了平衡。

图片

在实际生产环境中,我们根据具体业务场景设定 TiDB 和 Taishan 的主次关系。对于对数据实时性敏感、查询轻量的场景,设定 TiDB 为主存储,Taishan 为次存储;而对于数据实时性要求较低、大 SQL 查询的场景,则设定 Taishan 为主存储,TiDB 为次存储。

某天凌晨,TiDB 底层的 TiKV 节点宕机,在 TiKV 自愈期间,系统自动降级到 Taishan,评论业务未受影响,线上服务持续稳定运行。

图片

6.  总结与展望

B站评论服务对社区业务和用户体验至关重要。我们不仅致力于持续提升评论服务的稳定性,还不断优化评论业务,以便用户能够看到更优质的评论内容,从而增强归属感和认同感,提供更好的消费体验。

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

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

相关文章

若依分页插件失效问题

若依对数据二次处理导致查询total只有十条的问题处理办法_若依分页查询total-CSDN博客

css盒子水平垂直居中

目录 1采用flex弹性布局: 2子绝父相margin:负值: 3.子绝父相margin:auto: 4子绝父相transform: 5通过伪元素 6table布局 7grid弹性布局 文字 水平垂直居中链接:文字水平垂直居中-CSDN博客 以下为盒子…

Golang Gin系列-3:Gin Framework的项目结构

在Gin教程的第3篇,我们将讨论如何设置你的项目。这不仅仅是把文件扔得到处都是,而是要对所有东西的位置做出明智的选择。相信我,这些东西很重要。如果你做得对,你的项目会更容易处理。当你以后不再为了找东西或添加新功能而绞尽脑…

03JavaWeb——Ajax-Vue-Element(项目实战)

1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据,如下图所示的表格中的学生信息,应该来自于后台,那么我们的后台和前端是互不影响的2个程序,那么我们前端应该如何从后台获取数据呢?因为是2个程序&#xf…

【无法下载github文件】虚拟机下ubuntu无法拉取github文件

修改hosts来进行解决。 步骤一:打开hosts文件 sudo vim /etc/hosts步骤二:查询 github.com的ip地址 https://sites.ipaddress.com/github.com/#ipinfo将github.com的ip地址添加到hosts文件末尾,如下所示。 140.82.114.3 github.com步骤三…

【Idea启动项目报错NegativeArraySizeException】

项目场景: Idea启动项目报错(打包不报错),项目在服务器部署运行没有问题,尝试了重启idea、重启电脑、maven clean/install 都不行 maven-resources-production:sample: java.lang.NegativeArraySizeException: -5833…

【 MySQL 学习2】常用命令

文章目录 一、基础命令1.1、登录1.2 、退出1.3、查看数据库中有哪些数据库1.4 、选择使用的数据库1.5、创建数据库1.6 查看哪个数据库下有哪些表 二、SQL语句的分类2.1 DQL 数据查询语言2.2 DML 数据操作语言2.3 DDL 数据定义语言2.4 TCL 事物控制语言2.5 DCL 数据控制语言 三、…

JVM直击重点

JVM直击重点 JVM内存模型 JVM中类加载器分类与核心功能 Java里有如下几种类加载器 引导类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar、charsets.jar等 扩展类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的e…

图数据库 | 18、高可用分布式设计(中)

上文我们聊了在设计高性能、高可用图数据库的时候,从单实例、单节点出发,一般有3种架构演进选项:主备高可用,今天我们具体讲讲分布式共识,以及大规模水平分布式。 主备高可用、分布式共识、大规模水平分布式&#xff…

【常见BUG】Spring Boot 和 Springfox(Swagger)版本兼容问题

???欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老…

关于vite+vue3+ts项目中env.d.ts 文件详解

env.d.ts 文件是 Vite 项目中用于定义全局类型声明的 TypeScript 文件。它帮助开发者向 TypeScript提供全局的类型提示,特别是在使用一些特定于 Vite 的功能时(如 import.meta.env)。以下是详细讲解及代码示例 文章目录 **1. env.d.ts 文件的…

数字化时代,传统代理模式的变革之路

在数字化飞速发展的今天,线上线下融合(O2O)成了商业领域的大趋势。这股潮流,正猛烈冲击着传统代理模式,给它带来了新的改变。 咱们先看看线上线下融合现在啥情况。线上渠道那是越来越多,企业纷纷在电商平台…

接口测试自动化实战(超详细的)

🍅 点击文末小卡片,免费获取软件测试全套资料,资料在手,涨薪更快 前言 自从看到阿里云性能测试 PTS 接口测试开启免费公测,就想着跟大家分享交流一下如何实现高效的接口测试为出发点,本文包含了我在接口测…

意图颠覆电影行业的视频生成模型:Runway的Gen系列

大家好,这里是好评笔记,公主号:Goodnote,专栏文章私信限时Free。本文详细介绍Runway开发的视频生成模型Gen系列,包括Gen-1、Gen-2和Gen3 Alpha等,这些模型每次发布都震惊AI圈,荣获多个视频生成的…

ant design vue的级联选择器cascader的悬浮层样式怎么修改

平时想要修改组件内定样式会使用穿透deep和!important调优先级,但是在这里都不行,样式都不能改变 后来尝试出来是因为加了scoped,样式不起作用,但是不能直接去掉scoped,别的样式会受到影响,单独…

linux手动安装mysql5.7

一、下载mysql5.7 1、可以去官方网站下载mysql-5.7.24-linux-glibc2.12-x86_64.tar压缩包: https://downloads.mysql.com/archives/community/ 2、在线下载,使用wget命令,直接从官网下载到linux服务器上 wget https://downloads.mysql.co…

使用 ChatGPT 生成和改进你的论文

文章目录 零、前言一、操作引导二、 生成段落或文章片段三、重写段落四、扩展内容五、生成大纲内容六、提高清晰度和精准度七、解决特定的写作挑战八、感受 零、前言 我是虚竹哥,目标是带十万人玩转ChatGPT。 ChatGPT 是一个非常有用的工具,可以帮助你…

TinyEngine v2.1版本发布:全新的区块方案和画布通信方案,打造更强力的可拓展低代码引擎

前言 2025年蛇年已经到来,TinyEngine v2.1.0 版本也已经蛇气腾腾的发布了出来,新年新气象,为了让大家更详细了解到 v2.1.0 的内容更新,我们特此列举了该版本中的一些重要特性更新。 v2.1.0变更特性概览 1、使用了新的纯前端区块…

【机器学习实战入门】使用OpenCV和Keras的驾驶员疲劳检测系统

嗜睡驾驶者警报系统 防止司机疲劳驾驶警报系统 中级 Python 项目 - 司机疲劳检测系统 疲劳检测是一种安全技术,能够预防因司机在驾驶过程中入睡而造成的事故。 本中级 Python 项目的目标是建立一个疲劳检测系统,该系统将检测到一个人的眼睛闭合了一段时…

Ubuntu本地部署网站

目录 1.介绍 2.安装apache 3.网页升级 1.介绍 网站其实就相当于一个文件夹,用域名访问一个网页,就相当于访问了一台电脑的某一个文件夹,在网页中看见的视频,视频和音乐其实就是文件夹里面的文件。为什么网页看起来不像电脑文件夹…