Skip to main content

23. BiliBili 标签系统建设实践

01 背景

标签系统是一种用于组织和分类信息的技术,它广泛应用于内容管理、搜索引擎优化(SEO)、推荐系统和用户行为分析等多个领域。随着数据科学的发展,标签系统可以更有效地从大量数据中提取有用信息,实现智能分类和推荐。AI和ML技术的应用使得标签系统能够自学习和适应,提高标签生成的准确性和个性化推荐的效果。云计算提供了弹性的计算资源,大数据技术则为处理和分析海量数据提供了可能。

一个好的标签系统,可以帮助公司更高效地管理和检索内部和外部信息,提高工作效率;通过提供个性化的标签推荐,公司可以增强用户满意度和忠诚度,同时可以更好地分析用户行为和市场趋势,支持数据驱动的决策制定。B站标签系统在2021年立项时的初衷是解决业务侧频繁的adhoc查询问题,提高效率并节约资源。然而,随着时间的推移,系统在2022年中旬遇到了一些挑战,这些问题促使公司重新审视并启动了标签系统的建设。

1.1 遇到的问题

  • 性能瓶颈
    • 随着查询量和频率的增加,现有的标签系统计算方式已经达到了性能瓶颈。
  • 缺乏规范
    • 没有统一的标签体系建设规范,导致数据源接入和标签配置需要平台人员手动操作,效率低下。
  • 重复建设
    • 由于数据来源和业务的多样性,业务侧建设了多个定制化的类标签系统,这些系统虽然满足了特定需求,但缺乏通用性,造成了资源的重复投入。
  • 对接不规范
    • 下游业务应用对接缺乏规范和系统层面的管理,导致效果回收链路没有建设,影响了整体的业务流程和数据流转效率。

1.2 建设目标

  • 1.加速和巩固数据链路
    • 数据源接入和标签建设:开放给全站业务,允许自助操作,同时平台需明确规范标准并建立审批流程。
    • 多源存算引擎:引入以提升数据链路的稳定性和性能。
  • 2.打造全站通用的标签系统
    • 对外连通:与数据平台和各业务应用平台实现连通,确保数据和应用的无缝对接。
    • 对内整合:整合其他数据应用产品,构建一个全站统一的标签系统。

02 02 架构设计

2.1 实施策略

为实现标签系统的系统化建设,提高整个数据链路的效率和稳定性,我们从以下6个方面实施标签系统的重构:

技术升级:对现有标签系统进行技术升级,引入更高效的计算方法和存储解决方案。 建立标准:制定明确的标签建设和数据接入标准,简化操作流程,提高自动化水平。 平台化管理:建立统一的平台化管理机制,实现标签的集中管理和监控。 用户参与:鼓励业务侧用户参与标签系统的建设,提供反馈,以用户需求为导向优化系统。 跨部门协作:加强不同部门之间的协作,共同推动标签系统的建设和优化。 效果评估:建立效果评估机制,定期检查标签系统的性能和业务效果,确保持续改进。

2.2 整体架构

系统整体架构从下到上可以分为三个层级,依次为标签生产、人群圈选、人群应用

03 核心链路和使用场景

3.1 标签生产

标签生产作为标签系统最下面一层是整个系统的基础,需遵循一系列有序的构建阶段,以确保系统的稳定性、准确性和对业务需求的适应性。这些阶段包括标签的定义、预构建和预生产

  • 标签定义
    • 构建过程的起点,其核心在于确立标签的业务意义和使用场景。在此阶段,技术团队与业务利益相关者紧密合作,通过需求分析来明确标签的预期用途和业务目标的一致性。
    • 进行属性设计,定义每个标签的名称、描述、分类和适用对象等关键信息。此外,制定标签生成的逻辑规则,确定基于哪些数据源和条件来生成标签,为后续的实现打下基础。
  • 构建阶段
    • 目的在于验证标签生成逻辑的正确性。在此阶段,开发团队将基于定义阶段的规则,开发标签生成的原型。这不仅验证了逻辑的准确性,还对性能进行了初步评估,确保标签生成逻辑在处理大规模数据时的可行性和效率。
  • 生产阶段
    • 是标签的线上部署阶段,会对标签的更新周期、生产调度、质量监控等进行配置,确保系统在各种条件下都能稳定运行。

