【遇见Doris】Doris基于Hive表的全局字典设计与实现

感谢7月25日来参加 Doris 与 Dolphin Scheduler 合作举办的线上 Meetup 的小伙伴们,现在为大家带来 Meetup 的内容。

本次 Meetup 请到了来自百度、奇安信、美团点评和易观的技术大牛带来技术分享。了解更多详情请关注 Doris 官方公众号。嘉宾分享回顾会陆续放出,公众号后台回复“0725”立即get回放录像及嘉宾PPT。

点击上方“开源社”关注我们

(Author: 谭嘉露)

| 作者:ApacheDoris
| 转载自:ApacheDoris

| 编辑:李明康

| 设计:谭嘉露

| 责编:袁睿斌

本次为大家带来的是第二期内容回顾:《 Doris 基于 Hive 表的全局字典设计与实现》,第一期在这儿

本期主讲人:

王博

  美团点评

  数据开发工程师

讲座主要分为四部分内容:

为什么需要全局字典

什么是全局字典

全局字典的技术实现

总结与未来规划 

为什么需要全局字典

我们在业务上需要 OLAP 引擎能够⽀持精确去重的预计算,但这个需求本身是不是合理的呢?

目前线上数据分析会有一些与⾦钱相关的计算,比如某一个业务方要计算他们个人的销售业绩、以及一些广告的点击率等,这些能够转化为金钱的计算需求是要求精确去重的,近似去重是⽐较难以容忍的。

目前 Doris 的 BITMAP 列已经⽀持了对于整型的精确去重计算,但是现实的场景是业务会有更多的数据类型需要精确去重,比如 String,这些非整型的数据类型是无法直接存入 BITMAP 的。

什么是全局字典


1. 全局字典的数据结构

那么基于以上的问题我们需要引入一个新的数据结构——全局字典,其实就是一个比较大的MAP。

MAP 的KEY 列保存了原始值,VALUE 列保存了编码后的整型值。这样的话,即使我们有一个字段是 String 类型的,如果能将 String 字段值转成一个整型值,再把原始值的 String 类型的值和转换后的整型值都存入这个 MAP,那么在导入数据时就可以读取这个 MAP,先将原始值替换为整型值,再将整型值导入到 Doris 的 BITMAP列中,最终实现对 String类型的精确去重查询。

其实就是设计一个中间的映射数据结构去实现对原始值的转化,再把转化后的值存入 Doris,这样就能实现对任意数据类型进行精确去重。

2. 全局字典的构建与使用流程

当然,只有一个 MAP 的数据结构是不够的,它需要一个完整的使用流程。

简单来说,首先是更新,这个更新就是对全局字典数据结构进行更新。第二步是读取,在编码的阶段,也就是已经有了字典之后,需要读这个字典的数据,然后根据映射把原始值转成编码值,核心的步骤就是一个构建和编码的流程。这两个流程是比较容易形成性能瓶颈点的,这里后面会具体说到。

3. 业界目前主要有两种全局字典的实现

第一个是基于Trie树的全局字典,第二种是基于Hive表的全局字典。今天我们主要讲的就是基于Hive表的全局字典。

 

全局字典的实现

1. 基于Trie树的全局字典

这个方案主要是Kylin引擎支持,在美团内部有大规模的落地经验,大部分业务都用的这套方案。这套方案在内部使用的优点缺点也是很明确的了。

(1)Trie树的基本设计

Trie树就是基于前缀匹配的树。每次插入的时候都会进行一次前缀的匹配,如果前缀能匹配上,多出来的部分会做一个大小的比较。如果新插入的值大于已经存在的值,就插入到右侧,否则插入到左侧,会做一个排序和比较的过程。如果前缀都不匹配的话,会生成一个新的节点。

根据Trie树的特性我们可以得出一个结论——Trie结构在数据写入的时候就已经完成了去重和排序的工作(其实我们主要利用它去重的特点)。在读取数据时,数据在Trie树中的位置就是对应的编码值。也就是说,当数据写入完成时就达到了我们的预期——去重以及对数据的编码。

