
做数据湖的团队大多被同一个问题绊过:Hudi 表要么落在 HDFS,要么落在某家公有云的对象存储里。前者要养一套 Hadoop 集群,后者把数据和账单一起锁进厂商。如果手头已经有一套自托管的 RustFS,直接让 Hudi 的 Spark 作业把表写到它的 S3 接口上,数据湖的存储底座就换成了一个能自己掌控、按容量计费的对象存储。
数据湖的表文件,一直绑在 HDFS 上
Hudi 本身不存数据,它管理的是一批 Parquet 文件和一条叫 .hoodie 的时间线。传统部署里这批文件放在 HDFS,读写都过 NameNode。HDFS 高可用但重,养一套至少三台 master 加一堆 DataNode。当你的数据湖规模不大、却又想要 Hudi 的 upsert 和时间旅行能力时,这套底座的运维成本就显出来了。
Hudi 把时间线和 Parquet 都当对象存
Hudi 的设计里,commit 文件、增量时间线和实际数据块本质上都是对象存储里的文件。只要 Spark 能通过一个 S3 兼容端点读写 s3a:// 路径,Hudi 表就能完整落在对象存储上,不需要 NameNode 参与。每次写入和查询都走 S3 API,表的最新快照由时间线推导出来。RustFS 在这里就是那个 S3 端点,Hudi 侧完全不感知底层是谁。

Spark 起一个带 Hudi 的会话就能写
官方集成文档用 apache/spark:3.5.6 配上 hudi-spark3.5-bundle_2.12:0.15.0 和 hadoop-aws:3.3.4,对 rustfs/rustfs-x86-musl:v2.3.1 验证通过。s3a 的凭证以 Hadoop 属性传入,关键是把端点指向 RustFS、强制 path-style:
docker run --rm --network oo-rustfs_default \
-v "$PWD/hudi_test.scala":/tmp/hudi_test.scala \
apache/spark:3.5.6 /opt/spark/bin/spark-shell \
--packages org.apache.hudi:hudi-spark3.5-bundle_2.12:0.15.0,org.apache.hadoop:hadoop-aws:3.3.4 \
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
--conf spark.hadoop.fs.s3a.endpoint=http://rustfs:9000 \
--conf spark.hadoop.fs.s3a.path.style.access=true \
--conf spark.hadoop.fs.s3a.access.key=<your-access-key> \
--conf spark.hadoop.fs.s3a.secret.key=<your-secret-key> \
--conf spark.hadoop.fs.s3a.region=us-east-1 \
--conf spark.jars.ivy=/tmp/.ivy2 \
--conf spark.sql.shuffle.partitions=2 \
-i /tmp/hudi_test.scala
那个 scala 脚本里建一张非分区表,用 write.format("hudi") 落到 s3a://my-bucket/hudi-demo/events。两份配置值得单独说:Hudi 明确要求 Kryo 序列化器,用 Spark 默认的 Java 序列化会直接报错拒跑;spark.jars.ivy 是为了绕开容器里 Ivy 缓存的权限问题,第一次跑 --packages 会现场下载那两个 bundle,网络慢要有耐心。

读回验证那三行数据
写入成功后,同一个 Spark 会话用 spark.read.format("hudi").load("s3a://my-bucket/hudi-demo/events") 读回,show() 能拿到那三行 (1,a)(2,b)(3,c)。到 RustFS 侧 rc ls rustfs/my-bucket/hudi-demo/ -r,能看到 .hoodie/ 下的 commit 文件和 Parquet 数据块已经躺在桶里。这一步很关键,它证明 Hudi 表确实完整落在对象存储、不是只在 Spark 本地临时目录。Spark 是一次性客户端,跑完不落状态,表的状态全在桶里,下次换台机器照样能读。
版本对不齐,写入就炸
Hudi 最容易翻车的地方是版本匹配。hudi-spark3.5-bundle_2.12 必须配 Spark 3.5.x,bundle 内置的 Hadoop 客户端要和传进去的 hadoop-aws:3.3.4 兼容,错一个就会出现 NoSuchMethodError 或 ClassNotFoundException。非分区表还要把 hoodie.datasource.write.partitionpath.field 设成空字符串、别去指定 keygenerator 类,否则会报"Partition-path field has to be non-empty"。这套组合是官方验证过的,生产上别擅自升 Spark 大版本。官方也说明,这套本地集成测试不适用于生产环境,真上生产得按 Hudi 的 Spark 指南把 upsert、compaction 和并发写考虑进去。
把 Hudi 表直接写进 RustFS,等于把数据湖的存储底座从 HDFS 或公有云换成了一个自己能掌控的对象存储,表的 upsert 和时间旅行能力一点没少。完整的 Spark 配置和排错清单见 RustFS 官方 Hudi 集成文档;Hudi 本身的用法可以参考其官方 Spark 快速指南。