数据湖 Iceberg



数据湖(datalake)

传统数据库或数据仓库的特点

  • 存储和计算绑定
  • 专有数据格式
  • 数据结构类型较为单一
  • 对可靠性、一致性、数据事务要求较高
  • 细粒度的数据权限控制
  • 由于存储和计算绑定,容易优化,性能较高
  • 扩展性能较一般
  • 预先建模,写入型 schema,按预设的 schema 读写
  • 主要存储计算处理后的数据
  • 主要用于 Report、BI(有实时性要求)

数据湖的特点

  • 存储和计算解耦分离,支持多种存储,支持多种计算引擎
  • 开放通用的数据格式
  • 支持多种数据格式,包括结构化(行列)、非结构化(email、文档等)、半结构化(CSV、日志、XML、JSON 等)、二进制(图像、音频、视频)
  • 对可靠性、一致性、数据事务要求不高
  • 对数据权限控制要求不高
  • 优化和性能,比存储计算一体化的方案要差些
  • 低存储成本,高扩展性
  • 无须提前建模,读取型 schema,可以在读取时才确定 schema,灵活度高
  • 主要存储原始数据,也存储计算处理后的数据
  • 用于数据分析、机器学习,也可以用于 Report、BI 但性能差些

可以看到,数据湖像是一个,包括多种存储、多种计算、并解耦了存储和计算的、主要用于存储原始数据、做数据分析的、灵活的系统

如果没有节制地、不加选择地往数据湖里灌数据,可能会造成数据沼泽,导致混乱,难以挖掘有用信息

数据湖产品有 Iceberg、Delta lake、Hudi 等

对象存储

传统的存储

  • DAS(Direct Attached Storage):直接读写本地的磁盘或磁盘阵列
  • SAN(Storage Area Network):多个节点组成专用存储网络,能统一管理存储资源,使存储和服务隔离
  • NAS(Network Attached Storage): SAN 的存储资源在网络,文件系统在本地,而 NAS 的文件系统也在远端,关联后访问远端文件就像访问本地文件一样

对象存储,起源于云计算,就是把要存储的东西作为一个对象 Object 存到云端,不能直接打开或修改,只能做 PUT(上传)、GET(下载)、DELETE(删除)等操作,每个对象有相应的 metadata 来描述这个对象,可以理解为像网盘那样,可以上传下载任何东西,但不能直接编辑运行,并且可以对存储的东西做一些图片文字的描述(就是 metadata)

对象存储的优势(比如对比 HDFS)

  • 易于集群扩展,HDFS 有单点问题,元数据都在 NameNode,只有 DataNode 容易扩展,而对象的存储,和元数据的管理都是分布式可扩展的,可以无限扩展
  • 对大量小文件的支持好,HDFS 还是因为 NameNode 的限制,大量小文件可能导致 DataNode 尚有空间时也有可能因为没地方存元数据导致数据无法存储,而对象存储就没有这样的问题
  • 对象存储支持多站点部署(异地备份、多机房备份等),而 HDFS 不支持
  • 低存储开销,HDFS 需要 3 个副本,而对象存储通过 EC 纠删码等方式只增加 20% 额外空间就能有效备份

可以看到云对象存储比起 HDFS 还是有一定优势的

而以前的数据仓库数据湖经常使用 HDFS 作为存储

Iceberg

Apache Iceberg 是一个用于海量数据分析的开源的表格式,相当于一个中间层,使计算和存储解耦分离,支持多种底层存储,支持多种上层计算引擎

https://iceberg.apache.org/
https://github.com/apache/iceberg

功能

  • 支持 Parquet、Orc、S3(Simple Storage Service,AWS)、GCP(Google)、Aliyun 等存储
  • 支持 Spark、Flink、Hive、PrestoDB 等计算引擎
  • 支持 schema 的变更,包括 add、drop、update、rename 等操作
  • 支持数据的 ACID 操作,支持行级数据变更
  • 支持隐式分区 (Hidden Partitioning)
  • 支持分区布局变更 (Partition layout evolution)
  • 支持查询特定版本的数据
  • 支持版本回滚
  • 支持事务
  • 支持 python 和 java