离线标签生产过程如下图所示:

  • 数据源接入标签系统后,系统会在大数据平台自动创建对应的 Spark 离线计算任务,并每天零点触发任务进行标签构建。
  • 数据源接入和标签生产过程中系统会记录标签和数据源表字段绑定的元信息以及标签构建结果元信息,是下一个环节人群圈选的必要输入

3.2 人群圈选

核心思路:提供多样化创建方式以满足不同业务需求,各方式技术实现要点如下

创建方式技术实现要点
规则和行为创建人群开发灵活标签引擎 允许用户根据标签组合来定义人群。集成用户行为分析工具。对需要实时反馈的场景 集成流处理技术应对实时反馈场景
导入 Excel 创建人群提供稳定高效数据导入接口(支持大文件);导入中实施数据验证 确保数据质量和一致性
根据 HTTP 链接创建人群开发服务解析外部链接数据并导入;实现数据同步机制
基于 Hive 表创建人群与 Hive 数据库紧密集成;允许用户编写 SQL 查询定义人群
同步 DMP 人群包与数据管理平台(DMP)集成 实现人群包的同步。确保DMP中的人群信息正确映射本系统的人群定义中

3.3 人群应用

数据应用产品集成使用

标签系统与北极星埋点管理分析平台和AB实验平台做了深入的产品集成打通,可以形成一套完整的数据驱动解决方案,使业务能够在一个统一的平台上管理数据收集、分析和实验测试。这种整合可以减少数据孤岛,提高数据分析的效率和准确性。使用场景包括:

  • 人群圈选: 在AB实验中,企业可能需要根据特定的行为数据来选择目标受众,这可以通过北极星埋点管理分析平台来实现。
  • 画像分析: 实验组的用户数据可以进一步用于生成用户画像,这有助于更细致地了解用户特征和需求。
  • 行为分析: 对于参与实验的用户,可以进一步分析其行为模式,以评估实验效果或进行更深入的用户研究。

业务场景直接应用

当前系统在业务上主要有对接公司的推送平台、用增平台以及活动中台和任务平台,在分析上除了自身提供的画像分析能力之外,还有对接各类指标下钻分析系统

  • 在推送触达场景中下游系统获取到人群数据明细后会进行批量推送,例如推送站内私信,推送特定业务卡片等.
  • 在业务运营场景中则更多的是使用标签系统在线服务判定能力

04 核心方案

4.1 标签构建优化

4.1.1 引入 Iceberg 支持多存算引擎

在初版标签系统中,存在标签不区分类型的设计缺陷 -> 所有数据明细直接灌入 Clickhouse,离线标签需通过物化视图生成 RoaringBitMap.

这种架构在实际运行中暴露严重问题

当业务需要获取人群数据明细时,极易产生大查询请求,不仅会占用 Clickhouse 大量计算资源,影响整体查询稳定性,极端情况下还会导致节点因 内存溢出 OOM 宕机,无法满足业务对数据处理的可靠性需求。

tip

Apache Iceberg 的技术特性与适配价值

Apache Iceberg 作为专为大规模分析数据集设计的开源表格式,具备两大核心优势:

  • 多计算引擎兼容: 原生支持 Apache Spark、Presto 等主流计算引擎,可无缝适配业务侧已有的技术栈,无需重构底层计算架构。
  • 高效数据处理与生态集成: 能够高效读写 PB 级大型数据集,且可与 Hadoop、对象存储等数据湖生态系统无缝对接,保障数据流转的顺畅性。