(2)对Trie树进行分片

在实际的线上环境中有一个Trie树是不够的,因为通常Trie树非常大,小的可能几十MB上百MB,大的几十GB上百GB都有,所以一定要做树的分裂操作。

一个树经过分裂会分成多个分片,这样做的好处是不用一次把数据全加载到内存中,可以通过对分片文件进行缓存从而提高访问效率。但是目前的方案容易在缓存中出现一个性能的瓶颈。具体实现时,一个子树就是一个分片,每一个分片都是位于HDFS的一个文件。

(3)Trie树总结与分析

优点是资源比较节约,写入一个树的过程目前是一个单节点的构建,而编码阶段只需要做一次MAPREDUCE操作就可以完成字典的读取和对原始值的编码,相对来说资源比较节约而且在大部分场景下性能表现良好。

但是我们经过内部使用,也发现了一些局限性。

主要问题是在缓存上,上面提到了要对 Trie 树进行分片,但是在两种情况下分片缓存的命中率会下降

  • 字典非常大。我们有些业务去重字段基数很高,所以字典很大,达到几十GB上百GB的情况,分片会非常非常多。虽然字典是排过序的,但是输入的数据可能是比较离散的。比如你有10个分片,输入10行数据,你要对这10行数据做一个编码,最差的情况是你要访问10次 HDFS,比较好的情况是10行数据可以在一个分片中查到,生产环境中不能对用户的输入值做出最优的假设,事实上当字典基数很高时,最差的情况往往容易发生。

  • 有多列高基数去重列。比如当你对一张表进行编码的时候,因为单节点的内存是有限的,每列都需要加载不同的字典,会导致字典不停地读入读出,因为内存不足最终也会导致缓存的命中率下降。这种下降带来的影响是字典整体的构建流程耗时变长。

第二个局限性是目前字典是单个 Reducer 构建的,写入 HDFS 时一半数据位于本节点,一半数据分散在其他节点。而在我们使用过程中观察到 HDFS 不会把网络带宽作为一个查询路由,你的查询有可能打在单个节点上从而把 HDFS 的单节点网络带宽打满。此外,单节点的构建效率也比较低。

2. 基于Hive表的全局字典

基于 SparkSQL+Hive 表构建字典这套方案的优点是可以解决 Trie 树的瓶颈场景。

这套方案其实非常简单,它只需要三张表,第一张表是字典表也就是MAP,KEY列保存了去重列的原始值,VALUE列保存了去重字段编码后的值,该列为整型。第二张表是用户输入表,就是用户那张原始的Hive表。还有一张是去重表,保存了每次输入的值去重后的集合。

(1)构建流程:抽取去重值

如上图所示,右上是一个原始表,保存了两列数据,我们期望对这张表进行的操作是,把 name 列替换成一个编码后的列。第一步是要把去重值抽出来,用 insert 和group by写一个 SQL,将需要去重的值保存到一张 grpby_table。

(2)构建流程:构建全局字典

第二步要做一个全局字典的构建,这步其实就是写入字典的过程。

看右侧,我们期望的结果是对上面这个需要去重的值的 grpby_table 的name列生成一个对应的VALUE,也就是进行编码。

步骤#1是获取本次导入的新增的去重值,用去重值表left join字典表,通过where条件可以把新增的值抽出来,因为我们只需要对每次新增的去重值做一个编码。

步骤#2是使用row_number函数对新增的去重值做一个编码,现在我们已经拿到了这样一个集合。

步骤#3是和字典表做一个union,将增量值和历史值合在一起,结果就是右侧下方的表。

(3)构建流程:对原始值进行编码

 

有了字典之后我们就可以进行编码操作了,这个编码是对原始值做编码。

右上方左边的表是原始表( Hive 表),右边是我们对 name 列编码后的字典,这样我们只需要用原始表的 name 列和字典表的 dict_key(name)列做一个 join 就能得到右下所示的表。这就完成了对原始表进行编码的操作。

