第1章:Paimon整体架构概览
总览:什么是Paimon?
Apache Paimon是一个创新的湖格式(Lake Format),它赋予Flink和Spark构建**实时湖仓架构(Realtime Lakehouse)**的能力。Paimon的前身是Flink Table Store,融合了LSM(Log-Structured Merge-tree)结构,带来了流式实时更新能力,打破了传统数据仓库批量处理的限制。
简单来说,Paimon = 湖格式 + LSM树 = 流批一体处理。
核心架构设计
架构设计图
元数据管理"] Table["Table
表抽象"] end subgraph "核心存储层" FileStore["FileStore
存储引擎"] subgraph "主键表" KVStore["KeyValueFileStore"] LSM["LSM Tree"] MergeEngine["Merge Engine"] Compaction["Compaction"] end subgraph "追加表" AppendStore["AppendOnlyFileStore"] Clustering["Clustering"] end end subgraph "操作层" Scan["FileStoreScan
扫描"] Write["FileStoreWrite
写入"] Commit["FileStoreCommit
提交"] Read["SplitRead
读取"] end subgraph "元数据层" Snapshot["Snapshot
快照"] Manifest["Manifest
清单"] ManifestList["ManifestList
清单列表"] Schema["Schema
表结构"] end subgraph "文件系统层" HDFS["HDFS"] S3["S3"] OSS["OSS"] LocalFS["Local FS"] end Flink --> Catalog Spark --> Catalog Hive --> Catalog Catalog --> Table Table --> FileStore FileStore --> KVStore FileStore --> AppendStore KVStore --> LSM LSM --> MergeEngine LSM --> Compaction Table --> Scan Table --> Write Table --> Commit Table --> Read Scan --> Manifest Write --> Manifest Commit --> Snapshot Commit --> Manifest Snapshot --> ManifestList ManifestList --> Manifest Manifest --> HDFS Manifest --> S3 Manifest --> OSS Manifest --> LocalFS
写入流程时序图
读取流程时序图
Compaction压缩流程时序图
Snapshot提交流程时序图
manifestList
baseManifestList Snapshot->>Snapshot: 原子写入JSON Snapshot-->>Commit: Snapshot创建成功 Commit->>Lock: unlock() end
核心设计理念与多维解析
1.1 什么是Paimon - Lake Format的设计理念
什么(What):Paimon是什么?
Paimon是一个开源的通用文件格式,用于在云存储(HDFS、S3、OSS等)上构建数据湖。它定义了数据在文件系统上的组织方式,类似于Iceberg和Hudi,但有自己的独特设计。
关键特性对比表:
| 特性 | Paimon | Iceberg | Hudi |
|---|---|---|---|
| 流式更新 | ✓ (LSM优化) | △ (有但不是重点) | ✓ |
| 批处理 | ✓ | ✓ | ✓ |
| Schema演化 | ✓ | ✓ | △ |
| 多引擎支持 | Flink/Spark | 多引擎 | Spark/Hive |
| 设计导向 | 流式实时优先 | 批处理优先 | 写入优化 |
为什么(Why):为什么需要Paimon?
传统的数据处理面临一个难题:流和批的割裂。
场景举例: 你运营一个电商平台,需要:
- 实时处理:用户点击、购买等事件需要立即更新到用户画像表
- 批量处理:每天生成销售报告、计算用户留存率等
传统方案:
- 使用消息队列(Kafka) + 流处理引擎(Flink) → 实时更新
- 使用数据仓库(Hive) + 批处理引擎(Spark) → 离线分析
- 问题:两套系统、两套数据、难以同步
Paimon方案:
- 统一使用Paimon作为存储格式
- Flink可以实时写入和更新数据
- Spark可以读取最新快照进行分析
- 优势:一套系统、一份数据、天然一致性
怎样(How):Paimon如何解决问题?
Paimon通过以下关键设计实现流批统一:
-
LSM树结构:专为流式更新优化
- 写入优化:小批量数据快速写入
- 读取优化:自动压缩合并最新数据
-
快照机制(Snapshot):保证一致性视图
- 每次提交生成一个快照
- 可以基于快照进行批量读取
- 可以从特定快照恢复
-
主键表与追加表:两种表满足不同场景
- 主键表:支持更新删除(Update/Delete)
- 追加表:仅支持追加(Append-Only)
1.2 模块结构与依赖关系
Paimon项目的分层架构:
scss
┌─────────────────────────────────────────────────────────┐
│ 计算引擎集成层 │
│ paimon-flink │ paimon-spark │ paimon-hive │
└────────┬────────┴────────┬────────┴─────────┬─────────┘
│ │ │
┌────────┴─────────────────┴───────────────────┴─────────┐
│ API层(paimon-api) │
│ 提供Table、Catalog等高级接口 │
└────────┬────────────────────────────────────────────┘
│
┌────────┴─────────────────────────────────────────────┐
│ 核心存储层(paimon-core) │
│ FileStore、主键表、追加表、LSM实现 │
└────────┬────────────────────────────────────────────┘
│
┌────────┴────────┬──────────────┬──────────────┐──────┐
│ │ │ │ │
│ 格式层 │ 公共库 │ 文件系统 │代码生成│
│ paimon- │paimon- │paimon- │paimon-│
│ format │common │filesystems │codegen│
│ │ │ │ │
│ (Parquet/ │(数据结构、│(HDFS/S3/ │(性能 │
│ ORC/Lance) │工具类) │ OSS/本地) │优化) │
└─────────────┴──────────┴──────────────┴──────┘
核心模块详解:
| 模块 | 功能 | 核心类 |
|---|---|---|
| paimon-core | 存储引擎核心 | FileStore, FileStoreTable, Snapshot |
| paimon-common | 公共工具和数据结构 | InternalRow, RowType, BinaryRow |
| paimon-format | 文件格式支持 | ParquetFormat, OrcFormat |
| paimon-filesystems | 多文件系统适配 | FileIO接口的多种实现 |
| paimon-flink | Flink集成 | FlinkCatalog, Source, Sink |
| paimon-spark | Spark集成 | SparkCatalog, DataSource |
| paimon-codegen | 代码生成 | 性能优化的序列化/反序列化 |
1.3 核心概念:FileStore、Table、Catalog
三层抽象设计:
元数据管理"] -->|contains| B["Database
数据库"] B -->|contains| C["Table
表"] C -->|uses| D["FileStore
存储引擎"] D -->|manages| E["Files & Metadata
文件与元数据"] style A fill:#e1f5ff style B fill:#fff3e0 style C fill:#f3e5f5 style D fill:#e8f5e9 style E fill:#fce4ec
1.3.1 Catalog(目录/元数据管理)
定义:Paimon的Catalog负责管理所有元数据(数据库、表、Schema等)。
关键接口方法:
scss
Catalog {
// 数据库操作
createDatabase(name)
dropDatabase(name)
listDatabases()
getDatabase(name)
// 表操作
createTable(identifier, schema)
dropTable(identifier)
getTable(identifier)
listTables(database)
// Schema操作
alterTable(identifier, schemaChanges)
getSchema(identifier)
}
实现类:
FileSystemCatalog:基于文件系统存储元数据(最常用)HiveCatalog:使用Hive Metastore存储元数据CachingCatalog:在其他Catalog基础上添加缓存
场景示例:
less
// 创建Catalog
Catalog catalog = CatalogFactory.createCatalog(
"filesystem",
options // {warehouse: /data/warehouse}
);
// 获取表
Table table = catalog.getTable(Identifier.create("db", "users"));
// 修改Schema
catalog.alterTable(
Identifier.create("db", "users"),
SchemaChange.addColumn("new_col", DataTypes.STRING())
);
1.3.2 Table(表抽象)
定义:Table是用户与Paimon交互的主接口,提供读写能力。
核心方法:
java
Table {
// 读取操作
TableScan newScan()
TableRead newRead()
// 写入操作
TableWrite newWrite(user)
// 提交操作
TableCommit newCommit(user)
// 元数据
TableSchema schema()
List<String> primaryKeys()
List<String> partitionKeys()
}
两种表类型:
| 类型 | 特点 | 支持操作 | 使用场景 |
|---|---|---|---|
| 主键表 | 有主键定义 | Insert/Update/Delete | 用户表、订单表、维度表 |
| 追加表 | 无主键定义 | Insert Only | 日志表、事件表、归档表 |
场景示例:
java
// 主键表 - 用户表
Table usersTable = catalog.getTable(
Identifier.create("db", "users") // pk: user_id
);
// 可以进行更新
TableWrite write = usersTable.newWrite("app");
write.write(row); // 如果user_id已存在,则更新
write.prepareCommit();
// 追加表 - 日志表
Table logsTable = catalog.getTable(
Identifier.create("db", "logs") // no primary key
);
// 仅支持追加
logsTable.newWrite("app").write(row); // 必然是新增
1.3.3 FileStore(存储引擎)
定义:FileStore是实际管理数据文件的存储引擎,负责读写、压缩、管理。
核心接口:
java
FileStore<T> {
// 扫描操作
FileStoreScan newScan()
// 读写操作
SplitRead<T> newRead()
FileStoreWrite<T> newWrite(user)
FileStoreCommit newCommit(user)
// 管理操作
SnapshotManager snapshotManager()
ChangelogManager changelogManager()
// 配置
CoreOptions options()
BucketMode bucketMode()
}
实现类:
KeyValueFileStore:主键表的存储实现(使用LSM树)AppendOnlyFileStore:追加表的存储实现
工作流程:
写入流程:
用户数据 → FileStoreWrite → 内存缓冲 → 文件输出 → Manifest元数据 → Snapshot快照
读取流程:
Snapshot → Manifest → 文件列表 → FileStoreScan → Split生成 → SplitRead → 用户数据
场景示例:
java
FileStoreTable table = (FileStoreTable)
catalog.getTable(Identifier.create("db", "orders"));
// 获取存储引擎
FileStore store = table.store();
// 创建扫描器
FileStoreScan scan = store.newScan();
List<Split> splits = scan.plan().splits;
// 创建读取器
SplitRead read = store.newRead();
for (Split split : splits) {
RecordReader reader = read.createReader(split);
// 读取数据
}
// 创建写入器
FileStoreWrite write = store.newWrite("app");
write.write(record);
1.4 两种表类型:主键表 vs 追加表
对比与选择
指定Primary Key"] -->|有主键| B["主键表
KeyValueFileStore"] A -->|无主键| C["追加表
AppendOnlyFileStore"] B -->|支持| B1["Insert/Update/Delete
完整DML"] B -->|使用| B2["LSM Tree
流式优化"] B -->|适用| B3["实时业务数据
高并发更新"] C -->|支持| C1["Insert Only
仅追加"] C -->|使用| C2["简单追加结构
批处理优化"] C -->|适用| C3["日志/事件数据
顺序写入"]
主键表详解
定义:具有唯一主键约束的表,支持完整的DML操作(Insert/Update/Delete)。
核心特性:
- 自动去重:同一主键的多次写入只保留最新版本
- 支持更新:可以修改已存在的记录
- 支持删除:可以删除记录
- LSM优化:使用LSM树加速流式写入
Schema定义:
java
// 创建主键表
Schema schema = new Schema(
fields(
field(0, "user_id", DataTypes.INT()), // ← 主键字段1
field(1, "name", DataTypes.VARCHAR(50)),
field(2, "age", DataTypes.INT()),
field(3, "balance", DataTypes.DECIMAL(10, 2))
),
partitionKeys("region"), // 可选:分区键
primaryKeys("user_id") // ← 定义主键
);
catalog.createTable(
Identifier.create("db", "users"),
schema
);
工作原理:
当你写入同一用户的多条记录时:
ini
时刻1: 写入 (user_id=1, name='Alice', age=25, balance=1000)
时刻2: 写入 (user_id=1, name='Alice', age=26, balance=1500) ← 年龄更新
时刻3: 写入 (user_id=1, name='Alice', age=27, balance=2000) ← 年龄和余额更新
最终查询结果: (user_id=1, name='Alice', age=27, balance=2000)
← 自动保留最新版本
实时场景应用:
java
// Flink流处理:实时更新用户余额
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<RowData> source = env
.fromSource(kafkaSource, ...) // 从Kafka读取用户事件
.name("Kafka Source");
// 每条事件都可能更新用户表的某个字段
source.sinkTo(new PaimonSink(table));
env.execute();
// 同时,可以用Spark进行实时查询
Spark.sql("SELECT * FROM db.users WHERE age > 25")
追加表详解
定义:无主键约束的表,仅支持追加操作(Append-Only)。
核心特性:
- 追加仅:只能新增记录,不能更新或删除
- 简单高效:不需要去重,直接写入
- 批处理优化:专为顺序扫描优化
Schema定义:
java
// 创建追加表 - 注意没有primaryKeys参数
Schema schema = new Schema(
fields(
field(0, "event_id", DataTypes.LONG()),
field(1, "user_id", DataTypes.INT()),
field(2, "event_type", DataTypes.VARCHAR(50)),
field(3, "timestamp", DataTypes.TIMESTAMP(3)),
field(4, "payload", DataTypes.STRING())
),
partitionKeys("dt") // 按日期分区
// primaryKeys() 不指定!
);
catalog.createTable(
Identifier.create("db", "events"),
schema
);
工作原理:
css
日志表写入流程(无去重):
事件1: {event_id: 1, user_id: 1, event_type: 'click', ...}
事件2: {event_id: 2, user_id: 1, event_type: 'click', ...} ← 重复的事件也会保存
事件3: {event_id: 3, user_id: 2, event_type: 'purchase', ...}
查询结果: 包含所有3条记录(不去重)
日志场景应用:
java
// 处理日志流
DataStream<Event> eventStream = env
.fromSource(kafkaSource, ...)
.name("Event Stream");
// 直接写入追加表,无须担心重复
eventStream
.map(event -> convertToRowData(event))
.sinkTo(new PaimonSink(logTable));
// 批量分析:统计用户行为
Spark.sql("""
SELECT
user_id,
event_type,
COUNT(*) as count
FROM db.events
WHERE dt >= '2024-01-01'
GROUP BY user_id, event_type
""")
选择指南
| 需求 | 主键表 | 追加表 |
|---|---|---|
| 需要更新记录 | ✓ | ✗ |
| 需要删除记录 | ✓ | ✗ |
| 去重要求 | ✓ | ✗ |
| 高频实时更新 | ✓ | △ |
| 仅追加日志 | ✗ | ✓ |
| 历史全量保留 | ✗ | ✓ |
| 存储空间 | 需去重/压缩 | 节省(直接追加) |
| 查询性能 | 需扫描多版本 | 高(顺序扫描) |
总结:Paimon架构的核心价值
关键设计创新:
-
流批一体化
- LSM树为流式写入优化
- Snapshot为批量读取提供一致视图
- 同一份数据既能流处理又能批处理
-
灵活的表类型
- 主键表处理业务数据的变化
- 追加表保存历史日志的完整性
- 一个框架满足两种场景
-
多层次抽象
- Catalog管理元数据生命周期
- Table提供友好的操作接口
- FileStore是物理存储实现
-
多引擎支持
- Flink:实时流处理
- Spark:批量离线分析
- Hive/Presto:OLAP查询
实际应用场景:
场景1:电商实时数据仓库
scss
用户行为日志(Kafka)
↓
Flink实时处理 → 更新用户表(主键表)
↓
Paimon(统一存储)
↓
Spark日间分析 + 实时查询
场景2:日志去重与分析
markdown
原始日志流(多个来源)
↓
Flink去重 → Paimon追加表
↓
Spark/Hive统计分析
场景3:流式机器学习特征
markdown
用户实时特征更新 → Paimon主键表
↓
模型训练系统 → 定期生成snapshot
↓
在线推理 + 离线评估
思考题
-
为什么Paimon选择LSM树而不是其他结构?
- 提示:考虑流式小批量写入的特点
-
主键表和追加表可以在同一个Catalog中共存吗?
- 答案:可以,不同表可以有不同配置
-
Paimon比Hive的优势在哪里?
- 提示:考虑实时性、更新能力、成本等方面