4、环境搭建:Spark + Iceberg + Hive Metastore + MinIO 本地开发环境部署、Catalog配置详解

说实话,很多初学者在 Iceberg 上栽跟头,不是因为概念多难,而是环境没搭对。我自己早期也踩过不少坑——明明代码逻辑没问题,就是连不上 Catalog,或者写入时报错找不到文件。所以这一章,咱们把环境彻底捋清楚。

我会带你从零搭建一套完整的本地开发环境:Spark + Iceberg + Hive Metastore + MinIO。这套组合是目前生产中最常见的轻量级方案之一。你跟着走一遍,后面所有章节的实操都能跑起来。

4.1 整体架构概览

先看一张图,搞清楚每个组件的位置和作用。

Spark + Iceberg + Hive Metastore + MinIO 架构图 Spark SQL / DataFrame Iceberg Spark Runtime Iceberg Catalog Hive Metastore MinIO (S3 兼容) 说明: • Spark 通过 Iceberg Spark Runtime 读写 Iceberg 表 • Iceberg Catalog 连接 Hive Metastore 存储表元数据 • 数据文件实际存储在 MinIO(S3 兼容对象存储)中 • 这套架构完全本地可部署,无需云服务

说白了,Spark 是计算引擎,Iceberg 是表格式,Hive Metastore 管元数据,MinIO 存数据文件。我习惯把 Hive Metastore 比作「通讯录」——它不存数据本身,只记录表在哪儿、有哪些文件。

4.2 组件版本选择

版本选不对,后面全是坑。我列一个经过验证的版本组合,你照着来就行。

组件 推荐版本 说明
Apache Spark 3.4.x Iceberg 1.4.x 对 Spark 3.4 支持最好
Apache Iceberg 1.4.3 稳定版,功能完整
Hive Metastore 3.1.3 独立部署,不需要 Hive Server
MinIO RELEASE.2024-01-16 最新稳定版即可
Hadoop 3.3.6 Hive Metastore 依赖

我的建议:不要追最新版。Iceberg 1.5.x 虽然新,但有些 API 变了,社区文档还没跟上。1.4.3 足够你学完整个课程。

4.3 MinIO 部署与配置

MinIO 是兼容 S3 协议的对象存储。本地开发用它模拟 AWS S3,完全免费。

4.3.1 启动 MinIO

用 Docker 启动最简单:

docker run -d \
  --name minio \
  -p 9000:9000 \
  -p 9001:9001 \
  -e MINIO_ROOT_USER=admin \
  -e MINIO_ROOT_PASSWORD=admin123 \
  -v /data/minio:/data \
  minio/minio:RELEASE.2024-01-16T22-44-24Z \
  server /data --console-address ":9001"

启动后访问 http://localhost:9001,用 admin/admin123 登录。创建一个 bucket,名字叫 iceberg-warehouse。

注意:生产环境别用 admin/admin123。本地开发无所谓,但养成好习惯——用复杂密码。

4.3.2 生成 Access Key

在 MinIO Console 的「Access Keys」页面创建一个 key。记下 access_key 和 secret_key,后面配置要用。

4.4 Hive Metastore 部署

Hive Metastore 是整个架构的「元数据中心」。Iceberg 通过它来管理表结构、分区信息、快照等。

4.4.1 下载与配置

我建议用 Apache Hive 3.1.3 的 standalone metastore 包:

wget https://downloads.apache.org/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz
tar -xzf apache-hive-3.1.3-bin.tar.gz
cd apache-hive-3.1.3-bin

编辑 conf/hive-site.xml:

<configuration>
  <property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:derby:;databaseName=/data/metastore_db;create=true</value>
  </property>
  <property>
    <name>metastore.warehouse.dir</name>
    <value>s3a://iceberg-warehouse/</value>
  </property>
  <property>
    <name>fs.s3a.access.key</name>
    <value>你的access_key</value>
  </property>
  <property>
    <name>fs.s3a.secret.key</name>
    <value>你的secret_key</value>
  </property>
  <property>
    <name>fs.s3a.endpoint</name>
    <value>http://localhost:9000</value>
  </property>
  <property>
    <name>fs.s3a.path.style.access</name>
    <value>true</value>
  </property>
</configuration>

关键点:metastore.warehouse.dir 要指向 MinIO 的 bucket 路径。格式是 s3a://bucket-name/,不是 s3://。Hadoop 的 S3A 文件系统才能识别。

4.4.2 初始化与启动

# 初始化 Derby 数据库
bin/schematool -initSchema -dbType derby

# 启动 Metastore 服务
bin/hive --service metastore &

默认监听 localhost:9083。用 netstat -an | grep 9083 确认一下。

嗯,这里要注意:Derby 是单用户数据库,只适合本地开发。生产环境请换成 MySQL 或 PostgreSQL。

4.5 Spark + Iceberg 集成

这是最核心的一步。Spark 需要加载 Iceberg 的依赖包,并配置 Catalog。

4.5.1 下载 Iceberg Spark Runtime

我习惯把 jar 包放在 Spark 的 jars/ 目录下:

wget https://repo1.maven.org/maven2/org/apache/iceberg/iceberg-spark-runtime-3.4_2.12/1.4.3/iceberg-spark-runtime-3.4_2.12-1.4.3.jar
cp iceberg-spark-runtime-3.4_2.12-1.4.3.jar $SPARK_HOME/jars/

还需要 Hadoop S3A 的依赖:

wget https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/3.3.6/hadoop-aws-3.3.6.jar
cp hadoop-aws-3.3.6.jar $SPARK_HOME/jars/

4.5.2 启动 Spark Shell 验证

