随着互联网高速发展,大数据技术快速发展和迅速迭代,降低了用户处理海量数据的门槛,越来越多的应运场景出现在我们的身边存储和处理需求越来越多样化,逐渐呈现出数据仓库往数据湖方向发展、批处理往流式处理发展、本地部署往云模式发展的趋势。
但在技术发展层面,逐渐出现了诸多的掣肘,不断有新的问题出现,仅仅就存储方面来讲,与数据库这样高度优化的技术相比,大数据技术的抽象和实现还是太原始和初级。
目前的数据仓库技术出现了一定的局限性,比如单一不变的 schema 和模型已经无法满足各类不同场景和领域的数据分析的要求、流批一体的数据存储引擎和计算引擎适配问题以及Hadoop体系文件系统的ACID能力缺失等问题急需要解决,但就目前的技术栈而言,打破这些技术的桎梏,似乎多少有些力不从心,这个过程中,一项重大变革似乎在破晓前显得尤为必要。
在不停地探索和思考中,大数据人开始慢慢醒悟,回头看向成名已久的数据库,将更多数据库的成熟技术和理念借鉴到大数据中,似乎是一条高效又稳健的道路。至此,数据湖技术应运而生,在诸多方面向数据库看齐,你可以说是学习,当然,也可以说是致敬,是业界针对这些问题的一种解决方案。
那么,什么是数据湖技术呢?
计算引擎之下、数据存储之上,处于中间层的数据湖。
简单地说,这类新技术是介于上层计算引擎和底层存储格式之间的一个中间层,我们可以把它定义成一种“数据组织格式”。其最核心的点便是将事务能力带到了大数据领域,并抽象成统一的中间格式供不同引擎适配对接。
为此,Uber开源了Apache Hudi,Databricks提出了Delta Lake,而 Netflix 则发起了 Apache Iceberg 项目,一时间这种具备 ACID 能力的表格式中间件成为了大数据、数据湖领域炙手可热的方向。
Iceberg 将其称之为“表格式”也是表达类似的含义。它与底层的存储格式(比如 ORC、Parquet 之类的列式存储格式)最大的区别是,它并不定义数据存储方式,而是定义了数据、元数据的组织方式,向上提供统一的“表”的语义。它构建在数据存储格式之上,其底层的数据存储仍然使用 Parquet、ORC 等进行存储。
Apache Iceberg、Hudi 和 Delta Lake 诞生于不同公司,需要解决的问题存在差异,因此三者在设计初衷上稍有不同。
其中,Iceberg 的设计初衷更倾向于定义一个标准、开放且通用的数据组织格式,同时屏蔽底层数据存储格式上的差异,向上提供统一的操作 API,使得不同的引擎可以通过其提供的 API 接入;Hudi 的设计初衷更像是为了解决流式数据的快速落地,并能够通过 upsert 语义进行延迟数据修正;Delta Lake 作为 Databricks 开源的项目,更侧重于在Spark 层面上解决 Parquet、ORC 等存储格式的固有问题,并带来更多的能力提升。
虽然这三个项目在设计初衷上稍有不同,但实现的思路和提供的能力却非常相似,他们都提供了 ACID 的能力,都基于乐观锁实现了冲突解决和提供线性一致性,同时相应地提供了 time travel 的功能。
但是因为设计初衷的不同,三个项目当前的能力象限各有不同,Iceberg 在其格式定义和核心能力上最为完善,但是上游引擎的适配上稍显不足;Hudi 基于 Spark 打造了完整的流式数据落地方案,但是其核心抽象较弱,与 Spark 耦合较紧;Delta Lake 同样高度依赖于 Spark 生态圈,与其他引擎的适配尚需时日。
那么,当下数据湖技术呈现的三足鼎立的技术场面,优劣点如何看待,技术选型如何去做,当然是我们最关心的问题,下文逐步解析。
Delta Lake,spark生态圈急先锋
传统的 lambda 架构需要同时维护批处理和流处理两套系统,资源消耗大,维护复杂。
基于 Hive的数仓或者传统的文件存储格式(比如 parquet / ORC),都存在一些难以解决的问题:小文件问题、并发读写问题、有限的更新支持及海量元数据(例如分区)导致 metastore不堪重负问题等。
如上图,Delta Lake 是 Spark 计算框架和存储系统之间带有 Schema 信息的存储中间层。
它集中解决了传统hive数仓的诸多问题,使得实时数据湖变得优雅又丝滑,不见了天生的慵懒,只看到轻盈又婀娜的身姿。重要变化如下:
设计了基于 HDFS 存储的元数据系统,解决 metastore 不堪重负的问题;
支持更多种类的更新模式,比如 Merge / Update / Delete 等操作,配合流式写入或者读取的支持,让实时数据湖变得水到渠成;
流批操作可以共享同一张表;
版本概念,可以随时回溯,避免因为一次误操作或者代码逻辑而无法恢复的灾难性后果。
基于Parquet的列式存储层,在多并发写入之间提供 ACID 事务保证。每次写入都是一个事务,并且在事务日志中记录了写入的序列顺序。
但是,Delta Lake定位于spark流批一体的数据处理工具,地主家的公子,自己家的事情如数家珍,轻松搞定,但走出家门后,难免会有些水土不服。
Apache Hudi,有天生缺陷的优等生
Apache Hudi 代表 Hadoop Upserts and Incrementals,能够使HDFS数据集在分钟级的时延内支持变更,也支持下游系统对这个数据集的增量处理。
Hudi数据集通过自定义的inputFormat 兼容当前 Hadoop 生态系统,包括 Apache Hive,Apache Parquet,Presto 和 Apache Spark,使得终端用户可以无缝的对接。
如下图,基于 Hudi 简化的服务架构,分钟级延迟。
Hudi 会维护一个时间轴,在每次执行操作时(如写入、删除、合并等),均会带有一个时间戳。
通过时间轴,可以实现在仅查询某个时间点之后成功提交的数据,或是仅查询某个时间点之前的数据。
这样可以避免扫描更大的时间范围,并非常高效地只消费更改过的文件(例如在某个时间点提交了更改操作后,仅 query 某个时间点之前的数据,则仍可以 query 修改前的数据)。
如上图的左边,Hudi 将数据集组织到与 Hive 表非常相似的基本路径下的目录结构中。
数据集分为多个分区,每个分区均由相对于基本路径的分区路径唯一标识。
如上图的中间部分,Hudi 以两种不同的存储格式存储所有摄取的数据。
读优化的列存格式(ROFormat):仅使用列式文件(parquet)存储数据。在写入/更新数据时,直接同步合并原文件,生成新版本的基文件(需要重写整个列数据文件,即使只有一个字节的新数据被提交)。此存储类型下,写入数据非常昂贵,而读取的成本没有增加,所以适合频繁读的工作负载,因为数据集的最新版本在列式文件中始终可用,以进行高效的查询。
写优化的行存格式(WOFormat):使用列式(parquet)与行式(avro)文件组合,进行数据存储。在更新记录时,更新到增量文件中(avro),然后进行异步(或同步)的compaction,创建列式文件(parquet)的新版本。此存储类型适合频繁写的工作负载,因为新记录是以appending 的模式写入增量文件中。但是在读取数据集时,需要将增量文件与旧文件进行合并,生成列式文件。
Apache Iceberg,基础扎实,后生可畏
Iceberg 作为新兴的数据湖框架之一,开创性地抽象出“表格式”table format)这一中间层,既独立于上层的计算引擎(如Spark和Flink)和查询引擎(如Hive和Presto),也和下层的文件格式(如Parquet,ORC和Avro)相互解耦。
此外 Iceberg 还提供了许多额外的能力:
ACID事务;
时间旅行(time travel),以访问之前版本的数据;
完备的自定义类型、分区方式和操作的抽象;
列和分区方式可以进化,而且进化对用户无感,即无需重新组织或变更数据文件;
隐式分区,使SQL不用针对分区方式特殊优化;
面向云存储的优化等;
Iceberg的架构和实现并未绑定于某一特定引擎,它实现了通用的数据组织格式,利用此格式可以方便地与不同引擎(如Flink、Hive、Spark)对接。
所以 Iceberg 的架构更加的优雅,对于数据格式、类型系统有完备的定义和可进化的设计。
综合而言,三个引擎的初衷场景并不完全相同,Hudi 为了 incremental 的 upserts,相对而言最为成熟,但底层架构设计较差,扩展性及生态延续方面难度较大;Iceberg 定位于高性能的分析与可靠的数据管理,底层架构的抽象及架构的开放性方面做的很好,数据湖upsert和compaction两个关键的功能也趋于完善,正在快速发展期;Delta 定位于流批一体的数据处理,无缝对接Spark生态。
我们在技术选型的时候,不仅要知到要到哪里去,更要明确我们从哪里来,选择适合自己当下业务需求的技术,才能更快速、更高效辅助业务开发。