这里要关注一个问题,当去重列数据倾斜时容易出现性能瓶颈(后文会具体说明解决方案)。

(4)性能瓶颈分析:字典构建阶段

 

因为我们目前使用 SparkSQL 的窗口函数(row_number)对去重值做解码,此函数的瓶颈在于它是单机执行的,而非分布式的。可用内存有限,单次输入亿级别的行数时容易内存溢出。

这个问题是比较容易解决的:每次拿到去重值之后先进行一个统计计算,如果行数超过一亿行就做一个切分。

(5)性能瓶颈分析:编码阶段

 

第二个瓶颈相对来说不是很容易解决,当去重列发生数据倾斜的时候,要么跑不完要么耗时非常长。

第一种优化方法是使用 MAP JOIN,但 MAP JOIN 也是有性能上限的。

第二种优化方法是,如果倾斜列空值较多那么就只对非空值进行编码,先把原始值进行切分,把非空的抽出来之后就一定不会倾斜了。

还有一种非常极端的情况,如果数值特别大无法使用 MAP JOIN 而且倾斜的值不是空值的,那么就对原始表,维表和字典表都加一个随机的前缀(这个问题其实就转化为如何解决 Spark 数据倾斜的问题)。这种方法的弊端是,添加了随机前缀意味着原始值和字典值都进行了膨胀。

总结未来与规划

1. 两种方案对比分析

对比两种方案,有两个基本的维度:字典构建和对原始值进行编码。

在字典构建上,基于 Hive 的方案因为是使用 SparkSQL 和窗口函数实现的,这种方法并行度很高,性能会很好;而Trie树是基于单节点构建的,相对来说吞吐量会低一点,会慢一点。

在编码上,基于 Hive 的方法一次 Shuffle 完成一列的编码,如果有多列 Bitmap 列的话,就需要多次 Shuffle 这时资源开销会大一些;而 Trie 树是不需要 Shuffle的。

基于 Hive 的方法相对有更大的弹性,除了极端的数据倾斜的情况,Hive 这个方案是几乎没有什么瓶颈,是很占优势的;但是 Trie 树的方案它的瓶颈不在资源上,这也是我们线上落地的时候比较重要的痛点,它会在特定情况在缓存上出现瓶颈,这点是加资源也无法解决的问题。

2. 美团为什么选择了基于Hive表的方案?

最终我们设计 Doris 的字典方案时选择了Hive表方案是因为考虑到:

(1)Hive 表方案除了个别数据倾斜情况下整个方案没有什么明显的瓶颈,未来可以考虑从 Spark 引擎层的角度解决极端的数据倾斜问题。

(2)Hive 表方案可以解决 Trie 树的短板问题。

(3)这个方案实现起来比较简单可以很快落地。

(4)弹性比较好,在资源足够的情况下可以达到非常高的吞吐,很高的并行度。

3. 未来规划

目前在美团内部,基于 Hive 表的方案已经成为 Hive2Doirs 流程的默认方案,已经跑了大概2个月了。未来计划需要有更多的业务接入,可能会面临更复杂的考验,我们也期待对这套方案做持续的改进。

未来优化的方向是:

(1)考虑从 Spark 的层面解决数据倾斜的问题,比如引入 SparkAQE 特性(当 Join 时发现单个节点处理数据量过大时,会把数据做一个分发,引入多个节点去处理倾斜的数据,这样就可能会解决数据倾斜的问题),未来如果引入这一特性,整套 SparkSQL+Hive 做字典的方案将会没有明显的瓶颈。

(2)使用 Trie 树与 Hive 表融合的方案,这个方向是一个很理想的设计,但是改造成本会非常高。在大部分情况下使用 Trie 树做字典的编码,在基数高字典大的情况下就把 Trie 树转成 Hive 表然后使用 Join 完成编码。不过这套方案的改造成本比较高,比如写入时 Trie 树支持分布式构建,读取时需要能转成SparkRDD 再转成 Hive 表。

