Paimon 数据湖 + Gravitino 元数据中心:技术原理与实战指南
一、概述
Apache Paimon 是一个流批一体的数据湖存储格式,支持高吞吐的数据摄入和高效的实时/批量查询。Apache Gravitino 可以作为 Paimon 的统一元数据中心,通过 lakehouse-paimon Catalog 插件直接对接 Paimon 的元数据层,实现:
- 统一管理 Paimon + 其他数据源(Hive、MySQL、Iceberg、Kafka)的元数据
- 通过 Gravitino 的 Spark/Flink Connector 透明访问 Paimon 表
- 对 Paimon 表应用统一的权限控制、标签、策略治理
二、架构总览
┌─────────────────────────────────────────────────────────┐
│ 查询引擎层 │
│ Spark SQL Flink SQL Trino │
│ (Gravitino (Gravitino (Gravitino │
│ Spark Connector) Flink Connector) Trino Conn.) │
└────────┬──────────────────┬──────────────────┬──────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────┐
│ Gravitino Server (:8090) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Dispatcher Chain │ │
│ │ Event → Normalize → Hook → Operation │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼──────────────────────────┐ │
│ │ CatalogManager │ │
│ │ loadCatalog("my_paimon") → PaimonCatalog │ │
│ └──────────────────────┬──────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼──────────────────────────┐ │
│ │ PaimonCatalogOperations │ │
│ │ → PaimonCatalogOps │ │
│ │ → Paimon CatalogFactory.createCatalog(...) │ │
│ └──────────────────────┬──────────────────────────┘ │
└─────────────────────────┼───────────────────────────────┘
│
┌────────────────┼────────────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Filesystem│ │ JDBC │ │ Hive │
│ (HDFS/ │ │ (MySQL/ │ │Metastore │
│ S3/OSS) │ │ PG) │ │(Thrift) │
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────┐
│ Paimon 数据文件 (ORC/Parquet) │
│ 存储在 HDFS / S3 / OSS / 本地 │
└─────────────────────────────────────────────┘
三、核心源码解析
3.1 Catalog 插件入口 --- PaimonCatalog
文件 : catalogs/catalog-lakehouse-paimon/src/main/java/.../PaimonCatalog.java
java
public class PaimonCatalog extends BaseCatalog<PaimonCatalog> {
@Override
public String shortName() {
return "lakehouse-paimon"; // 创建 Catalog 时 provider 使用此名
}
@Override
protected CatalogOperations newOps(Map<String, String> config) {
return new PaimonCatalogOperations(); // 核心操作类
}
@Override
protected Capability newCapability() {
return new PaimonCatalogCapability(); // 能力声明
}
}
通过 SPI 注册:META-INF/services/org.apache.gravitino.CatalogProvider 中声明了 PaimonCatalog。
3.2 初始化流程 --- PaimonCatalogOperations.initialize()
文件 : catalogs/catalog-lakehouse-paimon/src/main/java/.../PaimonCatalogOperations.java
java
@Override
public void initialize(Map<String, String> conf, CatalogInfo info,
HasPropertyMetadata propertiesMetadata) {
// 1. 提取 gravitino.bypass.* 前缀的透传属性
Map<String, String> prefixMap = MapUtils.getPrefixMap(conf, CATALOG_BYPASS_PREFIX);
// 2. 将 Gravitino 属性转换为 Paimon 属性
// catalog-backend → metastore, warehouse → warehouse, uri → uri 等
Map<String, String> gravitinoConfig =
((PaimonCatalogPropertiesMetadata) propertiesMetadata.catalogPropertiesMetadata())
.transformProperties(conf);
// 3. 合并配置
Map<String, String> resultConf = Maps.newHashMap(prefixMap);
resultConf.putAll(gravitinoConfig);
// 4. 创建 Paimon 底层 Catalog
this.paimonCatalogOps = new PaimonCatalogOps(new PaimonConfig(resultConf));
}
在 PaimonCatalogOps 内部,通过 Paimon 官方 API 创建 Catalog:
java
// CatalogUtils.loadCatalogBackend()
Catalog catalog = CatalogFactory.createCatalog(CatalogContext.create(options));
3.3 Paimon 支持的 Catalog Backend
java
public enum PaimonCatalogBackend {
FILESYSTEM, // 本地/HDFS/S3/OSS 文件系统,元数据存在文件中
JDBC, // MySQL/PostgreSQL 存储元数据
HIVE, // Hive Metastore 存储元数据
REST // REST Catalog(如阿里云 DLF)
}
| Backend | 元数据存储位置 | 适用场景 |
|---|---|---|
| filesystem | warehouse 目录下的文件 | 开发测试、简单部署 |
| jdbc | MySQL / PostgreSQL | 生产环境、多节点共享 |
| hive | Hive Metastore | 与 Hive 生态共存 |
| rest | REST 服务(如阿里云 DLF) | 云原生部署 |
3.4 表操作的内部流程
以 createTable 为例:
用户 REST/SDK 请求
→ Gravitino REST API (TableOperations)
→ EventDispatcher → NormalizeDispatcher → HookDispatcher
→ TableOperationDispatcher
→ CatalogManager.loadCatalog("my_paimon")
→ PaimonCatalog (IsolatedClassLoader)
→ PaimonCatalogOperations.createTable()
→ 1. 构建 GravitinoPaimonTable (Gravitino 模型)
→ 2. toPaimonTableSchema() (转为 Paimon Schema)
→ 3. paimonCatalogOps.createTable(identifier, schema)
→ 4. Paimon Catalog.createTable() (真正创建)
关键转换 : GravitinoPaimonTable.toPaimonTableSchema() 将 Gravitino 的列定义、分区、主键、分桶等信息转换为 Paimon 的 Schema 对象。
3.5 属性映射
| Gravitino 属性 | Paimon 属性 | 说明 |
|---|---|---|
catalog-backend |
metastore |
Catalog 后端类型 |
warehouse |
warehouse |
数仓路径 |
uri |
uri |
后端连接 URI |
jdbc-user |
jdbc.user |
JDBC 用户名 |
jdbc-password |
jdbc.password |
JDBC 密码 |
gravitino.bypass.* |
* |
透传给 Paimon |
四、实战 Demo
4.1 前置条件
- Gravitino Server 已启动(默认
http://localhost:8090) - 已创建 Metalake(如
my_metalake)并启用 - 如使用 HDFS,确保
core-site.xml和hdfs-site.xml放在catalogs/lakehouse-paimon/conf/下
4.2 场景一:Filesystem Backend(开发测试)
创建 Paimon Catalog
bash
curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" -d '{
"name": "paimon_dev",
"type": "RELATIONAL",
"provider": "lakehouse-paimon",
"comment": "Paimon dev catalog on local filesystem",
"properties": {
"catalog-backend": "filesystem",
"warehouse": "file:///tmp/paimon-warehouse"
}
}' http://localhost:8090/api/metalakes/my_metalake/catalogs
创建 Schema
bash
curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" -d '{
"name": "ods",
"comment": "原始数据层"
}' http://localhost:8090/api/metalakes/my_metalake/catalogs/paimon_dev/schemas
创建 Paimon 表(带主键和分区)
bash
curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" -d '{
"name": "user_events",
"comment": "用户行为事件表",
"columns": [
{"name": "event_id", "type": "long", "nullable": false, "comment": "事件ID"},
{"name": "user_id", "type": "long", "nullable": false, "comment": "用户ID"},
{"name": "event_type", "type": "string", "comment": "事件类型"},
{"name": "event_time", "type": "timestamp", "comment": "事件时间"},
{"name": "dt", "type": "string", "nullable": false, "comment": "日期分区"}
],
"partitioning": [
{"strategy": "identity", "fieldName": ["dt"]}
],
"indexes": [
{
"indexType": "primary_key",
"name": "pk_user_events",
"fieldNames": [["event_id"], ["dt"]]
}
],
"properties": {
"merge-engine": "deduplicate",
"sequence.field": "event_time"
}
}' http://localhost:8090/api/metalakes/my_metalake/catalogs/paimon_dev/schemas/ods/tables
查看表详情
bash
curl -s http://localhost:8090/api/metalakes/my_metalake/catalogs/paimon_dev/schemas/ods/tables/user_events \
-H "Accept: application/vnd.gravitino.v1+json" | python3 -m json.tool
4.3 场景二:JDBC Backend(生产环境)
前置:准备 MySQL 元数据库
sql
CREATE DATABASE paimon_metastore;
将 MySQL JDBC 驱动(mysql-connector-java-8.x.jar)放到 catalogs/lakehouse-paimon/libs/ 目录下。
创建 Catalog
bash
curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" -d '{
"name": "paimon_prod",
"type": "RELATIONAL",
"provider": "lakehouse-paimon",
"comment": "Production Paimon catalog with JDBC backend",
"properties": {
"catalog-backend": "jdbc",
"uri": "jdbc:mysql://mysql-host:3306/paimon_metastore",
"warehouse": "hdfs://namenode:9000/data/paimon-warehouse",
"jdbc-user": "paimon",
"jdbc-password": "your_password",
"jdbc-driver": "com.mysql.cj.jdbc.Driver"
}
}' http://localhost:8090/api/metalakes/my_metalake/catalogs
4.4 场景三:Hive Backend(与 Hive 生态共存)
bash
curl -X POST -H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" -d '{
"name": "paimon_hive",
"type": "RELATIONAL",
"provider": "lakehouse-paimon",
"properties": {
"catalog-backend": "hive",
"uri": "thrift://hive-metastore:9083",
"warehouse": "hdfs://namenode:9000/user/hive/warehouse-paimon"
}
}' http://localhost:8090/api/metalakes/my_metalake/catalogs
4.5 Java SDK 使用
java
import org.apache.gravitino.client.GravitinoClient;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.rel.*;
// 1. 创建客户端
GravitinoClient client = GravitinoClient.builder("http://localhost:8090")
.withMetalake("my_metalake")
.build();
// 2. 创建 Paimon Catalog
Map<String, String> props = ImmutableMap.of(
"catalog-backend", "filesystem",
"warehouse", "hdfs://namenode:9000/paimon-warehouse"
);
Catalog catalog = client.createCatalog(
"paimon_dev", Catalog.Type.RELATIONAL,
"lakehouse-paimon", "Dev Paimon", props);
// 3. 创建 Schema
catalog.asSchemas().createSchema("ods", "原始数据层", Collections.emptyMap());
// 4. 创建表
Column[] columns = new Column[] {
Column.of("order_id", Types.LongType.get(), "订单ID", false, false, null),
Column.of("user_id", Types.LongType.get(), "用户ID"),
Column.of("amount", Types.DecimalType.of(10, 2), "金额"),
Column.of("dt", Types.StringType.get(), "日期分区", false, false, null)
};
Transform[] partitions = new Transform[] { Transforms.identity("dt") };
Index[] indexes = new Index[] {
Indexes.primary("pk_orders", new String[][] {{"order_id"}, {"dt"}})
};
catalog.asTableCatalog().createTable(
NameIdentifier.of("ods", "orders"),
columns, "订单表",
ImmutableMap.of("merge-engine", "deduplicate"),
partitions, Distributions.NONE, new SortOrder[0], indexes
);
// 5. 查询表
Table table = catalog.asTableCatalog().loadTable(NameIdentifier.of("ods", "orders"));
System.out.println("表名: " + table.name());
System.out.println("列数: " + table.columns().length);
4.6 Python SDK 使用
python
from gravitino.client import GravitinoClient
from gravitino.api.catalog import Catalog
client = GravitinoClient(uri="http://localhost:8090", metalake_name="my_metalake")
# 创建 Catalog
catalog = client.create_catalog(
name="paimon_dev",
catalog_type=Catalog.Type.RELATIONAL,
provider="lakehouse-paimon",
comment="Dev Paimon",
properties={
"catalog-backend": "filesystem",
"warehouse": "file:///tmp/paimon-warehouse"
}
)
# 创建 Schema
catalog.as_schemas().create_schema("ods", "原始数据层", {})
# 列出表
tables = catalog.as_table_catalog().list_tables(Namespace.of("ods"))
五、通过 Spark 访问 Gravitino 管理的 Paimon
5.1 Spark 配置
bash
spark-sql --master local[*] \
--packages org.apache.gravitino:gravitino-spark-connector-runtime-3.5_2.12:1.2.0 \
--packages org.apache.paimon:paimon-spark-3.5:1.2.0 \
--conf spark.plugins=org.apache.gravitino.spark.connector.plugin.GravitinoSparkPlugin \
--conf spark.sql.gravitino.uri=http://localhost:8090 \
--conf spark.sql.gravitino.metalake=my_metalake \
--conf spark.sql.gravitino.enablePaimonSupport=true
5.2 Spark SQL 操作
sql
-- 切换到 Gravitino 管理的 Paimon Catalog
USE paimon_dev;
-- 创建数据库
CREATE DATABASE IF NOT EXISTS dwd;
USE dwd;
-- 创建 Paimon 表
CREATE TABLE IF NOT EXISTS user_orders (
order_id BIGINT,
user_id BIGINT,
product_name STRING,
amount DECIMAL(10,2),
order_time TIMESTAMP,
dt STRING
) PARTITIONED BY (dt);
-- 写入数据
INSERT INTO user_orders VALUES
(1001, 1, '手机', 5999.00, TIMESTAMP '2025-01-15 10:30:00', '2025-01-15'),
(1002, 2, '笔记本', 8999.00, TIMESTAMP '2025-01-15 11:00:00', '2025-01-15'),
(1003, 1, '耳机', 299.00, TIMESTAMP '2025-01-16 09:00:00', '2025-01-16');
-- 查询
SELECT user_id, SUM(amount) as total_amount
FROM user_orders
WHERE dt = '2025-01-15'
GROUP BY user_id;
-- Schema Evolution
ALTER TABLE user_orders ADD COLUMNS (coupon_id BIGINT);
-- 查看分区
SHOW PARTITIONS user_orders;
-- 删除分区
ALTER TABLE user_orders DROP PARTITION (dt='2025-01-15');
5.3 Spark 内部原理
Spark SQL: USE paimon_dev
→ GravitinoSparkPlugin
→ GravitinoCatalogManager 从 Gravitino 获取 Catalog 列表
→ 发现 paimon_dev (provider=lakehouse-paimon)
→ 注册为 GravitinoPaimonCatalogSpark35
→ PaimonPropertiesConverter 转换属性:
catalog-backend → metastore
warehouse → warehouse
→ 内部创建 Paimon SparkCatalog
→ Spark SQL 操作直接走 Paimon SparkCatalog
六、通过 Flink 访问 Gravitino 管理的 Paimon
6.1 Flink SQL Client 配置
确保 classpath 包含:
gravitino-flink-connector-runtime-1.18_2.12-{version}.jarpaimon-flink-1.18-{version}.jar
6.2 Flink SQL 操作
sql
-- 创建 Gravitino Paimon Catalog
CREATE CATALOG paimon_dev WITH (
'type' = 'gravitino-paimon',
'gravitino.uri' = 'http://localhost:8090',
'gravitino.metalake' = 'my_metalake',
'warehouse' = 'file:///tmp/paimon-warehouse',
'metastore' = 'filesystem'
);
USE CATALOG paimon_dev;
SHOW DATABASES;
-- 创建表
CREATE TABLE ods.click_events (
event_id BIGINT,
user_id BIGINT,
page_url STRING,
click_time TIMESTAMP(3),
PRIMARY KEY (event_id) NOT ENFORCED
);
-- 流式写入
INSERT INTO ods.click_events
SELECT * FROM kafka_source_table;
-- 批量查询
SELECT * FROM ods.click_events;
七、支持的操作和限制
7.1 Schema 操作
| 操作 | 支持 | 备注 |
|---|---|---|
| 创建 Schema | 是 | |
| 删除 Schema | 是 | 支持 cascade |
| 查看 Schema | 是 | |
| 列出 Schema | 是 | |
| 修改 Schema | 否 | Paimon 不支持 |
7.2 表操作
| 操作 | 支持 | 备注 |
|---|---|---|
| 创建表 | 是 | 支持分区、主键、分桶 |
| 查看表 | 是 | |
| 列出表 | 是 | |
| 修改表 | 是 | 支持重命名、加列、改列等 |
| dropTable | 否 | 请使用 purgeTable |
| purgeTable | 是 | 同时删除元数据和数据 |
7.3 支持的 Alter Table 操作
- RenameTable(需单独执行,不能和其他变更混合)
- AddColumn / DeleteColumn / RenameColumn
- UpdateColumnComment / UpdateColumnNullability / UpdateColumnPosition / UpdateColumnType
- UpdateComment(表注释)
- SetProperty / RemoveProperty
7.4 类型映射
| Gravitino 类型 | Paimon 类型 |
|---|---|
| Boolean | Boolean |
| Byte / Short / Integer / Long | TinyInt / SmallInt / Int / BigInt |
| Float / Double | Float / Double |
| Decimal(p,s) | Decimal(p,s) |
| String | VarChar(Integer.MAX_VALUE) |
| VarChar(n) | VarChar(n) |
| FixedChar(n) | Char(n) |
| Date | Date |
| Time§ | Time§ |
| Timestamp§ | LocalZonedTimestamp§ |
| Timestamp_tz§ | Timestamp§ |
| Binary / Fixed | VarBinary / Binary |
| Struct / Map / List | Row / Map / Array |
7.5 限制事项
- 分区仅支持 Identity 类型(不支持 bucket/truncate/year/month 等变换分区)
- 索引仅支持一个主键(不支持二级索引)
- 分桶键必须是主键的子集
- 排序(Sort Orders)不支持
- Spark 目前仅支持 Filesystem + HDFS 后端
- 不支持 MERGE INTO / DELETE / UPDATE / TRUNCATE(Spark/Flink 限制)
八、生产环境建议
8.1 Backend 选择
| 环境 | 推荐 Backend | 原因 |
|---|---|---|
| 本地开发 | filesystem (file://) | 零依赖 |
| 测试环境 | filesystem (HDFS) | 简单可靠 |
| 生产环境 | jdbc (MySQL) | 高可用、多节点共享 |
| Hive 混合部署 | hive | 与 Hive 表共享 Metastore |
| 阿里云 | rest (DLF) | 全托管 |
8.2 推荐配置
properties
# gravitino.conf 相关
gravitino.catalog.cache.evictionIntervalMs=3600000
# Paimon Catalog 属性(通过 gravitino.bypass.* 透传)
gravitino.bypass.table.type=managed
gravitino.bypass.snapshot.num-retained-max=100
gravitino.bypass.snapshot.time-retained=7d
8.3 HDFS 集成
将以下文件放到 ${GRAVITINO_HOME}/catalogs/lakehouse-paimon/conf/:
core-site.xml(HDFS 地址配置)hdfs-site.xml(HDFS HA 配置)- Kerberos 相关配置(如有)