环境准备
Spark 作业使用 Hudi 前需引入 Hudi Spark Bundle(含 DataSource 与 SQL 扩展),并启用 Hudi 的 SQL 扩展与 Catalog。以 spark-submit 提交作业为例,配置方式如下:
spark-submit \
--master yarn --deploy-mode client \
--jars /usr/local/spark/jars/hudi-spark-bundle-1.2.0-1.0.0.jar \
--conf spark.sql.extensions=org.apache.spark.sql.hudi.HoodieSparkSessionExtension \
--conf spark.sql.catalog.spark_catalog=org.apache.spark.sql.hudi.catalog.HoodieCatalog \
--conf spark.sql.catalog.spark_catalog.impl=org.apache.spark.sql.hudi.catalog.HoodieCatalog \
<application-jar>交互式场景使用spark-sql 客户端时,启动命令携带相同参数:
spark-sql \
--conf 'spark.serializer=org.apache.spark.serializer.KryoSerializer' \
--conf 'spark.sql.extensions=org.apache.spark.sql.hudi.HoodieSparkSessionExtension' \
--conf 'spark.sql.catalog.spark_catalog=org.apache.spark.sql.hudi.catalog.HoodieCatalog' \
--conf 'spark.hadoop.spark.sql.legacy.parquet.nanosAsLong=false'各参数含义说明如下表所示:
| 参数 | 说明 |
|---|---|
| spark.serializer=KryoSerializer | 启用Kryo 序列化,提升 shuffle 与 Hudi 写入性能 |
| spark.sql.extensions=org.apache.spark.sql.hudi.HoodieSparkSessionExtension | 启用Hudi SQL 扩展,支持 MERGE INTO、UPDATE、DELETE 等语法 |
| spark.sql.catalog.spark_catalog=org.apache.spark.sql.hudi.catalog.HoodieCatalog | 将默认catalog 切换为 Hudi Catalog,支持 USING hudi 建表 |
| spark.hadoop.spark.sql.legacy.parquet.nanosAsLong=false | 关闭Parquet 时间纳秒到长整型的旧式转换,保持时间语义正确 |
说明
若集群已将上述配置写入spark-defaults.conf,提交时无需重复指定。
注意
使用Hudi 写入时必须关闭 Spark 推测执行,并关闭列默认值特性,否则写入会直接失败:
--conf spark.speculation=false \
--conf spark.sql.defaultColumn.enabled=false \两个参数的强制原因如下表所示(依据Hudi 1.2.0 源码):
| 参数 | 强制值 | 原因 |
|---|---|---|
| spark.speculation | false | 推测执行会对慢Task 启动备份实例并行写同一数据,导致重复写入与数据损坏。Hudi 1.2.0 默认开启写保护(hoodie.block.writes.on.speculative.execution 默认 true),检测到推测执行开启时抛出 HoodieException 拒绝写入 |
| spark.sql.defaultColumn.enabled | false | Spark 3.4 该参数默认 true(允许建表/加列时指定列默认值并在 INSERT 时自动注入)。与 Hudi 的 INSERT INTO 指定列语法冲突,报 "hudi not support specified cols when enable default columns",需显式关闭 |
创建表
使用Spark SQL 创建 Hudi 表,通过 TBLPROPERTIES 指定表类型、主键与预合并字段:
CREATE TABLE hudi_db.orders (
order_id STRING,
order_date STRING,
amount DOUBLE,
ts BIGINT
)
USING hudi
PARTITIONED BY (order_date)
TBLPROPERTIES (
type = 'cow',
primaryKey = 'order_id',
preCombineField = 'ts'
);主要建表属性说明如下表所示:
| 属性 | 说明 |
|---|---|
| type | 表类型:cow(默认)或 mor,建表后不可更改 |
| primaryKey | 记录主键,对应hoodie.datasource.write.recordkey.field,多个字段通过逗号分隔,建议显式指定业务键 |
| preCombineField | 预合并字段,对应hoodie.datasource.write.precombine.field,同一批次主键重复时保留该字段值最大的记录 |
| hoodie.datasource.write.partitionpath.field | 分区字段,通过PARTITIONED BY 子句指定 |
| hoodie.datasource.hive_sync.enable | 是否同步元数据至Hive Metastore,由平台建表流程统一控制 |
写入数据
DataSource API 方式:通过 df.write.format("hudi") 写入,关键写入参数(option)说明如下表所示:
| 参数 | 默认值 | 说明 |
|---|---|---|
| hoodie.datasource.write.table.type | cow | 表类型:COPY_ON_WRITE / MERGE_ON_READ |
| hoodie.datasource.write.operation | upsert | 写入操作类型,见下表 |
| hoodie.datasource.write.recordkey.field | 无 | 记录主键字段 |
| hoodie.datasource.write.partitionpath.field | 无 | 分区路径字段 |
| hoodie.datasource.write.precombine.field | 无 | 预合并字段 |
| hoodie.datasource.write.table.name | 无 | 表名 |
支持的写入操作类型(hoodie.datasource.write.operation 取值)如下表所示:
| 操作 | 说明 |
|---|---|
| upsert | 默认操作,按主键插入或更新(COW 重写文件组,MOR 追加日志) |
| insert | 仅插入,对已存在主键的处理由插入策略决定 |
| bulk_insert | 大批量初始导入,磁盘写路径,避免缓存输入,适合首次建表灌数 |
| insert_overwrite | 覆盖指定分区数据 |
| insert_overwrite_table | 覆盖整表数据 |
| delete | 按主键删除记录 |
| delete_partition | 删除整个分区 |
| bootstrap | 将已有非Hudi 表无拷贝地引导为 Hudi 表 |
DataSource API 写入示例(<路径> 为 Hudi 表存储根目录,表路径按 <路径>/<数据库>/<表名> 组织,实际取值以集群配置为准):
inputDF.write.format("hudi") \
.option("hoodie.datasource.write.table.name", "orders") \
.option("hoodie.datasource.write.recordkey.field", "order_id") \
.option("hoodie.datasource.write.partitionpath.field", "order_date") \
.option("hoodie.datasource.write.precombine.field", "ts") \
.option("hoodie.datasource.write.operation", "upsert") \
.mode(Append) \
.save("<路径>/hudi_db/orders")Spark SQL 方式:使用 INSERT INTO 插入,MERGE INTO 实现 upsert(MERGE INTO 中 updates 为待合入数据的临时表,可先通过 CREATE TABLE ... USING parquet 建表并灌入数据):
INSERT INTO hudi_db.orders VALUES
('1001', '2026-09-17', 99.0, 1760000000);
MERGE INTO hudi_db.orders AS t
USING updates AS s
ON t.order_id = s.order_id
WHEN MATCHED THEN UPDATE SET *
WHEN NOT MATCHED THEN INSERT *;
UPDATE hudi_db.orders SET amount = 199.0 WHERE order_id = '1001';
DELETE FROM hudi_db.orders WHERE order_id = '1001';查询数据
Hudi 表的查询类型由 hoodie.datasource.query.type 指定,默认 snapshot,各类型说明如下表所示:
| 查询类型 | 说明 | 适用表类型 |
|---|---|---|
| snapshot | 默认,查询最新快照,MOR 表实时合并基础文件与日志文件 | COW + MOR |
| read_optimized | 读优化查询,仅读取已压缩的基础文件,不合并日志 | MOR |
| incremental | 增量查询,仅拉取给定时刻之后的新数据 | COW + MOR |
增量查询需指定起始时刻(结束时刻可省略,默认为最新提交):
spark.read.format("hudi") \
.option("hoodie.datasource.query.type", "incremental") \
.option("hoodie.datasource.read.begin.instanttime", "20260917093000123") \
.load("<路径>/hudi_db/orders")注意
incremental 查询拉取的是 begin 时刻(含)之后完成的提交数据,begin.instanttime 不必精确对应某个 Instant,Hudi 按 completion time 过滤。instanttime 为 17 位时间戳,格式 yyyyMMddHHmmssSSS(如 20260917093000123 表示 2026-09-17 09:30:00.123),可用时刻可通过 hdfs dfs -ls <表路径>/.hoodie/timeline 查看时间线获取,或通过 CALL show_commits 查询(Spark SQL)。