病毒|作业帮基于 DeltaLake 的数据湖建设最佳实践( 二 )


数据探查慢、取数稳定性差 数据产出后很多时候是面向分析师使用的 , 直接访问 Hive 则需要几十分钟甚至小时级 , 完全不能接受 , 经常会收到用户的吐槽反馈 , 而采用 Presto 来加速 Hive 表的查询 , 由于 Presto 的架构特点 , 导致查询的数据表不能太大、逻辑不能太复杂 , 否则会导致 Presto 内存 OOM , 且 Hive 已有的 UDF 和 VIEW 等在 Presto 中也没法直接使用 , 这也非常限制分析师的使用场景 。
三、解决方案 问题分析
不论是常规的 ODS 层到 ADS 层全链路产出慢、或者是面对具体表的探查取数慢 , 本质上都是在说 Hive 层的计算性能不足 。 从上述场景分析来看:
链路计算慢的原因:由于 Hive 不支持增量更新 , 而来自业务层数据源的 Mysql-Binlog 则包含大量的更新信息 , 因此在 ODS 这一层 , 就需要用增量数据和历史的全量数据做去重后形成新的全量数据 , 其后 DWD、DWS、ADS 均是类似的原理 。 这个过程带来了数据的大量重复计算 , 同时也带来了数据产出的延迟 。数据查询慢的原因:由于 Hive 本身缺少必要的索引数据 , 因此不论是重吞吐的计算还是希望保障分钟级延迟的查询 , 均会翻译为 MR-Job 进行计算 , 这就导致在数据快速探查场景下 , 查询结果产出变慢 。方案调研
从上面分析来看 , 如果可以解决离线数仓的数据增量更新问题就可以提高链路计算的性能 , 而对于数据表支持索引能力 , 就可以在保障查询功能不降级的前提下降低查询的延迟 。
基于 HBase+ORC 的解决方案 解决数据的更新问题 , 可以采用 HBase 来做 。 对 RowKey 设置为主键 , 对各列设置为 Column , 这样就可以提供数据实时写入的能力 。 但是受限于 HBase 的架构 , 对于非主键列的查询性能则非常差 。 为了解决其查询性能 , 需要定期(如小时表则小时级、天级表则天级)将 HBase 的表按照特定字段排序后导出到 HDFS 并存储为 ORC 格式 , 但是 ORC 格式只支持单列的 min、max 索引 , 查询性能依然无法满足需求 , 且由于 HBase 的数据写入一直在持续发生 , 导出的时机难以控制 , 在导出过程中数据还可能发生变化 , 如我们希望导出12月11日21点前的数据作为数据表21点分区的数据就需要考虑版本数、存储容量、筛选带来的计算性能等因素 , 系统复杂度陡增 , 同时也引入了 HBase 系统增加了运维成本 。
数据湖 数据湖实际上是一种数据格式 , 可以集成在主流的计算引擎(如 Flink/Spark)和数据存储(如对象存储)中间 , 不引入额外的服务 , 同时支持实时 Upsert , 提供了多版本支持 , 可以读取任意版本的数据 。
目前数据湖方案主要有 DeltaLake、Iceberg、Hudi 。我们调研了阿里云上这三种方案 , 其区别和特点如下:

此外 , 考虑到易用性(DeltaLake 语义清晰 , 阿里云提供全功能 SQL 语法支持 , 使用简单;后两者的使用门槛较高)、功能性(仅 DeltaLake 支持 Zorder/Dataskipping 查询加速)等方面 , 结合我们的场景综合考虑 , 我们最后选择 DeltaLake 作为数据湖解决方案 。
四、基于 DeltaLake 的离线数仓 引入 DeltaLake 后 , 我们的离线数仓架构如下:

首先 Binlog 通过 Canal 采集后经过我们自研的数据分发系统写入 Kafka , 这里需要提前说明的是 , 我们的分发系统需要对 Binlog 按照 Table 级严格保序 , 原因下面详述 。 其后使用 Spark 将数据分批写入 DeltaLake 。 最后我们升级了数据取数平台 , 使用 Spark SQL 从 DeltaLake 中进行取数 。
在使用 DeltaLake 的过程中 , 我们需要解决如下关键技术点:
流数据转批
业务场景下 , 对于离线数仓的 ETL 任务 , 均是按照数据表分区就绪来触发的 , 如2021-12-31日的任务会依赖2021-12-30日的数据表分区就绪后方可触发运行 。 这个场景在 Hive 的系统上是很容易支持的 , 因为 Hive 天然支持按照日期字段(如dt)进行分区 。 但是对于 DeltaLake 来说 , 我们数据写入是流式写入的 , 因此就需要将流数据转为批数据 , 即某天数据完全就绪后 , 方可对外提供对应天级分区的读取能力 。