通过引入 Iceberg,标签系统实现了存储架构的优化重构

  • 数据存储分层
    • 标签的明细数据和连续标签直接存储于 Iceberg,仅将离散标签的 Bitmap 数据存入 Clickhouse.
  • 核心收益
    • a. 大幅降低 Clickhouse 的计算与存储压力,提升查询响应速度与系统稳定性;
    • b. 借助 Iceberg 的 数据不出仓 特性,避免数据额外出仓带来的冗余存储成本,显著节约资源开支;
    • c. 业务可根据需求灵活选择计算引擎,提升技术选型的灵活性与扩展性。

4.1.2 自定义分 shard 模式读写 Clickhouse

为进一步解决 Clickhouse 单节点负载过高的问题,标签系统设计了自定义分 shard 模式
通过分布式数据处理策略,实现标签数据与人群数据的高效读写。核心目标是 降低单节点数据查询量,避免单点过载,并通过并行处理提升数据读写效率

1. 分 shard 模式的核心优势

  • 负载均衡:将数据与查询请求均匀分配到多个 Clickhouse 节点(shard),彻底解决单点负载过高导致的系统不稳定问题;
  • 并行处理:支持配置并发数 m,可同时启动多个 Spark 任务 并行处理数据,大幅提升数据写入与查询的速度;
  • 查询优化:查询时可直接定位到目标 shard,减少跨节点数据传输与访问,降低网络开销,进一步提升查询效率。

2. 分 shard 模式的构建流程

分 shard 模式的核心逻辑是通过 “哈希分片 + 定向写入” 实现数据的分布式存储。具体步骤如下:

  • 1.数据分片
    • 读取某一标签对应列数据,按行遍历每条记录;
    • 根据 用户 idSpark 任务总数 (n × m) 进行哈希计算,将数据均匀拆分为 n × m 份;
      • 其中 n = Clickhouse shard数
      • m = 根据数据量级设定的并发数 即 Spark 任务数
  • 2.确定目标 shard
    • 每个 Spark 任务根据自身 任务 idshard 总数 再次哈希
    • 计算出该任务需要写入的目标 shard
  • 3.数据写入
    • Spark 任务将拆分后的数据以 Bitmap 结构 写入目标 shard 的本地表
    • 避免跨 shard 写入带来的性能损耗

假设Clickhouse有5个shard,设定并发度m为2,那么spark分区任务数为10,数据分shard写入的示意图如下图所示:

根据用户id分shard写入,可以保障同一个id只出现在同一个shard上,从而可以通过以下技巧提升计算和查询性能:

  • 交并差计算时使用本地表:
    • 各节点完成本地表计算并把结果再写入本地表即完成计算,不需要额外聚合计算
  • 分布式表读取时设置参数 distributed_group_by_no_merge=1:
    • 本地表做完聚合后,不用在分布式表中再聚合所有数据

改造之后使后续的人群计算成功率从原来的85%提升至99.9%,相同数据量级计算速度提升50%

4.2 人群交并差计算

初版方案问题

初版采用实时串型计算,人群创建少时尚可,但创建频率升高后,底层引擎瞬时压力大,资源利用率低,系统稳定性难以保障。

tip

实时串型计算

按固定顺序逐次执行任务的实时计算模式。前一任务完成后才启动下一任务,任务串联执行,要求低延迟响应。

  • 效率瓶颈 -> 任务无法并行处理,整体耗时等于所有子任务耗时之和。
  • 资源浪费 -> 同一时间仅使用部分计算资源,其他资源处于闲置状态。
  • 稳定性风险 -> 当任务数量增多或单个任务耗时增加时,会导致底层引擎瞬时压力骤增,容易引发超时、阻塞甚至系统崩溃。

举例来说,在初版人群计算中,若要计算 用户 A ∩ 用户 B ∪ 用户 C 的结果,实时串型计算会先完成 用户 A ∩ 用户 B 的交集运算,再用其结果与 用户 C 做并集运算,两个步骤严格先后执行,无法同时进行。这种模式在数据量小、任务少的时候可行,但在高并发场景下会暴露明显短板。

新版方案优化

