数据湖简介–Iceberg
数据湖生态

如上图所示,对于一个成熟的数据湖生态而言:
首先,下层需具备海量存储的能力,常见的有对象存储、公有云存储以及HDFS;
在此之上,需要支持丰富的数据类型,包括非结构化的图像视频,半结构化的CSV、XML、Log以及结构化的数据库表;
除此之外,需要高效统一的元数据管理,使得计算引擎可以方便地索引到各种类型数据来做分析;
最后,需要支持丰富的计算引擎,包括Flink、Spark、Hive、Presto等,从而方便对接企业中已与的一些应用架构;
结构化数据在数据湖上应用场景

上图为一个典型的数据湖上应用场景;
数据源上可能会有各种数据,不同的数据源和不同格式。比如事务数据、日志、埋点信息、IOT等;这些数据经过数据流然后进入计算平台,这个时候它需要一个结构化的方案,把数据组织放到一个存储平台上,然后供后端的数据应用进行实时计算或实时查询;
该方案具备以下特征:
首先,数据源类型繁多,需要支持比较丰富的数据Schema的组织;
其次,数据注入过程需要支撑实时的数据查询,需要ACID保证,确保不会读到未完成的脏数据;
最后,数据需支持临时变更,如格式调整或新增列,需避免像传统数仓将数据读取处理后重新写入,而是需要一个轻量级的解决方案来达成需求;
Iceberg数据库的定位就在于实现这样的功能,于上对哦街计算平台,于下对接存储平台;
结构化数据在数据库上解决方案

对于数据结构化组织,典型的解决方式是用数据库传统的组织方式;
如上图所示,上方有命名空间,数据库表的隔离;中间有多个表,可以提供多种数据Schema的保存;底下存放数据,表格需要提供ACID的特性,同时支持局部Schema的演进;
Iceberg表数据组织架构

- 快照Metadata:表格Schema、Partition、Partition spec、Manifest List路径、当前快照等;
- Manifest List:Manifest File路径及其Partition、数据文件统计信息;
- Manifest File:Data File路径及其每列数据上下边界;
- Data File:实际数据内容,支持Parquet、ORC、Avro等数据格式;
Iceberg写入流程

以Flink写入数据至iceberg举例:
Data Workers会从元数据上读取数据并解析,然后将记录交给iceberg进行存储;iceberg与常见数据库一样,也支持预定义分区,根据根据分区键将数据写入到不同分区,形成一些数据文件;同时会形成文件清单文件,并提交给Commit Worker;Commit Worker读取当前快照信息并与新接收到的文件清单文件合并,生成一个新的Manifest List以及后续元数据的标文件信息,之后进行提交,成功后形成新的快照;
Iceberg读取流程

以Flink从iceberg读取数据举例:
Flink Table scan worker发起scan操作,scan的时候按照树形结构,从根开始,找到当前快照或者用户指定的历史快照,然后从快照中拿出当前快照的Manifest List文件,根据当时保存的信息,过滤出满足查询条件的Manifest File;
根据Manifest File记录的信息,过滤出需要的Data Files,获取到Data File后转交给Record reader workers,由它读取Data Files中满足条件的Record并返回给上层调用;
iceberg文件已树形结构组织,不需要使用List,都是单路径指向,对于对象存储比较友好(因为对象存储在List上是比较消耗资源的操作);
Iceberg Catalog功能介绍
Iceberg提供Catalog用良好的抽象来对接数据存储和元数据管理;任何一个存储,只要实现Iceberg的Catalog抽象,就与机会跟Iceberg对接,用来组织接入数据湖方案;
Catalog提供如下抽象:
- Iceberg定义了一系列角色文件;
- 支持File IO定制,包括读写与删除;
- 支持定制命名空间和表操作,也可称为元数据操作;
- 支持定制表的读取、扫描及提交
Catalog提供灵活的操作空间,方便对接各种下层的各种存储;
对象存储支撑Iceberg数据湖
Iceberg Catalog实现
目前社区Iceberg Catalog实现分为两部分,一是数据IO部分,二是元数据管理部分;
当前缺少面向私有对象存储的Catalog实现;
对象存储与HDFS比较

- 对象存储在集群扩展性,小文件油耗,多站点部署和低存储开销上更加有优势;
- HDFS提供最佳上传和原子性Rename;(这两个优势正是Iceberg需要的特性)
比较之:集群扩展性

