导读:腾讯音乐内容库数据平台旨在为应用层提供库存盘点、分群画像、指标分析、标签圈选等内容分析服务,高效为业务赋能。目前,内容库数据平台的数据架构已经从1.0演进到了4.0,经历了分析引擎从ClickHouse到ApacheDoris的替换、经历了数据架构语义层的初步引入到深度应用,有效提高了数据时效性、降低了运维成本、解决了数据管理割裂等问题,收益显著。本文将为大家分享腾讯音乐内容库数据平台的数据架构演进历程与实践思考,希望所有读者从文章中有所启发。
作者:腾讯音乐内容库数据平台张俊、代凯
腾讯音乐娱乐集团(简称“腾讯音乐娱乐”)是中国在线音乐娱乐服务开拓者,提供在线音乐和以音乐为核心的社交娱乐两大服务。腾讯音乐娱乐在中国有着广泛的用户基础,拥有目前国内市场知名的四大移动音乐产品:QQ音乐、酷狗音乐、酷我音乐和全民K歌,总月活用户数超过8亿。
业务需求
腾讯音乐娱乐拥有海量的内容曲库,包括录制音乐、现场音乐、音频和视频等多种形式。通过技术和数据的赋能,腾讯音乐娱乐持续创新产品,为用户带来更好的产品体验,提高用户参与度,也为音乐人和合作伙伴在音乐的制作、发行和销售方面提供更大的支持。
在业务运营过程中我们需要对包括歌曲、词曲、专辑、艺人在内的内容对象进行全方位分析,高效为业务赋能,内容库数据平台旨在集成各数据源的数据,整合形成内容数据资产(以指标和标签体系为载体),为应用层提供库存盘点、分群画像、指标分析、标签圈选等内容分析服务。
数据架构演进
TDW是腾讯最大的离线数据处理平台,公司内大多数业务的产品报表、运营分析、数据挖掘等的存储和计算都是在TDW中进行,内容库数据平台的数据加工链路同样是在腾讯数据仓库TDW上构建的。截止目前,内容库数据平台的数据架构已经从1.0演进到了4.0,经历了分析引擎从ClickHouse到ApacheDoris的替换、经历了数据架构语义层的初步引入到深度应用,有效提高了数据时效性、降低了运维成本、解决了数据管理割裂等问题,收益显著。接下来将为大家分享腾讯音乐内容库数据平台的数据架构演进历程与实践思考。
数据架构1.0
如图所示为数据架构1.0架构图,分为数仓层、加速层、应用层三部分,数据架构1.0是一个相对主流的架构,简单介绍一下各层的作用及工作原理:
数仓层:通过ODS-DWD-DWS三层将数据整合为不同主题的标签和指标体系,DWM集市层围绕内容对象构建大宽表,从不同主题域DWS表中抽取字段。
加速层:在数仓中构建的大宽表导入到加速层中,Clickhouse作为分析引擎,Elasticsearch作为搜索/圈选引擎。
应用层:根据场景创建DataSet,作为逻辑视图从大宽表选取所需的标签与指标,同时可以二次定义衍生的标签与指标。
存在的问题:
数仓层:不支持部分列更新,当上游任一来源表产生延迟,均会造成大宽表延迟,进而导致数据时效性下降。
加速层:不同的标签跟指标特性不同、更新频率也各不相同。由于ClickHouse目前更擅长处理宽表场景,无区别将所有数据导入大宽表生成天的分区将造成存储资源的浪费,维护成本也将随之升高。
应用层:ClickHouse采用的是计算和存储节点强耦合的架构,架构复杂,组件依赖严重,牵一发而动全身,容易出现集群稳定性问题,对于我们来说,同时维护ClickHouse和Elasticsearch两套引擎的连接与查询,成本和难度都比较高。
除此之外,ClickHouse由国外开源,交流具有一定的语言学习成本,遇到问题无法准确反馈、无法快速获得解决,与社区沟通上的阻塞也是促进我们进行架构升级的因素之一。
数据架构2.0
基于架构1.0存在的问题和ClickHouse的局限性,我们尝试对架构进行优化升级,将分析引擎ClickHouse切换为Doris,Doris具有以下的优势:
ApacheDoris的优势:
Doris架构极简易用,部署只需两个进程,不依赖其他系统,运维简单;兼容MySQL协议,并且使用标准SQL。
支持丰富的数据模型,可满足多种数据更新方式,支持部分列更新。
支持对Hive、Iceberg、Hudi等数据湖和MySQL、Elasticsearch等数据库的联邦查询分析。
导入方式多样,支持从HDFS/S3等远端存储批量导入,也支持读取MySQLBinlog以及订阅消息队列Kafka中的数据,还可以通过FlinkConnector实时/批次同步数据源(MySQL,Oracle,PostgreSQL等)到Doris。
社区目前ApacheDoris社区活跃、技术交流更多,SelectDB针对社区有专职的技术支持团队,在使用过程中遇到问题均能快速得到响应解决。
同时我们也利用Doris的特性,解决了架构1.0中较为突出的问题。
数仓层:ApacheDoris的Aggregate数据模型可支持部分列实时更新,因此我们去掉了DWM集市层的构建,直接增量到Doris/ES中构建宽表,解决了架构1.0中上游数据更新延迟导致整个宽表延迟的问题,进而提升了数据的时效性。数据(指标、标签等)通过Spark统一离线加载到Kafka中,使用Flink将数据增量更新到Doris和ES中(利用Flink实现进一步的聚合,减轻了Doris和ES的更新压力)。
加速层:该层主要将大宽表拆为小宽表,根据更新频率配置不同的分区策略,减小数据冗余带来的存储压力,提高查询吞吐量。Doris具备多表查询和联邦查询性能特性,可以利用多表关联特性实现组合查询。
应用层:DataSet统一指向Doris,Doris支持外表查询,利用该特性可对ES引擎直接查询。
架构2.0存在的问题:
DataSet灵活度较高,数据分析师可对指标和标签自由组合和定义,但是不同的分析师对同一数据的定义不尽相同、定义口径不一致,导致指标和标签缺乏统一管理,这使得数据管理和使用的难度都变高。
Dataset与物理位置绑定,应用层无法进行透明优化,如果Doris引擎出现负载较高的情况,无法通过降低用户查询避免集群负载过高报错的问题。
数据架构3.0
针对指标和标签定义口径不统一,数据使用和管理难度较高的问题,我们继续对架构进行升级。数据架构3.0主要的变化是引入了专门的语义层,语义层的主要作用是将技术语言转换为业务部门更容易理解的概念,目的是将标签(tag)与指标(metric)变为“一等公民”,作为数据定义与管理的基本对象。
引入语义层的优势有:
对于技术来说,应用层不再需要创建DataSet,从语义层可直接获取特定内容对象的标签集(tagset)和指标集(metricset)来发起查询。
对于数据分析师来说,可统一在语义层定义和创建衍生的指标和标签,解决了定义口径不一致、管理和使用难度较高的问题。
对于业务来说,无需耗费过长时间考虑什么场景应选择哪个数据集使用,语义层对标签和指标透明统一的定义提升了工作效率、降低了使用成本。
存在的问题:
从架构图可知,标签和指标等数据均处于下游位置,虽然标签与指标在语义层被显式定义,但仍然无法影响上游链路,数仓层有自己的语义逻辑,加速层有自己的导入配置,这样就造成了数据管理机制的割裂。
数据架构4.0
在数据架构3.0的基础上,我们对语义层进行更深层次的应用,在数据架构4.0中,我们将语义层变为架构的中枢节点,目标是对所有的指标和标签统一定义,从计算-加速-查询实现中心化、标准化管理,解决数据管理机制割裂的问题。
语义层作为架构中枢节点所带来的变化:
数仓层:语义层接收SQL触发计算或查询任务。数仓从DWD到DWS的计算逻辑将在语义层中进行定义,且以单个指标和标签的形式进行定义,之后由语义层来发送命令,生成SQL命令给数仓层执行计算。
加速层:从语义层接收配置、触发导入任务,比如加速哪些指标与标签均由语义层指导。
应用层:向语义层发起逻辑查询,由语义层选择引擎,生成物理SQL。
架构优势:
可以形成统一视图,对于核心指标和标签的定义进行统一查看及管理。
应用层与物理引擎完成解耦,可进一步对更加灵活易用的架构进行探索:如何对相关指标和标签进行加速,如何在时效性和集群的稳定性之间平衡等。
存在的问题:
因为当前架构是对单个标签和指标进行了定义,因此如何在查询计算时自动生成一个准确有效的SQL语句是非常有难度的。如果你有相关的经验,期待有机会可以一起探索交流。
优化经验
从上文已知,为更好地实现业务需求,数据架构演进到4.0版本,其中ApacheDoris作为分析加速场景的解决方案在整个系统中发挥着重要的作用。接下来将从场景需求、数据导入、查询优化以及成本优化四个方面出发,分享基于Doris的读写优化经验,希望给读者带来一些参考。
场景需求
目前我们有+标签,+指标,对应TDW中有80+Source表,单个标签、指标的最大基数达到了2亿+。我们希望将这些数据从TDW加速到Doris中完成标签画像和指标的分析。从业务的角度,需要满足以下要求:
实时可用:标签/指标导入以后,需实现数据尽快可用。不仅要支持常规离线导入T+1,同时也要支持实时打标场景。
部分更新:因每个Source表由各自ETL任务产出对应的数据,其产出时间不一致,并且每个表只涉及部分指标或标签,不同数据查询对时效性要求也不同,因此架构需要支持部分列更新。
性能高效:具备高效的写入能力,且在圈选、洞察、报表等场景可以实现秒级响应。
控制成本:在满足业务需求的前提下,最大程度地降低成本;支持冷热数据精细化管理,支持标签灵活上下架。
数据导入方案
为了减轻Doris写入压力,我们考虑在数据写入Doris之前,尽量将数据生成宽表,再写入到Doris中。针对宽表的生成,我们有两个实现思路:第一个是在TDW数仓中生成宽表;第二个是Flink中生成宽表。我们对这两个实现思路进行了实践对比,最终决定选择第二个实现思路,原因如下:
在TDW中生成宽表,虽然链路简单,但是弊端也比较明显。
存储成本较高,TDW除了要维护离散的80+个Source表外,还需维护1个大宽表、2份冗余的数据。
实时性比较差,由于每个Source表产出的时间不一样,往往会因为某些延迟比较大的Source表导致整个数据链路延迟增大。
开发成本较高,该方案只能作为离线方式,若想实现实时方式则需要投入开发资源进行额外的开发。
而在Flink中生成宽表,链路简单、成本低也容易实现,主要流程是:首先用Spark将相关Source表最新数据离线导入到Kafka中,接着使用Flink来消费Kafka,并通过主键ID构建出一张大宽表,最后将大宽表导入到Doris中。如下图所示,来自数仓N个表中ID=1的5条数据,经过Flink处理以后,只有一条ID=1的数据写入Doris中,大大减少Doris写入压力。
通过以上导入优化方案,极大地降低了存储成本,TDW无需维护两份冗余的数据,Kafka也只需保存最新待导入的数据。同时该方案整体实时性更好且可控,并且大宽表聚合在Flink中执行,可灵活加入各种ETL逻辑,离线和实时可对多个开发逻辑进行复用,灵活度较高。
数据模型选择
目前我们生产环境所使用的版本为ApacheDoris1.1.3,我们对其所支持的Unique主键模型、Aggregate聚合模型和Duplicate明细模型进行了对比,相较于Unique模型和Duplicate模型,Aggregate聚合模型满足我们部分列更新的场景需求:
Aggregate聚合模型可以支持多种预聚合模式,可以通过REPLACE_IF_NOT_NULL的方式实现部分列更新。数据写入过程中,Doris会将多次写入的数据进行聚合,最终用户查询时,返回一份聚合后的完整且正确的数据。
另外两种数据模型适用的场景,这里也进行简单的介绍:
Unique模型适用于需要保证Key唯一性场景,同一个主键ID多次导入之后,会以append的方式进行行级数据更新,仅保留最后一次导入的数据。在与社区进行沟通后,确定后续版本Unique模型也将支持部分列更新。
Duplicate模型区别于Aggregate和Unique模型,数据完全按照导入的明细数据进行存储,不会有任何预聚合或去重操作,即使两行数据完全相同也都会保留,因此Duplicate模型适用于既没有聚合需求,又没有主键唯一性约束的原始数据存储。
确定数据模型之后,我们在建表时如何对列进行命名呢?可以直接使用指标或者是标签的名称吗?
在使用场景中通常会有以下几个需求:
为了更好地表达数据的意义,业务方会有少量修改标签、指标名称的需求。
随着业务需求的变动,标签经常存在上架、下架的情况。
实时新增的标签和指标,用户希望数据尽快可用。
Doris1.1.3是不支持对列名进行修改的,如果直接使用指标/标签名称作为列名,则无法满足上述标签或指标更名的需求。而对于上下架标签的需求,如果直接以drop/addcolumn的方式实现,则会涉及数据文件的更改,该操作耗时耗力,甚至会影响线上查询的性能。
那么,有没有更轻量级的方式来满足需求呢?接下来将为大家分享相关解决方案及收益:****
为了实现少量标签、指标名称修改,我们用MySQL表存储相应的元数据,包括名称、全局唯一的ID和上下架状态等信息,比如标签歌曲名称song_name的ID为4,在Doris中存储命名为a4,用户使用更具有业务含义song_name进行查询。在查询Doris前,我们会在查询层将SQL改写成具体的列名a4。这样名称的修改只是修改其元数据,底层Doris的表结构可以保持不变。
为了实现标签灵活上下架,我们通过统计标签的使用情况来分析标签的价值,将低价值的标签进入下架流程。下架指的是对元信息进行状态标注,在下架标签重新上架之前,不会继续导入其数据,元信息中数据可用时间也不会发生变化。
对于实时新增标签/指标,我们基于名称ID的映射在Doris表中预先创建适量ID列,当标签/指标完成元信息录入后,直接将预留的ID分配给新录入的标签/指标,避免在查询高峰期因新增标签/指标所引起的SchemaChange开销对集群产生的影响。经测试,用户在元信息录入后10分钟内就可以使用相应的数据。
值得