新版引入任务拆解 + 任务队列机制提升效率与稳定性

  • 任务拆解: 将人群计算过程分解为多个小任务,依据数据来源和存储类型生成任务 DAG 有向无环图
  • 任务调度: 任务 DAG 提交至任务队列,由 DAG 调度引擎按任务类型,分发到对应计算 / 存储引擎处理,优化资源分配,提高计算效率,降低单节点负载。

人群计算核心链路如下图:

规则圈选人群的具体流程

  • 用户操作层
    • 用户通过界面选择标签(如 "性别 = 男")和已有人群(如 "活跃用户"),设置多层级逻辑关系(交、并、差集)
    • 定义目标人群规则(例如 "活跃用户 且 性别 = 男 且 非会员用户")。
  • 规则转换层
    • 系统将用户配置的规则组合转换为DSL(领域特定语言) 描述的计算请求
    • 后端对 DSL 进行逻辑合并与优化(如合并重复条件、简化嵌套关系),减少无效计算步骤
  • 任务生成与调度层
    • 根据优化后的规则生成最小化任务节点的 DAG 图(明确任务执行顺序和依赖关系)
    • 按 DAG 节点顺序依次将任务提交至对应队列(如 Clickhouse 计算队列、Iceberg 查询队列),由调度引擎按优先级执行

连续标签处理的具体流程

  • 数据查询层
    • 通过 Trino 引擎直接查询 Iceberg 中的连续标签数据 (如 "观看时长 > 60 分钟") -> 生成针对性的 Iceberg 任务节点。
  • 计算与转换层
    • 输入:定制化 Iceberg SQL(包含连续标签过滤条件)
    • 处理:将多个 SQL 子句按模型表维度整合为单条查询,减少跨表访问次数与计算复杂度
    • 输出:计算结果转换为 Bitmap 格式,写入 Clickhouse 存储
  • 联合计算层: 将 Iceberg 生成的 Bitmap 与以下数据进行交并差运算
    • 离散标签的 Bitmap(如 "会员等级 = 钻石")
    • 已有人群的 Bitmap(如 "近 7 日登录用户")
    • 组合生成下游 Clickhouse 任务 SQL,最终计算得到目标人群的 Bitmap。
  • 附加任务处理: 可按需触发两类后续任务
    • 数据导出任务-> 通过 Boss 系统将人群数据导出至外部存储
    • 离线 Spark 同步任务 -> 将人群 Bitmap 写入 Redis,支撑在线服务查询

4.3 在线服务

在线标签服务是 C 端业务的关键组件,核心任务是实时判断用户是否属于特定人群,并根据结果触发不同的业务逻辑。由于直接面向线上流量,该服务必须满足严格的 SLA 要求,主要体现在以下三个方面。

  • 安全性
    • 高可用架构: 在高并发场景下仍能保持稳定,单点故障不会影响整体服务。
    • 访问控制: 通过人群权限校验,确保只有授权业务才能访问对应的人群数据。
  • 水平扩展性
    • 无状态计算: 每个计算节点独立运行,互不依赖,方便弹性扩容和故障隔离。
    • 模块化存储: 不同业务的数据存储彼此隔离,支持局部扩展,不影响整体系统。
  • 全生命周期支持
    • 覆盖环节: 支持人群数据从生成、验证、配置到上线和下线的全链路操作。
    • 统一接口: 提供标准化服务,确保在保证安全性和扩展性的同时,满足业务在生命周期各阶段的调用需求。

在线服务整体功能架构如下图所示:

人群版本管理

离线构建任务会确定人群数据写入的存储集群和版本,每个人群最多保留 5 个版本,构建时需明确要保留的最近 4 个版本 ID 和待写入的下一个版本 ID。

Redis 采用 KKV 结构存储数据,第一个 K 是用户 id,第二个 K 是人群 id,V 存版本信息,这种结构可解决正查场景(通过用户 id 查询所有人群包)的效率问题,在同时判定多个人群的场景下,只需与 Redis 交互一次。

tip

问题背景

