Apache Paimon 这个名字,最近一年多几乎出现在每次数据架构讨论里。它从 Flink Table Store 孵化而来,现在已经独立为 Apache 顶级项目,定位是流式数据湖存储格式。我之所以关注它,是因为它确实能把实时和离线两条链路压缩到同一套存储上:Flink 负责写入和流读,Hive、Spark、Trino 再来做批量分析,底层数据只有一份。这篇文章记录的是我基于 Hive Catalog 模式搭建 Paimon 计算与存储环境的完整过程。为什么强调 Hive Catalog?因为大部分公司的元数据已经挂在 Hive Metastore 上,用这种方式接入 Paimon,现有资产和数据权限体系都不用动。不管你是刚接触 Paimon 的新手,还是已经在调研数据湖方案想找落地参考,这篇内容应该能让你少花几天时间踩坑。
1. 方案动机:为什么一定选 Hive Catalog
1.1 Filesystem Catalog 和 Hive Catalog 的差别
Paimon 支持好几种元数据管理模式,最常被拿来做对比的是 filesystem catalog 与 hive catalog。filesystem 模式下,catalog 的元数据(库表列表、字段信息)直接以 JSON 文件形式存放在 warehouse 根目录下,Flink 读的时候扫描这些文件就能知道有哪些表。这种模式配置非常简单,完全不需要外部依赖,单机验证或者临时测试非常方便。但问题也很明显:整个集群的元数据只对 Flink 自己可见。一旦你的 Spark 任务、Hive 查询、Trino 集群也想访问同一份表,你就得在每套引擎里手工维护一份一样的 catalog 配置,而且这些引擎之间互相都感知不到对方创建的库表变更,整个元数据视图是割裂的。
打个比方,filesystem catalog 就像每个人的通讯录都存在自己手机里,别人想知道你的联系方式,必须你把号码报给他才知道。Hive Catalog 则像公司内部员工通讯录,只要入职(创建表)就自动同步到公共系统,任何部门(引擎)都能通过这同一个系统查到。这个公共系统就是 Hive Metastore。
在真实数仓环境里,HMS 通常已经承载着大量 Hive 外部表、分区的元数据。使用 Hive Catalog 之后,Paimon 表在 HMS 中注册为普通外部表,Hive 的权限管理(比如通过 Ranger 对 HMS 做授权)可以直接作用到 Paimon 表上,这对生产环境非常重要。当你的数据地图、元数据采集工具从 HMS 拉取表清单时,Paimon 表也会和其他表混在一起,对运维人员的认知负担小很多。
1.2 计算引擎与元数据服务如何分工
在我搭的环境里,计算链路由 Flink 承担:通过 Flink SQL 客户端或作业提交创建 Paimon Catalog、建表、执行 INSERT 写入、跑流式查询。Hive Metastore 只做元数据存储与对外发布,不参与数据读写。实际数据文件写到 HDFS 的 warehouse 目录,snapshot 和 manifest 文件管理 Paimon 表的版本与文件清单。后续 Hive 查询、Spark Batch 任务都能绕过 Flink,直接读取底层数据文件。
这个元数据与计算解耦的设计是整套方案的灵魂,也是 Paimon 能对接多引擎的根本原因。数据文件的格式是开放的(ORC/Parquet),谁都能读;关键是表在哪里、有哪些文件、哪些文件属于哪个快照,这些信息只存在于 catalog 和 snapshot 里。Hive Catalog 把第一项交给 HMS 管理,第二项由 Paimon 自身的 commit 机制管理。这样用户不需要去记 Paimon 表的物理路径,直接像查一张普通 Hive 表一样操作。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备:版本选型和依赖清单
2.1 版本兼容矩阵:别在版本上翻车
Paimon 的官方文档里有每一版本对应 Flink 的兼容表格。我这次用的是 Paimon 0.8 + Flink 1.18 + Hive 3.1.3,这个组合验证得最多,网上资料也最齐全,新手照着这个版本搭能少踩很多坑。如果你用的是 Paimon 0.7,Flink 版本最好控制在 1.17 及以下;如果直接用最新版 Paimon 0.9,Flink 支持到 1.19,但社区里相关博客和解决经验少一些,遇到问题只能靠啃源码或翻官方 issue。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | Flink 1.17+ 对两者都兼容,生产环境根据团队基础统一 |
| Hadoop | 3.1.x / 3.2.x | 3.3 也能跑,但部分老组件可能存在兼容警告 |
| Hive | 3.1.x | 这里主要用到 Hive Metastore,元数据建议存 MySQL |
| Flink | 1.17 / 1.18 | 与 Paimon 0.8 搭配最稳 |
| Paimon | 0.8.x | 功能完整,文档详细,适合生产验证 |
2.2 Jar 包清单和放置目录
这块非常容易出错,我先给你一张清单。
| Jar 包 | 作用 | 放置位置 |
|---|---|---|
| paimon-flink-1.18-0.8.x.jar | Flink 引擎与 Paimon 的桥接层 | $FLINK_HOME/lib |
| paimon-hive-connector-3.1-0.8.x.jar | Hive 引擎读取 Paimon 表 | Hive 的 auxlib 目录或运行时 ADD JAR |
| flink-sql-connector-hive-3.1.3_2.12-1.18.x.jar | Flink 连接 Hive Metastore | $FLINK_HOME/lib |
注意,paimon-flink jar 和 paimon-hive-connector jar 不要同时放到 Hive 的 classpath,否则容易出现类冲突。Flink 和 Hive 各自维护自己的 jar 集,Flink 使用 paimon-flink,Hive 使用 paimon-hive-connector。另外,Hadoop 的 classpath 也需要在 Flink 环境里显式声明,否则 HDFS 客户端加载不上。经典解决方式是启动客户端前执行:
bash复制export HADOOP_CLASSPATH=`$HADOOP_HOME/bin/hadoop classpath`
2.3 没有现成 HMS 的最小化启动方案
如果你的环境比较干净,从零开始,最小化方案是先装一个单机 Hive,只启动 Metastore 服务,不需要启动 HiveServer2。
第一步,安装 Hive 3.1.3,把元数据配置到 MySQL。Derby 也可以,但 Derby 是嵌入式数据库,只支持单会话连接,HMS 一启动就占用这个库,后续任何并发访问都会锁表,非常痛苦。MySQL 作为独立服务,HMS 通过 JDBC 访问,性能和并发都好很多。
第二步,修改 hive-site.xml 的关键配置:
xml复制<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://localhost:3306/hive?createDatabaseIfNotExist=true</value>
</property>
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.cj.jdbc.Driver</value>
</property>
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>hive</value>
</property>
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>hive</value>
</property>
<property>
<name>hive.metastore.uris</name>
<value>thrift://hadoop01:9083</value>
</property>
第三步,初始化 schema 并启动:
bash复制$HIVE_HOME/bin/schematool -dbType mysql -initSchema
nohup $HIVE_HOME/bin/hive --service metastore > /tmp/hive-metastore.log 2>&1 &
netstat -tlnp | grep 9083
端口 9083 监听成功,说明 HMS 已经就绪。这个最小化环境足够支撑后续 Paimon 的验证。
3. 核心配置详解:Flink 里如何连通 Hive Catalog
3.1 设置 hive-conf-dir 而不是复制文件
很多人习惯把 hive-site.xml 直接 copy 到 Flink 的 conf 目录,这样 Flink 也能读到。但这种方式处理不好版本更新:每次改 Hive 配置,都得记得同步一份到 Flink 目录,漏了就会出“为什么连接地址还是旧节点”这种诡异问题。我推荐在创建 catalog 时指定 hive-conf-dir 参数,直接指向 Hive 的 conf 目录,让 Flink 运行时自己去读配置文件,逻辑上更符合单一事实来源原则。
但要注意,Flink 的 sql-client 执行时,进程必须有权限读取那个目录。如果 Flink 跑在独立机器上,需要把 hive-site.xml 同步到那台机器上的某个固定目录(比如 /etc/hive/conf),再在 SQL 里指向它。读不到 hive-site.xml 时,HMS 的 uri 就无从解析,连接必然失败。
3.2 创建 Hive Catalog 的完整 SQL
在 Flink SQL 客户端里执行:
sql复制CREATE CATALOG paimon_hive WITH (
'type' = 'paimon',
'metastore' = 'hive',
'uri' = 'thrift://hadoop01:9083',
'warehouse' = 'hdfs://hadoop01:9000/user/paimon/warehouse',
'hive-conf-dir' = '/etc/hive/conf'
);
参数逐个拆开看:
- type='paimon':固定写法,告诉 Flink 这个 catalog 是 Paimon 类型。
- metastore='hive':使用 Hive Metastore 作为 catalog 后端。
- uri:HMS 的 thrift 地址。
- warehouse:Paimon 表数据的根目录,这个路径只对 Paimon 表有效,不会影响 Hive 原有表。
- hive-conf-dir:指向包含 hive-site.xml 的目录。
执行完接着:
sql复制USE CATALOG paimon_hive;
SHOW DATABASES;
如果返回 default,说明已经连上 HMS。如果在 SHOW DATABASES 就报错,优先去排查 HMS 是否在监听、uri 是否写对。
3.3 连接串里的坑:URI 与 hive-site.xml 的优先级
这里有个容易被忽略的点:如果 create catalog 里同时写了 uri 和 hive-conf-dir,最终生效的是哪个?实践来看,Paimon 会优先使用 SQL 里显式传入的 uri;如果没写 uri,则从 hive-site.xml 中读取 hive.metastore.uris。所以排查问题的时候,不要去改 hive-site 然后期望立刻生效,先看你 SQL 里有没有写死 uri。我调试时就遇到过这种“配置改了没用”的情况,最后发现是 SQL 里的 uri 把配置覆盖了。
补充一点:在没有显式 uri 但配置了 hive-conf-dir 时,Paimon 会通过 HiveConf 读取配置。这个特性要求 hive-site.xml 至少包含 hive.metastore.uris,否则依然无法连接。
4. 实操全流程:从空环境到数据落地
4.1 启动顺序和准备
按照依赖关系,顺序是 HDFS、Hive Metastore、Flink SQL 客户端。
bash复制# 1. 启动 HDFS(假设在 NameNode 节点)
start-dfs.sh
# 2. 启动 Hive Metastore
nohup hive --service metastore > /tmp/hms.log 2>&1 &
sleep 5
netstat -tlnp | grep 9083
# 3. 启动 Flink standalone(可选,嵌入式模式也能跑)
$FLINK_HOME/bin/start-cluster.sh
如果只是单机验证,不启动 Flink standalone 也没问题,sql-client embedded 模式默认就是 local 执行,足够演示。进入客户端时我习惯把 checkpoint 间隔一起设置好:
bash复制$FLINK_HOME/bin/sql-client.sh embedded -Dexecution.checkpointing.interval=10s
这里设置 10 秒,后续流式写入时数据会每 10 秒提交一个快照,方便观察流读效果。
4.2 创建 Catalog 并建表
执行完上一节那段 create catalog 之后,直接建表:
sql复制USE CATALOG paimon_hive;
CREATE TABLE ods_order (
order_id BIGINT,
user_id BIGINT,
goods_id BIGINT,
amount DECIMAL(10, 2),
order_time TIMESTAMP(3),
dt STRING,
PRIMARY KEY (order_id) NOT ENFORCED
) PARTITIONED BY (dt)
WITH (
'bucket' = '4',
'changelog-producer' = 'input',
'file.format' = 'orc'
);
几个参数的选型逻辑:
- 主键表:order_id 作为主键,配合 upsert 语义,同一订单的数据多次写入只会保留最新一行,这是 Paimon 区别于 Hive 表的关键能力。
- PARTITIONED BY (dt):按天分区,后续查询如果带 dt 过滤条件,可以直接做分区裁剪。
- bucket=4:每个分区拆成 4 个桶,桶是并发写入和文件组织的单位。桶数越多,并发度越高,但小文件也越多,需要权衡。常见经验是写入并行度 4~8 时,bucket 取 4~8 都能跑得很顺。
- changelog-producer='input':让 Paimon 根据输入数据本身生成 changelog。这样下游流式读取时能拿到完整的 INSERT 和 UPDATE 事件。想用 Paimon 做实时数仓的 ODS 层,这个配置几乎是必须的。
4.3 写入与查询:验证主键更新与分区裁剪
先写入两条数据:
sql复制INSERT INTO ods_order VALUES
(1, 1001, 2001, 99.90, TIMESTAMP '2025-01-01 10:00:00', '2025-01-01'),
(2, 1002, 2002, 199.00, TIMESTAMP '2025-01-01 11:00:00', '2025-01-01');
再更新主键为 1 的那条:
sql复制INSERT INTO ods_order VALUES
(1, 1001, 2001, 88.00, TIMESTAMP '2025-01-01 12:00:00', '2025-01-01');
查询:
sql复制SELECT * FROM ods_order;
期望返回两条,其中 order_id=1 的 amount 是 88.00。这一步能直观看到主键表在存储层发生了真正合并:第二条插入不是简单追加,而是根据 order_id 找到旧数据,在 LSM 树里标记删除旧记录、写入新记录,查询时只返回最新状态。这正是 Paimon 能实现流式 upsert 的核心能力。
分区裁剪验证:
sql复制EXPLAIN SELECT * FROM ods_order WHERE dt = '2025-01-01';
观察执行计划是否只读取了目标分区,这样能确认分区信息已经成功注册到 HMS。
4.4 流式写入验证:用最简单的方式演示实时链路
真实场景里,Paimon 一般配合 Kafka 做实时入仓。这里用一个简化流程演示。
先创建一张随机生成数据的源表:
sql复制CREATE TABLE src (
id BIGINT,
ts TIMESTAMP(3),
dt STRING
) WITH (
'connector' = 'datagen',
'rows-per-second' = '100',
'fields.id.kind' = 'sequence',
'fields.id.start' = '1',
'fields.id.end' = '100000'
);
再把实时数据写入 Paimon 表:
sql复制INSERT INTO ods_order
SELECT id, id % 100, id % 10, id / 10.0, ts, dt FROM src;
由于启动客户端时设置了 checkpoint 间隔 10 秒,Paimon 每 10 秒提交一个 snapshot,底层会不断产生新文件。
另开一个 sql-client,执行流读:
sql复制SET 'execution.checkpointing.interval' = '10s';
SELECT * FROM ods_order /*+ OPTIONS('scan.mode'='latest') */;
这个查询会一直挂着等待新数据。scan.mode=latest 表示只读取快照之后新产生的数据。你会看到它每 10 秒刷一批,这就是 Paimon 的流式读能力,也是很多实时数仓选型时最看重的点。
4.5 在 Hive 侧查询同一张表
到这一步,存储和计算环境已经打通了 Flink 侧。现在验证 Hive 能否直接读。
先把 Paimon 的 Hive connector 放到 Hive 的 classpath,最简单方式是放到 auxlib 目录:
bash复制mkdir -p $HIVE_HOME/auxlib
cp /opt/paimon/paimon-hive-connector-3.1-0