有些功能可能会受引擎制约导致不支持,比如 hive 不支持数据的 update

感觉文档不是很全

Schema 变更

Iceberg 支持 Schema 变更操作

  • Add: 添加新的列到表,或添加新的域到内嵌结构
  • Drop: 从表移除列,或从内嵌结构移除域
  • Rename: 重命名列,或内嵌结构的域
  • Update: 改变列的类型,或内嵌结构的域的类型
  • Reorder: 改变列的排序,或内嵌结构的域的排序

schema 的更改只改变 metadata,数据文件不需要改变

隐式分区和分区布局变更

https://iceberg.apache.org/#partitioning/
https://iceberg.apache.org/#evolution/#partition-evolution

感觉说的不清楚,好像是说了两个事

一是自动管理关联字段,比如 event_time, format_time(event_time, 'YYYY-MM-dd') as event_date,正常说如果 partition 只指定 event_date,那么 where 条件只用到 event_time 的时候不会用到分区机制,而 iceberg 分区会管理这两个字段,只使用 event_time 也可以

二是可以对已有分区的表修改分区配置,并且不需要迁移数据,新旧数据分别存储,查询的时候会产生两个 plan

查询特定版本和版本回滚

查询历史

// time travel to October 26, 1986 at 01:21:00
spark.read
     .option("as-of-timestamp", "499162860000")
     .format("iceberg")
     .load("path/to/table")

// time travel to snapshot with ID 10963874102873L
spark.read
     .option("snapshot-id", 10963874102873L)
     .format("iceberg")
     .load("path/to/table")

回滚到某个时间

CALL catalog_name.system.rollback_to_timestamp('db.sample', TIMESTAMP '2021-06-30 00:00:00.000')

回滚到某个版本

CALL catalog_name.system.set_current_snapshot('db.sample', 1)

https://iceberg.apache.org/#spark-procedures/#rollback_to_timestamp

Iceberg in Spark

iceberg 需要配合计算引擎使用,这里以 spark 为例子

启动时带上 iceberg package 或者把 jar 包放到 spark 的 jar 目录

spark-shell --packages org.apache.iceberg:iceberg-spark3-runtime:0.12.1

添加 catalogs

// 这里创建两个 catalog,其名字分别是 spark_catalog 和 local
// 下面创建表,读写表的操作,都需要指定 catalog 的名字
// 支持两种实现
//   org.apache.iceberg.spark.SparkCatalog : supports a Hive Metastore or a Hadoop warehouse
//   org.apache.iceberg.spark.SparkSessionCatalog : to Spark’s built-in catalog
// type
//   hive or hadoop or left unset if using a custom catalog
// uri
//   hive thrift://host:port or default from hive-site.xml
// warehouse
//   hadoop path like hdfs://nn:8020/warehouse/path

spark-sql --packages org.apache.iceberg:iceberg-spark3-runtime:0.12.1\
    --conf spark.sql.extensions=org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions \
    --conf spark.sql.catalog.spark_catalog=org.apache.iceberg.spark.SparkSessionCatalog \
    --conf spark.sql.catalog.spark_catalog.type=hive \
    --conf spark.sql.catalog.local=org.apache.iceberg.spark.SparkCatalog \
    --conf spark.sql.catalog.local.type=hadoop \
    --conf spark.sql.catalog.local.warehouse=$PWD/warehouse

// hadoop 还可以进一步配置,比如
--conf spark.sql.catalog.hadoop_prod.hadoop.fs.s3a.endpoint = http://aws-local:9000

默认 type 只支持 hive 或 hadoop,如果需要使用其他的比如 aws s3,需要添加额外的包,以及需要使用 custom catalog

# add Iceberg dependency
ICEBERG_VERSION=0.12.1
DEPENDENCIES="org.apache.iceberg:iceberg-spark3-runtime:$ICEBERG_VERSION"