HDFS架构使用单个NN保存所有元数据,这决定了它单节点的能力有限,所以在元数据方便没有横向扩展能力;
对象存储一般采用哈希方式,把元数据分割成各个块,把这个块交给不同Node上面的服务来进行管理,天然地它元数据上限会更高,甚至在极端情况下可以进行rehash,将块切的更细,交给更多的Node来管理元数据,达到扩展能力;
比较之:小文件友好

在大数据应用中,小文件越来越常见,并逐渐称为一个痛点;
HDFS基于架构的限制,小文件存储受限于NN内存等资源,虽然HDFS提供了Archive的方法来合并小文件,减少对NN的压力,但这需要额外增加复杂度,且非原生功能;
同样,小文件的TPS也是受限于NN的处理能力,因为它只有单个NN;对象存储的元数据是分布式存储和管理,流量可以很好地分布到各个Node上,这样单节点就可以存储海量的小文件;
当前,很多对象存储提供多介质、分层加速,提升小文件性能;
比较之:多站点部署

对象存储支持多站点部署全局命名空间支持丰富的规则配置
对象存储的多站点部署能力适用于两地三中心多活架构,而HDFS没有原生的多站点部署能力;
比较之:低存储开销

对于存储系统来说,为了适应随机的硬件故障,它一般会有副本机制来保护数据;常见的如三副本,将数据存储三份,分布到不同节点上,存储开销也是三倍,但可以容忍最高两副本故障而不丢失数据;另一种是Erasure Coding,简称EC,以10+2举例,将数据切分为10个数据块,然后用算法算出两个代码块,一共12个块;将12个块分布至四个节点上,存储开销是1.2倍,同样可以容忍最高两副本故障而不丢失数据,这种情况可以用剩余的10个块算出所有的数据,这样减少存储开销,同事达到故障容忍程度;
HDFS默认使用三副本机制,新版本HDFS也已经支持EC能力;它基于文件做EC,所以对小文件有天然的劣势,因为如果小文件的大小小于分块要求的大小时,它的开销就会比原定的开销更大,因为两个代码块这边是不能省的;在极端情况下,如果它的大小等同于单个代码块的大小,它就等同于三副本;同时,HDFS一旦启用EC,就不能再支持append、hflush、hsync等操作,这会极大地影响EC能够使用的场景;对象存储原生支持EC,对于小文件,它内部会把小文件合并成一个大的块再做EC,这样确保数据开销方面始终是恒定的,基于预先配置的策略。
对象存储的挑战:数据追加与上传

在S3协议中,对象在上传时需要提供大小;
以S3标准为例,对象存储跟Iceberg对接时,S3标准对象存储不支持数据追加上传的接口,协议要求上传文件时提供文件大小,这种情况对于流失File IO传入不太友好;
解决方案一:S3 Catalog数据追加上传-小文件缓存本地/内存

对小文件,流失传入时先写入到本地缓存/内存中,等数据写完后,再上传到对象存储中;
解决方案二:S3 Catalog数据追加上传-MPU分段上传大文件

对于大文件,使用S3标准定义的MPU分段上传;
一般分为几个步骤:
创建初始化的MPU,拿到一个Upload ID,然后给每一个分段赋予一个Upload ID以及一个编号,这些分块就可以并行上传;
上传完成后,还需要进行Complete操作,相当于通知系统,将基于同一个Upload ID以及所有的编号,从小到大排列起来,组成一个大文件;
MPU优缺点:
- MPU分片数量存在上限,S3标准里只有1W个分片;想支持大文件,分块不能太小,所以对于小分块的文件,依然要利用前面一种方法进行缓存上传;
- MPU支持并行上传;假设进行一个异步上传,文件在缓存达到以后,不用等上一个分块上传成功,就可以继续缓存下一个,之后开始上传;当前面注入的速度足够块,后端异步提交就变成了并行操作,达到更快上传能力;
对象存储挑战:原子提交
数据注入过程,提交部分分为几个步骤,是一个线性事务;首先读取当前的快照版本,然后将这一次的文件清单合并,接着提交自己的新版本;类似编程常见的’i=i+1’,并不是原子操作,对象存储的标准里也没有提供这个能力;