数据湖三剑客:Delta Lake、Hudi 与 Iceberg 详解 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的的第11天,本篇笔记主要是关于第十一次大数据课程《数据湖三剑客:Delta Lake、Hudi 与 Iceberg 详解》的课堂笔记
发展历史
Hadoop HDFS: 数据湖最开始的概念——分布式存储HDFS,使用目录来区分不同的数据集。
- 好处:
- 同一公司/组织可以使用共享数据
- 数据访问方便,灵活性高
- 坏处:
- 没有记录文件的schema (包括列名、列类型),经常使用Schema on Query的方式
- 难以得知数据集包含了那些文件,是通过什么样的分区组织的
- 如果多个程序都在修改这个数据集(修改数据、修改表结构), 其他程序难以配合做修改
Hive: 数据湖的演进——Hive Metastore,对数据湖中的数据进行集中“定义”
- 坏处: 如果A和B都在读同一个文件,同时C在写这个文件,则A和B读到的文件可能不同。
湖仓一体:
- 数据仓库:
- 将数据从数据源提取和转换,加载到目的地
- 数据仓库存储+计算不分离
- 数据仓库严格控制写入数据的schema
湖仓一体(数据湖的现状):
- 结合了数据湖和数据仓库的优势
- 将数据仓库中对于数据的严格管理直接实现到了低成本的分布式存储之上
- Key Features :
- Transaction ACID
- Schema管理
- 存储计算分离
- 支持多种计算引擎和文件格式
核心技术
文件结构:
- 写入数据湖时
- 按照每条数据的date进行分区
- 额外使用metadata文件记录表信息。
Time travel:
- 要点:
- 每次写入都生成一个新的元数据文件,记录变更
- 分区数据在Update时, 不要删除旧数据,保证新旧共存
- 元数据中存储具体的文件路径,而不仅仅是分区文件夹
- 具体实现:
- 每一次写入操作,创建一个新的json文件,以递增版本号命名,记录本次新增/删除的文件。
- 每当产生N个json,做一次聚合,记录完整的分区文件信息。
- 用checkpoint记录上次做聚合的版本号。
Transaction:
- 数据湖中的ACID :
- Atomicity: 原子性-本次写入要么对用户可见,要么不可见(需要设计)
- Consistency:- 致性-输入是什么,落盘的就是什么(由计算引擎保证)
- Isolation: 事务隔离-正确解决读写冲突和写写冲突(需要设计)
- Durability: 持久性-落完数据后,即便服务器重启结果不变(由存储引擎保证)
-
原子性
如何确保原子性? (从用户可见性入手! )
- 用户只会读取以版本号数字命名的json文件, 每次都读取到最大的版本号作为数据集的现状
- 新的写入写完parquet后开始写json文件,使用hash值对json文件命名,如a2fs4hfg8ee.json
- 直到json文件内容写入完毕, 利用hdfs的renamelfAbsent能力将a2fs4hfg8ee.json替换为000006.json,到此为止commit完成,新的读取将会以00006.json作为最新版本
读写冲突已经被解决:
- 新的写入除非已经commit,否则用户读不到
- 用户正在读的分区,被另个写入进行了更新,数据不会进行替换,而是共存
-
事务隔离
Update写入流程:
- 从最新的版本中,获取需要update的分区
- 乐观锁先把该写入的文件全落盘,然后进入写json阶段
- 分几种情况:
- 发现版本号和一开始没区别,直接写新的版本。
- 发现版本号增加了,看看新增的这些版本有没有更新我要更新的分区?
- 没有,直接写新的版本。
- 有,两者都更新了同一分区,得重新update了。
Schema Evolution:
Add/Drop/Rename:
- 用户并不直接读取parquet文件本身,而是通过数据湖接口读取,如Dataset<Row> ds = simpleDataL ake.read(mytable).option(date=2020-01-01)
- 数据湖内部会读取应该读的parquet,并在schema上做进一步处理 ID将data和metadata的列名做一一对应
- 唯一确定的ID。新增列赋予新ID。删列ID不复用。
- 写入数据时, ID也写入数据文件
- 读取数据时,用ID做映射,如果
- Data中没有 , metadata中有: ADD
- Data中有 , metadata中没有: DROP
- Data和metadata中都有同一ID ,但是name不同: RENAME
- 如果都有同一列名,而ID不同:先DROP后ADD
各有所长
IceBerg:
一些有助于filter 的数据被层层记录,比如:
- Manifest file记录了每个data file的分区范围
- Manifest list记录了每个manifest file的分区范围分区可以被快速定位!可以做manifest list级别裁剪。
- Manifest file记录了每个data file每一列的最 大值,最小值可以通过其他的列(Userld) 做data file级别裁剪。
- 传统的分区方式: 数据中包含了date列, 则按照date分区;如果希望按照hour分区,则需要新增hour列。
- ceberg的分区方式: 数据中包含timestamp列,设置好partition transform方式的设置为date时,iceberg 帮你转化为date分区的设置为hour时,iceberg 帮你转化为hour分区中lceberg记录了这层转化关系,并且按你的需要进行partition evolution。
Hudi:
- Timeline Service:记录的信息类似于metadata
- Upsert:每一条样本都有一个主键PK,当Upsert一条数据时,如果现有的数据中有这个PK,则update这条数据。否则直接insert这条数据
- Incremental:某个时间点后新增的数据
- Copy on write:Copy-On-Write文件使用列式存储(如parquet)。数据在写入期间执行同步合并原有数据来更新并重写文件,数据每次提交都会进行隐式压缩,并附带时间戳。
- Merge On Read:增量数据放到append log里,并没有和历史数据进行合并,只有在compact(压缩)操作之后,才会与历史文件的合并和更新。
Delta Lake:
- ACID Transaction
- Schema 校验(不是evolution)
- 流批一体
- Time Travel
- Upsert/Delete
- Z-Order 优化
- 只开源了一部分,由Databricks自己主导开发,Z-order 等优化的实现未开源