# add AWS dependnecy
AWS_SDK_VERSION=2.15.40
AWS_MAVEN_GROUP=software.amazon.awssdk
AWS_PACKAGES=(
    "bundle"
    "url-connection-client"
)
for pkg in "${AWS_PACKAGES[@]}"; do
    DEPENDENCIES+=",$AWS_MAVEN_GROUP:$pkg:$AWS_SDK_VERSION"
done

# start Spark SQL client shell
spark-sql --packages $DEPENDENCIES \
    --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \
    --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket/my/key/prefix \
    --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.aws.glue.GlueCatalog \
    --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \
    --conf spark.sql.catalog.my_catalog.lock-impl=org.apache.iceberg.aws.glue.DynamoLockManager \
    --conf spark.sql.catalog.my_catalog.lock.table=myGlueLockTable

如果是 JDBC 同样需要用 custom catalog

# uri 是真正存数据的地方,warehouse 是存 iceberg 元数据的地方
spark-sql --packages org.apache.iceberg:iceberg-spark3-runtime:0.12.1 \
    --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \
    --conf spark.sql.catalog.my_catalog.warehouse=s3://my-bucket/my/key/prefix \
    --conf spark.sql.catalog.my_catalog.catalog-impl=org.apache.iceberg.jdbc.JdbcCatalog \
    --conf spark.sql.catalog.my_catalog.uri=jdbc:mysql://test.1234567890.us-west-2.rds.amazonaws.com:3306/default \
    --conf spark.sql.catalog.my_catalog.jdbc.verifyServerCertificate=true \
    --conf spark.sql.catalog.my_catalog.jdbc.useSSL=true \
    --conf spark.sql.catalog.my_catalog.jdbc.user=admin \
    --conf spark.sql.catalog.my_catalog.jdbc.password=pass

使用 spark-sql shell 或 spark.sql(...) 函数创建表和读写数据

建表

CREATE TABLE local.db.table (id bigint, data string) USING iceberg

CREATE TABLE ... PARTITIONED BY
CREATE TABLE ... AS SELECT
ALTER TABLE
DROP TABLE

partition

CREATE TABLE ... PARTITIONED BY years(ts)

years(ts): partition by year
months(ts): partition by month
days(ts) or date(ts): equivalent to dateint partitioning
hours(ts) or date_hour(ts): equivalent to dateint and hour partitioning
bucket(N, col): partition by hashed value mod N buckets
truncate(L, col): partition by value truncated to L

修改表的存储格式

ALTER TABLE prod.db.sample SET TBLPROPERTIES (
    'write.format.default'='orc'
)

默认是 parquet,支持 parquet, avro, orc 等格式

https://iceberg.apache.org/#configuration/
https://iceberg.apache.org/#spark-ddl/#alter-table

写数据

INSERT INTO local.db.table VALUES (1, 'a'), (2, 'b'), (3, 'c');
INSERT INTO local.db.table SELECT id, data FROM source WHERE length(data) = 1;

或使用 dataframe 的 接口

// v2 接口
dataframe.writeTo("local.db.table").append()

// v1 接口
dataframe.write
         .format("iceberg")
         .mode("append")
         .save("local.db.table")

读数据

SELECT count(1) as count, data
FROM local.db.table
GROUP BY data

或使用 dataframe 接口

val df = spark.table("local.db.table")

val df = spark.table("local.db.table").select("id", "data")

使用 streaming 写

data.writeStream
    .format("iceberg")
    .outputMode("append")
    .trigger(Trigger.ProcessingTime(1, TimeUnit.MINUTES))
    .option("path", tableIdentifier)
    .option("checkpointLocation", checkpointPath)
    .start()

https://iceberg.apache.org/#spark-queries/

表格式说明

https://iceberg.apache.org/#spec/

讲了 metadata 文件和 data 文件是如何组织的

https://help.aliyun.com/document_detail/312246.html

和 hive 和 clickhouse 的比较,和适用场景



posted @ 2022-01-25 23:54  moon~light  阅读(1356)  评论(0编辑  收藏  举报