业务经常要问 👉「某个用户属于哪些人群?在这些人群里是哪个版本?」

比如用户小明

  • 属于「活跃用户」人群 -> 版本 3、5
  • 属于「新手用户」人群 -> 版本 2、3

如果使用「人群 → 用户」的方式存(倒排),每次都要去不同的人群桶里找小明,假如要判定 5 个人人群,就要查 5 次 Redis,耗时多。

KKV 的设计思路

  • 以「用户」为第一个 Key
  • 以「人群 ID」为第二个 Key
  • Value 里放这个用户在该人群的版本列表

例如在 Redis 里存储如下数据

user:A → {
10001 : "3,5",
10002 : "2,3"
}

user:A → {
10001 : "4,6",
10002 : "2,3,4"
}

假如要判定「用户 A 是否在 10003 这几个人人群里?」

  • 传统存法(人群→用户): 得查三次(10001 桶一次,10002 桶一次,10003 桶一次)。
  • KKV 存法: 只要查一次用户的清单,拿到一整个 map,再比对有没有这些人群。
  • 数据更新时,Spark 任务先获取对应用户 id 的数据,再进行版本覆盖,业务层保证同一时刻、同一个人群只有一个计算实例运行,避免 ABA 覆盖问题,版本更新逻辑为 V & current_version ∪ next_version,防止 V 无限增长。
  • 由于 Redis 不支持在第二个 K 上设置过期时间。我们采用了类似 Redis 清理过期 Key 的渐进式删除策略,随机从集群中取 Key 判断对应人群是否过期,过期则删除,能在 20 小时内收敛 90% 过期人群,且当前人群用户量级控制在 1w tps

流量控制

流量控制用于保证线上服务的稳定性。例如,一个新的人群包在正式上线前,往往需要进行灰度放量 -> 先只开放给一部分用户,如果监控指标正常,再逐步扩大比例,直至全量发布。这样可以降低风险,避免因为单一人群计算压力过大导致服务抖动

在实现上,系统为每个人群配置了 放流比例、最新版本、冻结版本、替换人群 等元信息。例如下表所示

人群ID放流比例最新版本冻结版本替换人群
78990%2022071020220708790
79180%20220711792
79220%20220711
79310%20220711
  • 人群 789 -> 90% 流量使用版本 20220710,剩余 10% 回退到冻结版本 20220708
  • 人群 791 -> 放流 80%,其余 20% 流量切换到替换人群 792
  • 人群 793 -> 放流 10%,其余 90% 仍然保留。

这样配置后,业务调用时只会访问目标人群(如 789 和 791),不会因为回退或替换导致额外请求。

对业务调用方来说,它始终是在请求 人群 789。

业务方不需要关心 “789 的一部分流量被回退到旧版本”,它看到的还是同一个人群 ID=789。

下图展示了 789、791、793 的分流表

  • 789 分流表:大部分请求命中 20220710 版本,小部分回退到 20220708
  • 791 分流表:80% 请求命中 20220711,20% 替换到人群 792;
  • 793 分流表:仅 10% 命中新版本,其余仍在旧版本运行。

通过这种机制,可以灵活控制每个人群的流量分配,支持平滑灰度和回滚,既保证了用户体验,又降低了系统风险。

条件判定

判定请求条件表达式采用 antlr -> 支持多个人群进行交并差逻辑运算判定

05 落地成果

人群规模标签类型依赖数据库圈选耗时
千万级离线标签Clickhouse<10 秒
亿级离线标签Clickhouse10-30 秒
1-5 千万连续标签Iceberg1-2 分钟
  • 技术稳定性与性能
    • 人群圈选成功率 -> 99.9%
    • 不同规模人群圈选耗时
  • 在线服务稳定性
    • 稳定性达 99.999% ,响应耗时 <5ms
  • 业务场景支持
    • 打通推送、活动、任务、风控等多平台,支持超过30+ 不同业务场景,适应性与扩展性强。
  • 规模扩展性
    • 累计标签数量达 3000+ ,累计人群生产规模达 10w+