数据湖Iceberg | 如何正确使用Iceberg

772a8d2cab262d450d7ac5140a0146ff.png

在介绍如何使用Iceberg之前,先简单地介绍一下Iceberg catalog的概念。catalog是Iceberg对表进行管理(create、drop、rename等)的一个组件。目前Iceberg主要支持HiveCatalog和HadoopCatalog两种Catalog。其中HiveCatalog将当前表metadata文件路径存储在Metastore,这个表metadata文件是所有读写Iceberg表的入口,所以每次读写Iceberg表都需要先从Metastore中取出对应的表metadata文件路径,然后再解析这个Metadata文件进行接下来的操作。而HadoopCatalog将当前表metadata文件路径记录在一个文件目录下,因此不需要连接Metastore。

在下述案例中,笔者会分别使用HiveCatalog和HadoopCatalog创建Iceberg表并进行读写操作,再结合案例在下一篇文章中介绍两者的不同之处。

1

下载Iceberg

在Iceberg官方下载页面(http://iceberg.apache.org/releases/)下载Iceberg Spark runtime Jar。目前社区已经发布了两个版本:apache-iceberg-0.7.0-incubating和0.8.0-incubating,建议下载最新的0.8.0版本。这里需要注意的是,Iceberg在使用上就是一个Jar包,这个Jar包可以放在Spark的Jars目录下,然后就可以使用Spark创建Iceberg表、将数据导入Iceberg表中以及从Iceberg表中查询。另外需要注意的一点是,目前Iceberg仅支持Spark 2.4版本。

我们先将下载的iceberg-spark-runtime-0.8.0-incubating.jar放到spark的classpath下,然后启动spark-shell:

./spark-shell --jars ../../iceberg-spark-runtime-0.8.0-incubating.jar

2

基于HiveCatalog示例

1.使用Spark创建Iceberg表

使用HiveCatalog创建Iceberg表之前需要先在Hive中创建对应的数据库,否则执行建表语句的话会报错。下面是完整的建表语句:

import org.apache.iceberg.hive.HiveTableCatalog
import org.apache.iceberg.catalog.TableIdentifier
import org.apache.spark.sql.types._
import org.apache.iceberg.PartitionSpec
import org.apache.iceberg.spark.SparkSchemaUtil
import org.apache.spark.sql._
import org.apache.spark.sql.functions.{to_date, to_timestamp}
import java.sql.Timestamp
val conf = spark.sessionState.newHadoopConf()
conf.set("hive.metastore.uris","xxxxxx")
conf.set("hive.metastore.warehouse.dir","xxxxxx")
val catalog = new HiveTableCatalog(conf)
//need to create hiveiceberg at Hive env firstly
val name = TableIdentifier.of("hive_iceberg", "action_logs_36")
val sparkSchema = StructType(List(StructField("id", IntegerType,true),
StructField("user", StringType,false),StructField("action", StringType,false),
StructField("music_id", LongType,false), StructField("event_time", TimestampType,false)))
val icebergSchema = SparkSchemaUtil.convert(sparkSchema)
val spec = PartitionSpec.builderFor(icebergSchema).hour("event_time").identity("action").build
val table = catalog.createTable(name, icebergSchema, spec)

建好表后可以在hive查询到创建的表:

hive (default)> use hive_iceberg;
OK
Time taken: 0.148 seconds
hive (hive_iceberg)> show tables;
OK
database_nametab_name
hive_icebergaction_logs
Time taken: 0.035 seconds, Fetched: 1 row(s)
hive (hive_iceberg)> show create table action_logs;
OK
createtab_stmt
CREATE EXTERNAL TABLE `action_logs`(
  `id` int COMMENT '',
  `user` string COMMENT '',
  `action` string COMMENT '',
  `music_id` bigint COMMENT '',
  `event_time` timestamp COMMENT '')
ROW FORMAT SERDE
  'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
STORED AS INPUTFORMAT
  'org.apache.hadoop.mapred.FileInputFormat'
OUTPUTFORMAT
  'org.apache.hadoop.mapred.FileOutputFormat'
LOCATION
  'hdfs://ntsdb0.jd.163.org:9000/libis/hive-2.3.6/hive_iceberg.db/action_logs'
TBLPROPERTIES (
  'metadata_location'='/libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata/00000-e7c1e6ce-8eb9-4faf-a176-bd94dec3c0e4.metadata.json',
  'numFiles'='1',
  'table_type'='ICEBERG',
  'totalSize'='1448',
  'transient_lastDdlTime'='1591587097')
Time taken: 0.501 seconds, Fetched: 20 row(s)

2.使用Spark导入数据到Iceberg表

 iceberg表建好之后,就可以使用spark将数据导入Iceberg表中。下面是完整的示例:

val action_data = Seq(Row(5,"lly","view",13643L,Timestamp.valueOf("2020-06-05 03:03:00")),
Row(7,"mint_1989","view",34769L,Timestamp.valueOf("2020-06-05 04:17:00")),
Row(6,"lly","click",13643L,Timestamp.valueOf("2020-06-05 03:04:00")),
Row(8,"mint_1989","click",34769L,Timestamp.valueOf("2020-06-05 04:17:05")))
val df = spark.createDataFrame(sc.makeRDD(action_data), sparkSchema)
df.write.format("iceberg").mode("append").save("hive_iceberg.action_logs_35")

3.使用Spark查询Iceberg表

scala>spark.read.format("iceberg").load("hive_iceberg.action_logs_35").show
+---+---------+------+--------+-------------------+
| id|     user|action|music_id|         event_time|
+---+---------+------+--------+-------------------+
|  1|      lly|  view|   13643|2020-06-05 03:03:00|
|  2|      lly| click|   13643|2020-06-05 03:04:00|
|  3|mint_1989|  view|   34769|2020-06-05 04:17:00|
|  3|mint_1989| click|   34769|2020-06-05 04:17:05|
+---+---------+------+--------+-------------------+

3

基于HadoopCatalog示例

基于HadoopCatalog的用法与HiveCatalog基本相同,主要的区别在于建表的时候稍有不同,如下所示:

import org.apache.iceberg.hadoop.HadoopCatalog
import org.apache.hadoop.conf.Configuration
val conf = new Configuration()
val catalog = new HadoopCatalog(conf,"hdfs://ntsdb0.jd.163.org:9000/libis/hive-2.3.6/")
val name = TableIdentifier.of("hadoop_iceberg", "action_logs")
...
val table = catalog.createTable(name,icebergSchema, spec)

导入和查询的时候也有一点区别:

df.write.format("iceberg").mode("append").save("hdfs://ntsdb0.jd.163.org:9000/libis/hive-2.3.6/hadoop_iceberg/action_logs")
spark.read.format("iceberg").load("hdfs://ntsdb0.jd.163.org:9000/libis/hive-2.3.6/hadoop_iceberg/action_logs").show

4

Iceberg表的目录组织形式

1.HiveCatalog

hadoop@xxx:~$ hdfs dfs -ls /libis/hive-2.3.6/hive_iceberg.db/action_logs
Found 2 items
drwxrwxrwx   - hadoop supergroup          0 2020-06-08 12:20 /libis/hive-2.3.6/hive_iceberg.db/action_logs/data
drwxrwxrwx   - hadoop supergroup          0 2020-06-08 12:20 /libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata

其中data目录下存储数据文件,metadata目录下存储元数据文件。

2.metadata目录

hadoop@xxx:~$ hdfs dfs -ls /libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata
Found 4 items
-rw-r--r--   1 hadoop supergroup       1448 2020-06-08 11:31 /libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata/00000-e7c1e6ce-8eb9-4faf-a176-bd94dec3c0e4.metadata.json
-rw-r--r--   1 hadoop supergroup       2217 2020-06-08 12:20 /libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata/00001-62ade8ab-c1cf-40d3-bc21-fd5027bc3a55.metadata.json
-rw-r--r--   1 hadoop supergroup       5040 2020-06-08 12:20 /libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata/bb641961-162a-49a8-b567-885430d4e799-m0.avro
-rw-r--r--   1 hadoop supergroup       2567 2020-06-08 12:20 /libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata/snap-6771375506965563160-1-bb641961-162a-49a8-b567-885430d4e799.avro

其中00001-62ade8ab-c1cf-40d3-bc21-fd5027bc3a55.metadata.json中存储表的schema、partition spec以及当前snapshot manifests文件路径。snap-6771375506965563160-1-bb641961-162a-49a8-b567-885430d4e799.avro存储manifest文件路径。bb641961-162a-49a8-b567-885430d4e799-m0.avro记录本次提交的文件以及文件级别元数据。

3.data目录

hadoop@xxx:~$ hdfs dfs -ls /libis/hive-2.3.6/hive_iceberg.db/action_logs/data/event_time_hour=2020-06-04-19/action=click
Found 1 items
-rw-r--r--   1 hadoop supergroup       1425 2020-06-08 12:20 /libis/hive-2.3.6/hive_iceberg.db/action_logs/data/event_time_hour=2020-06-04-19/action=click/00015-47-a9f5ce8f-ee6f-4748-9f49-0f94761859bc-00000.parquet

4.HadoopCatalog

Hadoopcatalog与Hivecatalog的的data目录完全相同,metadata目录下文件稍有不同,HadoopCatalog管理的metadata目录如下所示:

hadoop@xxx:~$ hdfs dfs -ls /libis/hive-2.3.6/hadoop_iceberg/action_logs/metadata
Found 5 items
-rw-r--r--   1 hadoop supergroup       5064 2020-06-08 17:24 /libis/hive-2.3.6/hadoop_iceberg/action_logs/metadata/b222d277-2692-4e35-9327-3716dec9f070-m0.avro
-rw-r--r--   1 hadoop supergroup       2591 2020-06-08 17:24 /libis/hive-2.3.6/hadoop_iceberg/action_logs/metadata/snap-3124052841098464551-1-b222d277-2692-4e35-9327-3716dec9f070.avro
-rw-r--r--   1 hadoop supergroup       1476 2020-06-08 17:23 /libis/hive-2.3.6/hadoop_iceberg/action_logs/metadata/v1.metadata.json
-rw-r--r--   1 hadoop supergroup       2261 2020-06-08 17:24 /libis/hive-2.3.6/hadoop_iceberg/action_logs/metadata/v2.metadata.json
-rw-r--r--   1 hadoop supergroup          1 2020-06-08 17:24 /libis/hive-2.3.6/hadoop_iceberg/action_logs/metadata/version-hint.text
 其中文件version-hint.text中存储当前iceberg表的最新snapshot_id,如下所示:
hadoop@xxx:~$ hdfs dfs -cat /libis/hive-2.3.6/hadoop_iceberg/action_logs/metadata/version-hint.text 2

说明该表的最新snapshot_id是2,即对应的snapshot元数据文件是v2.metadata.json,解析v2.metadata.json可以获取到该表当前最新snapshot对应的schema、partition spec、父snapshot以及该snapshot对应的manifestList文件路径等,因此version-hint.text是HadoopCatalog获取当前snapshot版本的入口。

阅读到这里,读者可能就会问了,HiveCatalog的metadata目录下并没有version-hint.text文件,那它获取当前snapshot版本的入口在哪里呢?它的入口在Metastore中的schema里面,读者可以在HiveCatalog建表schema中的TBPROPERTIES中有个key是"metadata_location",对应的value就是当前最新的snapshot文件。因此,有两点需要说明:

  1. HiveCatalog创建的表,每次提交写入文件生成新的snapshot后都需要更新Metastore中的metadata_location字段。

  2. HiveCatalog和HadoopCatalog不能混用。即使用HiveCatalog创建的表,再使用HadoopCatalog是不能正常加载的,反之亦然。

5

为什么选择HadoopCatalog

上面说到Iceberg目前支持两种Catalog,而且两种Catalog相互不兼容。那这里有两个问题:

  1. 社区是出于什么考虑实现两种不兼容的Catalog?

  2. 因为两者不兼容,必须选择其一作为系统唯一的Catalog,那是选择HiveCatalog还是HadoopCatalog,为什么?

先回答第一个问题。社区是出于什么考虑实现两种不兼容的Catalog?

在回答这个问题之前,首先回顾一下上一篇文章中介绍到的基于HadoopCatalog,Iceberg实现数据写入提交的ACID机制,最终的结论是使用了乐观锁机制和HDFS rename的原子性一起保障写入提交的ACID。如果某些文件系统比如S3不支持rename的原子性呢?那就需要另外一种机制保障写入提交的ACID,HiveCatalog就是另一种不依赖文件系统支持,但是可以提供ACID支持的方案,它在每次提交的时候都更新MySQL中同一行记录,这样的更新MySQL本身是可以保证ACID的。这就是社区为什么会支持两种不兼容Catalog的本质原因。

再来回答第二个问题。HadoopCatalog依赖于HDFS提供的rename原子性语义,而HiveCatalog不依赖于任何文件系统的rename原子性语义支持,因此基于HiveCatalog的表不仅可以支持HDFS,而且可以支持s3、oss等其他文件系统。但是HadoopCatalog可以认为只支持HDFS表,比较难以迁移到其他文件系统。但是HadoopCatalog写入提交的过程只依赖HDFS,不和Metastore/MySQL交互,而HiveCatalog每次提交都需要和Metastore/MySQL交互,可以认为是强依赖于Metastore,如果Metastore有异常,基于HiveCatalog的Iceberg表的写入和查询会有问题。相反,HadoopCatalog并不依赖于Metastore,即使Metastore有异常,也不影响Iceberg表的写入和查询。

考虑到我们目前主要还是依赖HDFS,同时不想强依赖于Metastore,所以我们选择HadoopCatalog作为我们系统唯一的Catalog。即使有一天,想要把HDFS上的表迁移到S3上去,也是可以办到的,大家想想,无论是HadoopCatalog还是HiveCatalog,数据文件和元数据文件本身都是相同的,只是标记当前最新的snapshot的入口不一样,那只需要简单的手动变换一下入口就可以实现Catalog的切换,切换到HiveCatalog上之后,就可以摆脱HDFS的依赖,问题并不大。

6

Iceberg表相关元数据内容

上一篇介绍Iceberg的文章中讲到了Iceberg写入数据文件提交snapshot的时候会依次生成manifest文件、manifestList文件以及snapshot文件,那这些元数据文件中都存放什么记录呢?如果使用HiveCatalog创建的表,可以直接使用spark读出相关元数据记录。如果是HadoopCatalog创建的表,暂时不能直接使用spark读出相关信息,但是也可以使用文件解析器解析出相关元数据文件内容。

下文基于HiveCatalog建表模式介绍Iceberg相关元数据:

scala>spark.read.format("iceberg").load("hive_iceberg.action_logs.history").show
+--------------------+-------------------+---------+-------------------+
|     made_current_at|        snapshot_id|parent_id|is_current_ancestor|
+--------------------+-------------------+---------+-------------------+
|2020-06-08 12:20:...|6771375506965563160|     null|               true|
+--------------------+-------------------+---------+-------------------+
scala> spark.read.format("iceberg").load("hive_iceberg.action_logs.snapshots").show
+--------------------+-------------------+---------+---------+--------------------+--------------------+
|        committed_at|        snapshot_id|parent_id|operation|       manifest_list|             summary|
+--------------------+-------------------+---------+---------+--------------------+--------------------+
|2020-06-08 12:20:...|6771375506965563160|     null|   append|/libis/hive-2.3.6...|[spark.app.id -> ...|
+--------------------+-------------------+---------+---------+--------------------+--------------------+
scala> spark.read.format("iceberg").load("hive_iceberg.action_logs.manifests").show(false)
+---------------------------------------------------------------------------------------------------+------+-----------------+-------------------+----------------------+-------------------------+------------------------+-------------------------------------------------------------+
|path                                                                                               |length|partition_spec_id|added_snapshot_id  |added_data_files_count|existing_data_files_count|deleted_data_files_count|partition_summaries                                          |
+---------------------------------------------------------------------------------------------------+------+-----------------+-------------------+----------------------+-------------------------+------------------------+-------------------------------------------------------------+
|/libis/hive-2.3.6/hive_iceberg.db/action_logs/metadata/bb641961-162a-49a8-b567-885430d4e799-m0.avro|5040  |0                |6771375506965563160|4                     |0                        |0                       |[[false, 2020-06-04-19, 2020-06-04-20], [false, click, view]]|
+---------------------------------------------------------------------------------------------------+------+-----------------+-------------------+----------------------+-------------------------+------------------------+-------------------------------------------------------------+
scala> spark.read.format("iceberg").load("hive_iceberg.action_logs.files").show(false)
+---------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------------+------------+------------------+-------------------+---------------------------------------------+----------------------------------------+----------------------------------------+--------------------------------------------------------------------+--------------------------------------------------------------------+------------+-------------+
|file_path                                                                                                                                                |file_format|partition      |record_count|file_size_in_bytes|block_size_in_bytes|column_sizes                                 |value_counts                            |null_value_counts                       |lower_bounds                                                        |upper_bounds                                                        |key_metadata|split_offsets|
+---------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------------+------------+------------------+-------------------+---------------------------------------------+----------------------------------------+----------------------------------------+--------------------------------------------------------------------+--------------------------------------------------------------------+------------+-------------+
|/libis/hive-2.3.6/hive_iceberg.db/action_logs/data/event_time_hour=2020-06-04-19/action=view/00007-39-4e7af786-9668-4e3d-b8aa-07b7b30fa60a-00000.parquet |PARQUET    |[442027, view] |1           |1418              |67108864           |[1 -> 51, 2 -> 50, 3 -> 51, 4 -> 47, 5 -> 51]|[1 -> 1, 2 -> 1, 3 -> 1, 4 -> 1, 5 -> 1]|[1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0]|[1 -> , 2 -> lly, 3 -> view, 4 -> K5, 5 -> !�F�]      |[1 -> , 2 -> lly, 3 -> view, 4 -> K5, 5 -> !�F�]      |null        |[4]          |
|/libis/hive-2.3.6/hive_iceberg.db/action_logs/data/event_time_hour=2020-06-04-19/action=click/00015-47-a9f5ce8f-ee6f-4748-9f49-0f94761859bc-00000.parquet|PARQUET    |[442027, click]|1           |1425              |67108864           |[1 -> 51, 2 -> 50, 3 -> 52, 4 -> 47, 5 -> 51]|[1 -> 1, 2 -> 1, 3 -> 1, 4 -> 1, 5 -> 1]|[1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0]|[1 -> , 2 -> lly, 3 -> click, 4 -> K5, 5 -> ���F�]     |[1 -> , 2 -> lly, 3 -> click, 4 -> K5, 5 -> ���F�]     |null        |[4]          |
|/libis/hive-2.3.6/hive_iceberg.db/action_logs/data/event_time_hour=2020-06-04-20/action=view/00023-55-f0494272-6166-4386-88c7-059e3081aa11-00000.parquet |PARQUET    |[442028, view] |1           |1460              |67108864           |[1 -> 51, 2 -> 56, 3 -> 51, 4 -> 47, 5 -> 51]|[1 -> 1, 2 -> 1, 3 -> 1, 4 -> 1, 5 -> 1]|[1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0]|[1 -> , 2 -> mint_1989, 3 -> view, 4 -> ч, 5 -> '��G�] |[1 -> , 2 -> mint_1989, 3 -> view, 4 -> ч, 5 -> '��G�] |null        |[4]          |
|/libis/hive-2.3.6/hive_iceberg.db/action_logs/data/event_time_hour=2020-06-04-20/action=click/00031-63-a04ce10d-ae98-4004-bda8-2f18d842b66b-00000.parquet|PARQUET    |[442028, click]|1           |1467              |67108864           |[1 -> 51, 2 -> 56, 3 -> 52, 4 -> 47, 5 -> 51]|[1 -> 1, 2 -> 1, 3 -> 1, 4 -> 1, 5 -> 1]|[1 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 5 -> 0]|[1 -> , 2 -> mint_1989, 3 -> click, 4 -> ч, 5 -> @r�G�]|[1 -> , 2 -> mint_1989, 3 -> click, 4 -> ч, 5 -> @r�G�]|null        |[4]          |
+---------------------------------------------------------------------------------------------------------------------------------------------------------+-----------+---------------+------------+------------------+-------------------+---------------------------------------------+----------------------------------------+----------------------------------------+--------------------------------------------------------------------+--------------------------------------------------------------------+------------+-------------+

7

总结

本文基于上一篇介绍Iceberg入门的文章,介绍了如何使用Iceberg进行简单的创建表、往表中写数据、查询表数据以及表中元数据。同时也介绍了HadoopCatalog和HiveCatalog的不同以及我们选择HadoopCatalog的一些想法。

作者简介

子和,网易大数据开发工程师,长期从事分布式KV数据库、分布式时序数据库以及大数据底层组件等相关工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值