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

devtools/2025/1/21 12:59:19/

以下文章来源于哔哩哔哩技术 ,作者业务

哔哩哔哩技术.

提供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.ppmy.cn/devtools/152353.html

相关文章

Unity2021.3.13崩溃的一种情况

如果出现如下的报错,可能是软件冲突的原因。自己的原因是使用f.lux这款软件似乎和Unity相互冲突,出现下面报错。 错误信息如上图

单例及线程池的实现及感悟分享

碎碎念: 有快3个月没有写博客了,这段时间很多事,抽不出空来写博客。后面的博客可能风格也会转变,从理论到实战为主,尽量减少源码的解读。(看完源码后面自己也忘了QAQ),本篇博客主要介绍线程池的…

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

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

MIAOYUN信创云原生项目亮相西部“中试”生态对接活动

近日,以“构建‘中试’生态,赋能科技成果转化”为主题的“科创天府智汇蓉城”西部“中试”生态对接活动在成都高新区菁蓉汇隆重开幕。活动分为成果展览、“中试”生态主场以及成果路演洽谈对接三大板块。在成果展览环节,成都元来云志科技有限…

OpenHarmony API 设计规范

OpenHarmony API 设计规范 修订记录 版本作者时间更新内容v0.1,试运行版OpenHarmony API SIG2022年11月初版发布 目的 API是软件实现者提供给使用者在编程界面上的定义,API在很大程度上体现了软件实体的能力范围。 同时,API定义的好坏极…

《鸿蒙 HarmonyOS 应用开发从入门到精通(第 2 版)》学习笔记 ——HarmonyOS 环境搭建之安装DevEco Studio

作为一款开发工具,除了具有基本的代码开发、编译构建及调测等功能外,DevEco Studio还具有如下特点: 高效智能代码编辑:支持Java、XML、ArkTS、JS、C/C等语言的代码高亮、代码智能补齐、代码错误检查、代码自动跳转、代码格式化、…

[Unity]【游戏开发】 脚本创建物体的实践与技巧

在Unity游戏开发中,动态创建物体是一个常见的需求。为了提高开发效率并实现灵活的物体生成,开发者通常会利用预制体来作为物体的模板,然后通过脚本在运行时动态创建物体。本文将详细讲解如何通过脚本创建物体,并涵盖一些常见的技巧和方法。 预制体与实例化 预制体简介 预…

深入理解机器学习中的零样本、少样本与微调

在机器学习领域,特别是在大语言模型(LLM)的评估中,我们经常听到zero-shot(零样本)、few-shot(少样本)和fine-tuning(微调)这些术语。这篇文章将通过具体示例来…