启动时指定 Catalog 配置:

$SPARK_HOME/bin/spark-shell \
  --conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \
  --conf spark.sql.catalog.my_catalog.type=hive \
  --conf spark.sql.catalog.my_catalog.uri=thrift://localhost:9083 \
  --conf spark.sql.catalog.my_catalog.warehouse=s3a://iceberg-warehouse/ \
  --conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO \
  --conf spark.hadoop.fs.s3a.access.key=你的access_key \
  --conf spark.hadoop.fs.s3a.secret.key=你的secret_key \
  --conf spark.hadoop.fs.s3a.endpoint=http://localhost:9000 \
  --conf spark.hadoop.fs.s3a.path.style.access=true

进入 Spark Shell 后,执行:

spark.sql("CREATE DATABASE IF NOT EXISTS my_catalog.my_db")
spark.sql("USE my_catalog.my_db")
spark.sql("CREATE TABLE IF NOT EXISTS test (id INT, name STRING) USING iceberg")
spark.sql("INSERT INTO test VALUES (1, 'Alice'), (2, 'Bob')")
spark.sql("SELECT * FROM test").show()

如果能看到数据,说明环境通了。

避坑指南:我曾经因为 io-impl 没配,导致写入时报 FileNotFoundException。Iceberg 默认用 HDFS 的 FileIO,连 MinIO 必须显式指定 S3FileIO。

4.6 Catalog 配置详解

Catalog 是 Iceberg 的「入口」。它决定了表元数据存在哪里、怎么查。Iceberg 支持多种 Catalog 类型,咱们重点讲三种。

Catalog 类型 元数据存储 适用场景
hive Hive Metastore 生产最常用,与 Hive 生态兼容
hadoop 文件系统(HDFS/S3) 轻量级,不需要 Hive Metastore
rest REST 服务 多引擎共享,云原生场景

4.6.1 Hive Catalog 配置

刚才咱们用的就是 Hive Catalog。核心参数:

  • type=hive:指定 Catalog 类型
  • uri=thrift://localhost:9083:Hive Metastore 地址
  • warehouse=s3a://iceberg-warehouse/:数据文件存储路径
  • io-impl=org.apache.iceberg.aws.s3.S3FileIO:文件 IO 实现

我的经验:Hive Catalog 的 uri 一定要写 thrift:// 前缀。我见过有人只写了 localhost:9083,结果一直连不上。

4.6.2 Hadoop Catalog 配置

如果你不想部署 Hive Metastore,可以用 Hadoop Catalog。元数据直接以文件形式存在 S3 上:

--conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \
--conf spark.sql.catalog.my_catalog.type=hadoop \
--conf spark.sql.catalog.my_catalog.warehouse=s3a://iceberg-warehouse/ \
--conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO

说白了,Hadoop Catalog 就是「无中心化」的方案。适合单机测试或小团队使用。但多引擎并发操作时,容易产生元数据冲突。

4.6.3 REST Catalog 配置

REST Catalog 是 Iceberg 社区主推的云原生方案。它通过 REST API 统一管理元数据,Spark、Flink、Trino 都能连同一个 Catalog 服务。

--conf spark.sql.catalog.my_catalog=org.apache.iceberg.spark.SparkCatalog \
--conf spark.sql.catalog.my_catalog.type=rest \
--conf spark.sql.catalog.my_catalog.uri=http://localhost:8080/iceberg/ \
--conf spark.sql.catalog.my_catalog.warehouse=s3a://iceberg-warehouse/ \
--conf spark.sql.catalog.my_catalog.io-impl=org.apache.iceberg.aws.s3.S3FileIO

注意:REST Catalog 需要额外部署 Iceberg REST Server。咱们课程后面会专门讲。本地开发阶段,用 Hive Catalog 就够了。

4.7 验证环境完整性

环境搭完,我习惯跑一套完整的「冒烟测试」:

  1. 创建表并写入数据
  2. 查询数据
  3. 执行一次快照查询(time travel)
  4. 检查 MinIO 上是否有数据文件

在 Spark Shell 里执行:

// 1. 创建表
spark.sql("CREATE TABLE my_catalog.my_db.orders (order_id INT, amount DOUBLE, ts TIMESTAMP) USING iceberg PARTITIONED BY (days(ts))")

// 2. 写入数据
spark.sql("INSERT INTO my_catalog.my_db.orders VALUES (1, 100.0, current_timestamp()), (2, 200.0, current_timestamp())")

// 3. 查询
spark.sql("SELECT * FROM my_catalog.my_db.orders").show()

// 4. 查看快照
spark.sql("SELECT * FROM my_catalog.my_db.orders.snapshots").show()

然后去 MinIO Console 看看 iceberg-warehouse bucket 下有没有生成 my_db/orders 目录。如果有 metadata 和 data 子目录,说明一切正常。

一个小技巧:如果写入后查不到数据,先检查 Spark 日志里有没有 S3FileIO 相关的报错。八成是 S3A 配置不对。

4.8 常见问题与排查

我把这些年遇到的环境问题整理了一下:

现象 原因 解决
连不上 Hive Metastore 端口没开或 thrift 协议不对 检查 thrift:// 前缀和 9083 端口
写入报 FileNotFoundException 没有指定 S3FileIO 添加 io-impl 配置
查询返回空结果 Catalog 没切换或表不存在 用 USE my_catalog.my_db 切换
MinIO 上没数据 warehouse 路径配置错误 检查 s3a:// 格式和 bucket 名称

嗯,环境搭建这部分确实琐碎,但它是后面所有实操的基础。你花半小时把环境跑通,后面学起来会顺畅很多。