*本文图片来源原文,如有侵权请联系删除!

开源社简介

开源社是由国内外支持开源的企业,社区及个人,依“贡献,共识,共治”原则,所组织的厂商中立、纯志愿者、非营利的开源联盟,旨在共创健康可持续发展的开源生态体系,并推动中国开源社区成为全球开源软件的积极参与及贡献者。我们专注于开源治理、国际接轨、社区发展和开源项目。

相关阅读 | Related Reading

使用Apache协议的是自由软件吗?

开源特训营

开源到底有多难?

欢迎大家点击阅读原文,关注开源社官网

<think>好的,我现在需要帮用户介绍一下数据中台结合业务系统的架构图。首先,用户之前问过数据中台的是否需要独立于业务系统,现在可能想更全面地了解两者在整体架构中的关系。我需要先回顾之前的回答,确保内容连贯。 接下来,用户想要架构图,虽然我只能用文字描述,但需要尽量详细,让用户能想象出来。可能需要分层次介绍,比如业务系统层、数据接入层、数据存储计算层、数据服务层等。 用户可能是技术人员或架构师,希望了解如何将数据中台整合到现有业务系统中。他们可能关心数据流向、各层组件以及如何实现数据整合分析。需要强调数据中台的核心作用,比如数据集成、处理、服务化,以及如何支持上层应用。 要注意之前的回复中提到的分层建模,比如ODS、DWD、DWS,这些应该在架构图中体现。同时,结合业务系统的架构,可能需要说明业务系统作为数据源,数据中台如何通过ETL或CDC获取数据,处理后再提供服务。 用户可能没有说出来的深层需求是希望了解如何设计一个可扩展、高效的数据架构,确保业务系统和中台既能独立运作又能协同工作。需要强调解耦、标准化、性能优化等方面。 可能会遗漏的部分是具体的例子或组件,比如使用的技术工具(Kafka、Hadoop等),需要适当提及。同时,确保架构描述清晰,逻辑分明,让用户能理解各部分的职责和交互流程。 最后,总结时需要再次强调数据中台业务系统的协同效应,以及架构设计的关键点,帮助用户形成完整的认识。以下是</think>数据中台业务系统结合的架构图描述及核心逻辑解析,通过分层设计展现两者的协同关系: --- ### **整体架构图(文字描述)** ``` +-------------------+ +-----------------------+ | | | | | **业务系统层** | | **数据应用层** | | (OLTP场景) |<--->| (OLAP场景) | | - 交易系统 | | - BI报 | | - CRM系统 | | - 用户画像 | | - ERP系统 | | - 实时大屏 | | - 物流系统 | | - 推荐引擎 | +---------+ ↓ ↑ +-------------------+ +-----------------------+ | **数据接入层** | | **数据服务层** | | - CDC实时同步 | | - 统一API网关 | | - ETL批处理 | | - 标签服务 | | - 日志采集 | | - 指标服务 | | - API数据拉取 | | - 数据资产目录 | +-------------------+ +-----------------------+ ↓ +-----------------------------------------------+ | **数据存储计算层** | | (数据中台核心) | | +-----------------+ +---------------------+ | | | **原始数据层** | | **明细数据层** | | | | - ODS原始镜像 | | - DWD宽 | | | | (业务备份) | | (多系统数据整合) | | | +-------+ | | +-----------------+ +---------------------+ | | | **汇总数据层** | | **维度数据层** | | | | - DWS汇总指标 | | - 维度/字典 | | | | (预聚合计算) | | (一致性维度定义) | | | +-----------------+ +-----------------+ ``` --- ### **核心模块解析** #### **1. 业务系统层(数据源)** - **角色**:数据的生产者,支撑企业核心业务流程。 - **典型系统**: - **交易系统**:订单、支付数据。 - **CRM系统**:客户信息、行为日志。 - **ERP系统**:供应链、库存数据。 - **IoT/日志系统**:设备传感器数据、用户操作日志。 - **特点**:高并发事务处理(OLTP),结构随业务快速迭代。 #### **2. 数据接入层** - **核心能力**:**多模态数据采集**。 - **技术工具**: - **批量同步**:Sqoop、DataX(适合T+1数据迁移)。 - **实时同步**:Debezium(CDC)、Kafka(消息队列)。 - **日志采集**:Flume、Filebeat。 - **API对接**:定制化接口调用(如Restful API)。 - **关键设计**: - **数据缓冲**:使用Kafka作为异步管道,解耦业务系统中台。 - **轻量级清洗**:去重、格式校验(如时间戳标准化)。 #### **3. 数据存储计算层(数据中台核心)** - **分层设计**: - **ODS层(原始数据层)**: - 直接映射业务系统结构,保留原始数据。 - 存储方式:全量快照 + 增量日志(如Hive分区)。 - **DWD层(明细数据层)**: - **独立建模**:跨系统数据整合(如订单+用户+商品宽)。 - **标准化处理**:统一字段命名、单位、编码(如性别转码)。 - **DWS层(汇总数据层)**: - 预聚合指标(如分区域销售额、用户留存率)。 - 使用OLAP引擎(如ClickHouse、Doris)加速查询。 - **维度层**: - 维护一致性维度(如地区字典、用户分级标准)。 - **技术栈**: - **存储**:HDFS(海量数据)、Iceberg/Hudi(数据湖格式)。 - **计算**:Spark/Flink(批流一体)、Presto/Trino(即席查询)。 #### **4. 数据服务层** - **核心功能**:**将数据资产API化**。 - **典型服务**: - **查询服务**:SQL接口、OLAP多维查询。 - **标签服务**:实时用户标签计算(如“高价值客户”)。 - **指标服务**:预定义业务指标(GMV、DAU)的快速获取。 - **数据目录**:元数据管理、数据血缘追踪。 - **技术实现**: - **API网关**:统一鉴权、限流(如Kong、Spring Cloud Gateway)。 - **缓存优化**:Redis缓存热点数据,降低数据库压力。 #### **5. 数据应用层** - **典型场景**: - **BI报**:Tableau、FineBI可视化。 - **用户画像**:圈选特定群体进行精准营销。 - **实时监控**:基于Flink的欺诈检测、库存预警。 - **AI模型**:推荐算法、销量预测。 --- ### **关键交互流程** 1. **数据从业务系统到中台** - 交易系统 → Kafka(CDC捕获增量) → ODS层(原始存储) → ETL清洗 → DWD宽。 2. **数据从中台到业务系统** - 数据服务层(API) → 返回实时库存状态 → 交易系统下单页展示。 3. **闭环反馈** - 推荐引擎(应用层) → 调用标签服务 → 更新用户行为数据 → 回流至DWD层。 --- ### **架构设计原则** 1. **松耦合** - 业务系统中台通过消息队列或API交互,避免直接库依赖。 2. **可扩展性** - 数据分层结构支持灵活新增数据源(如新增一个业务系统仅需扩展ODS层接入)。 3. **性能隔离** - 业务系统OLTP数据库中台OLAP存储分离,避免资源竞争。 4. **治理标准化** - 中台内定义全局数据标准(如主数据模型),反向推动业务系统改造。 --- ### **示例:电商场景联动** 1. **业务系统**:用户下单后,订单数据经CDC同步至中台ODS层。 2. **数据中台**: - DWD层:关联订单(交易系统)、用户(CRM)、库存(ERP)生成宽。 - DWS层:按小时汇总各品类销售额。 3. **数据服务**:大屏调用API获取实时销售额,风控系统查询用户画像。 4. **应用反馈**:推荐引擎根据画像调整首页商品排序,提升转化率。 --- ### **总结** 数据中台业务系统的架构设计,本质是通过**分层解耦**和**服务化**,将分散的业务数据转化为可复用的数据资产。业务系统专注于流程执行,中台则承担数据整合、加工开放职责,最终形成“业务产生数据 → 数据反哺业务”的闭环。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值