数据湖是大数据时代的核心架构,Hudi和Iceberg是两大主流数据湖解决方案。
本文系统介绍数据湖的核心概念、Hudi和Iceberg的对比分析和实战应用,帮助你构建现代化数据平台。
数据湖架构:
┌─────────────────────────────────────────────────────────────┐
│ 数据源层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ 关系型数据库 │ │ 日志文件 │ │ 实时流数据 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 数据湖存储层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Hudi │ │ Iceberg │ │ Delta Lake │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 计算层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Spark │ │ Flink │ │ Trino/Presto │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ 应用层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ BI报表 │ │ 数据分析 │ │ 机器学习 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
| 特性 | 数据湖 | 数据仓库 |
|---|
| 数据类型 | 结构化、半结构化、非结构化 | 结构化 |
| Schema | 读时Schema | 写时Schema |
| 存储成本 | 低 | 高 |
| 灵活性 | 高 | 中 |
| ACID | 支持 | 支持 |
Hudi核心能力:
1. 事务支持
├─ ACID事务
├─ 原子写入
└─ 读已提交隔离
2. 增量处理
├─ 增量查询
├─ CDC支持
└─ 实时入湖
3. 数据管理
├─ Upsert
├─ 删除
└─ 合并
-- Copy-on-Write表
CREATE TABLE hudi_cow (
id bigint,
name string,
ts bigint
) USING hudi
OPTIONS (
'hoodie.datasource.write.recordkey.field' = 'id',
'hoodie.table.type' = 'COPY_ON_WRITE'
);
-- Merge-on-Read表
CREATE TABLE hudi_mor (
id bigint,
name string,
ts bigint
) USING hudi
OPTIONS (
'hoodie.datasource.write.recordkey.field' = 'id',
'hoodie.table.type' = 'MERGE_ON_READ'
);
-- 插入数据
INSERT INTO hudi_cow VALUES (1, 'Alice', 1000);
-- 更新数据
MERGE INTO hudi_cow AS target
USING (SELECT 1 as id, 'Bob' as name, 2000 as ts) AS source
ON target.id = source.id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
-- 增量查询
SELECT * FROM hudi_cow
WHERE _hoodie_commit_time > '2026-09-01 00:00:00';
Iceberg核心能力:
1. 快照隔离
├─ 读写不阻塞
├─ 时间旅行
└─ 版本回滚
2. Schema演进
├─ 添加列
├─ 删除列
└─ 重命名列
3. 分区演进
├─ 修改分区策略
└─ 无需重写数据
-- 创建Iceberg表
CREATE TABLE iceberg_table (
id bigint,
name string,
ts timestamp
) USING iceberg
PARTITIONED BY (days(ts));
-- 插入数据
INSERT INTO iceberg_table VALUES (1, 'Alice', current_timestamp());
-- 时间旅行查询
SELECT * FROM iceberg_table VERSION AS OF '2026-09-01 00:00:00';
-- 快照回滚
CALL iceberg.system.rollback_to_snapshot('db.table', snapshot_id);
-- Schema演进
ALTER TABLE iceberg_table ADD COLUMN age int;
ALTER TABLE iceberg_table RENAME COLUMN name TO user_name;
| 特性 | Hudi | Iceberg |
|---|
| ACID事务 | 支持 | 支持 |
| 增量查询 | 强 | 弱 |
| Schema演进 | 支持 | 强 |
| 分区演进 | 不支持 | 支持 |
| 时间旅行 | 支持 | 强 |
| 实时入湖 | 强 | 中 |
选择策略:
1. 是否需要实时入湖?
├─ 是 → Hudi
└─ 否 → 继续
2. 是否需要频繁Schema演进?
├─ 是 → Iceberg
└─ 否 → 继续
3. 是否需要时间旅行?
├─ 是 → Iceberg
└─ 否 → 继续
4. 社区活跃度?
├─ Hudi → 强
└─ Iceberg → 强
实时数仓架构:
┌─────────────────────────────────────────────────────────────┐
│ 数据源: MySQL Binlog → Debezium → Kafka │
├─────────────────────────────────────────────────────────────┤
│ 实时层: Flink → Hudi (ODS/DWD) │
├─────────────────────────────────────────────────────────────┤
│ 离线层: Spark → Iceberg (DWS/ADS) │
├─────────────────────────────────────────────────────────────┤
│ 服务层: Trino查询 → API → BI报表 │
└─────────────────────────────────────────────────────────────┘
// Flink写入Hudi
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Order> orders = env.addSource(new KafkaSource<>());
orders.writeTo(HudiSink.builder()
.withTableName("orders")
.withRecordKey("order_id")
.withPreCombineField("ts")
.build());
env.execute("Real-time orders to Hudi");
-- 创建Iceberg目录
CREATE DATABASE iceberg_db;
-- 创建外表
CREATE EXTERNAL TABLE iceberg_db.orders (
order_id bigint,
user_id bigint,
amount decimal(10,2),
order_time timestamp
) USING iceberg
LOCATION 's3://data-lake/orders';
-- 查询
SELECT user_id, sum(amount) as total_amount
FROM iceberg_db.orders
WHERE order_time >= '2026-09-01'
GROUP BY user_id;
| 原则 | 说明 |
|---|
| 分区策略 | 按时间分区,避免过多分区 |
| 文件大小 | 控制在128MB-1GB |
| 压缩格式 | 使用Parquet格式 |
| 元数据管理 | 完善的数据目录 |
| 问题 | 原因 | 解决方案 |
|---|
| 小文件 | 频繁写入 | 合并文件 |
| 查询慢 | 分区不合理 | 优化分区策略 |
| 数据不一致 | 并发写入 | 事务控制 |