Apache Hudi RFC-93 解读:可插拔表格式(Pluggable Table Formats)架构设计与实现剖析 数据湖湖仓一体大数据数据存储【免费下载链接】hudiUpserts, Deletes And Incremental Processing on Big Data.项目地址https://gitcode.com/gh_mirrors/hud/hudi点击查看免费下载导读本篇技术文章以 RFC-93: Pluggable Table Formats in Hudi 为骨架深入剖析 Apache Hudi 如何把“表格式Table Format存储布局”从平台核心中抽象出来形成可插拔的HoodieTableFormat接口。文章将带读者理解为什么 Hudi 要在保留原生格式的同时开放这一层抽象、外部表格式如 Iceberg如何以“插件回调 元数据覆盖”的方式接入 Hudi 的写路径与表服务以及当前仓库中该设计的实际落地代码、配置项与测试验证。读完本文你将掌握hoodie.table.format配置的来龙去脉、HoodieTableFormat接口契约、提交/回滚/清理等操作的插件化回调语义以及如何基于该抽象实现一个自定义表格式插件。1. 背景为什么 Hudi 需要“可插拔表格式”Hudi 在过去数年中一直将自己定位为数据湖上的更广泛平台与软件栈其原生表格式文件组 FileGroup、文件片 FileSlice、时间线 Timeline、索引等针对数据湖负载做出了具体取舍具备高效读写能力。与此同时社区决定将重心放在原生 Hudi 存储格式上但这并不意味着要拒绝其他存储布局的共存。RFC-93 提出了支持不同后端表格式实现的方案其技术动机与社区动机都很明确。1.1 技术动因时间线与元数据的云原生高性能实现部分使用场景希望用 NoSQL 数据存储例如 DynamoDB来承载HoodieTimeline与HoodieMetadata接口背后的实现以获得超低延迟查询需要表格式层可替换。存储布局已具备可扩展基础Hudi 本身已支持多种存储布局分桶、一致性哈希、按到达顺序组织数据等相关抽象见 HoodieStorageLayout。分层可插拔已是惯例Hudi 在记录合并Record Merger、索引以及核心读写路径等多个层次都已允许插件化定制。数据库领域的通行做法主流数据库普遍支持多种存储后端如 MySQL 的 MyISAM/InnoDB、MyRocks 的 LSM可插拔表格式是走向“数据库北极星”愿景的关键一步。生态共赢支持 Iceberg、Delta Lake 等既有表格式也能让 Hudi Streamer 等工具、开箱即用的自动表管理、高性能写路径惠及其他社区。1.2 社区动因从非技术角度看过去两年该领域厂商关注度极高Hudi 常常被简化为“一种表格式”并与其他格式直接比较。将表格式层开放给不同实现有助于突出 Hudi 开放软件服务的价值同时使普通 OSS 贡献者免受厂商之争vendor FUD的干扰聚焦于开源软件设计与开发本身。2. 核心抽象HoodieTableFormat接口实现层面的核心动作是创建名为HoodieTableFormat的抽象统一处理表格式相关操作包括提交写入Committing writes更新与读取表格式专属的元数据操作时间线Timeline冲突消解Conflict Resolution锁提供者Lock Provider回滚RollbacksHudi 平台负责管理数据路径可通过表格式插件进行配置默认情况下继续使用 Hudi 原生表格式。其他表格式将各自实现这一抽象。2.1 接口定义与契约RFC-93 给出的接口定义在仓库中已落地为 HoodieTableFormat。从源码看接口继承Serializable核心契约如下String getName()返回表格式名称用于与hoodie.table.format配置值匹配。void init(Properties properties)使用HoodieTableConfig提供的属性初始化表格式实现默认空实现。void commit(...)在 Hudi 时间线中将写动作标记为完成后被调用实现负责保存额外的状态到extraMetadata。void clean(...)clean 动作完成后回调。void archive(...)时间线归档 instants 后回调。void rollback(...)/void completedRollback(...)回滚动作之前/完成后的回调。void savepoint(...)/void restore(...)savepoint 与 restore 动作完成后的回调。TimelineFactory getTimelineFactory()返回以插件元数据状态为“唯一事实来源”source of truth的时间线工厂。TableMetadataFactory getMetadataFactory()返回使用插件格式元数据的元数据读取器工厂。相较于 RFC 草案中的completeWrite/completeClustering/completeClean/completeRollback/archiveInstants等显式方法落地接口统一收拢为以动作名命名的commit、clean、archive、rollback、restore、savepoint回调并增加了init与getName更贴合 Hudi 内部动作类型。调用方通过HoodieTableMetaClient、FileSystemViewManager获取元数据与文件系统视图通过HoodieEngineContext感知执行引擎本地、Spark 或 Flink。2.2 原生实现NativeTableFormatHudi 原生表格式作为默认实现位于 NativeTableFormatpublic class NativeTableFormat implements HoodieTableFormat { public static final String TABLE_FORMAT native; ... public TimelineFactory getTimelineFactory() { return TimelineLayout.fromVersion(timelineLayoutVersion).getTimelineFactory(); } Override public TableMetadataFactory getMetadataFactory() { return NativeTableMetadataFactory.getInstance(); } }从源码可以看到原生实现以native为名称时间线工厂直接复用TimelineLayout按时间线布局版本TimelineLayoutVersion派生的工厂V1/V2 布局元数据工厂使用 NativeTableMetadataFactory。也就是说未配置任何插件时Hudi 行为与当前完全一致。3. 提交协议Hudi 时间线 外部格式元数据的“两步完成”Hudi 依赖在时间线目录中创建“动作完成文件”如.commit、.deltacommit、.clean等来对外通告动作完成。引入TableFormatPlugin后这一过程变为两步在.hoodie时间线中创建动作完成文件将动作完成时间存储到表格式的提交元数据中。Hudi 时间线仍被内部所有操作使用表格式的提交元数据相当于叠加在其上的“覆盖层”overlay。只有当上述两步都完成后动作才算真正完成。插件提供的时间线需要“围栏”fence住 Hudi 时间线保证“完成”的定义始终一致从而维持快照隔离snapshot isolation。在仓库实现中这一两步语义体现为 HoodieTableMetaClient 对tableFormat.getTimelineFactory().createActiveTimeline(this)的调用源码第 520/548 行即所有活跃时间线的构造都经由表格式插件工厂插件可据此实现“只有提交元数据成功写入才把 instant 标记为 complete”的过滤语义。4. 表属性hoodie.table.format配置RFC 提出新增表属性hudi.table.format以标识表格式默认值为native从而在所有 Hudi 写入端保持插件行为一致。落地代码中该配置定义为 HoodieTableConfig.TABLE_FORMATpublic static final ConfigPropertyString TABLE_FORMAT ConfigProperty .key(hoodie.table.format) .defaultValue(NativeTableFormat.TABLE_FORMAT)读取与插件装配逻辑位于HoodieTableConfig#getTableFormatpublic HoodieTableFormat getTableFormat(TimelineLayoutVersion layoutVersion) { String tableFormat getStringOrDefault(TABLE_FORMAT); if (!tableFormat.equals(NativeTableFormat.TABLE_FORMAT)) { ServiceLoaderHoodieTableFormat loader ServiceLoader.load(HoodieTableFormat.class); for (HoodieTableFormat tableFormatImpl : loader) { if (getString(TABLE_FORMAT).equals(tableFormatImpl.getName())) { tableFormatImpl.init(props); return tableFormatImpl; } } } return new NativeTableFormat(layoutVersion); }关键机制值得注意SPI 装配非原生格式通过 Java 标准的ServiceLoader机制按HoodieTableFormat类型加载运行时根据hoodie.table.format的值与各实现的getName()匹配默认回退只要配置值等于native或找不到匹配插件都回退到NativeTableFormat贯穿写路径HoodieTableMetaClient在初始化源码第 228 行与 reload第 566 行时均调用getTableFormat并在TableBuilder中支持setTableFormat(...)源码第 1289-1290 行确保 Spark/Flink 等各写入端识别同一表格式。5. 分模块设计要点5.1 元数据Metadata当配置了不同的表格式时Hudi 的元数据操作被替换为该表格式的元数据做法是新增一个HoodieTableMetadata的适配器实现。在落地代码中TableMetadataFactory 提供了create(...)抽象方法用于构建HoodieTableMetadata插件通过getMetadataFactory()返回自己的工厂从而让“外部格式元数据 Hudi 数据文件”共同支撑查询。5.2 时间线Timeline时间线实现确保外部表格式被插件化的格式的元数据状态例如提交状态是所有操作的唯一事实来源。上文提到的TimelineFactory抽象见 TimelineFactory定义了默认时间线、活跃时间线、归档时间线、完成时间查询视图等一系列创建方法插件需实现这些方法保证只有插件提交回调成功后才把对应 instant 标记为 complete。5.3 冲突消解Conflict ResolutionHudi 已提供可配置的多种策略用于在文件级别判断两个并发操作是否冲突。外部可插拔格式可能不具备如此细的粒度因此需要基于其自身元数据实现冲突消解策略。RFC 草案中对应方法为getConflictResolutionStrategyClassName即插件返回一个与自身格式对齐的冲突消解策略类名。5.4 锁机制LockingHudi 具备可插拔的锁提供者支持外部格式通常需要依赖 Catalog 或其他手段提供锁语义。外部表格式实现必须提供自定义的LockProvider将自身锁机制适配到 Hudi 的LockProvider接口以便所有写操作与表服务操作可以并发运行。仓库中 Hudi 自身的锁提供者示例包括基于文件系统的 FileSystemBasedLockProvider、基于 Zookeeper 的 BaseZookeeperBasedLockProvider 以及 DynamoDBBasedLockProviderDynamoDB 场景可作为插件锁适配的参照。5.5 回滚Rollbacks外部表格式实现需要能够回滚失败的写入并恢复到之前的提交。插件实现必须保留足够的状态以支持回滚——这正是落地接口中rollback回滚前与completedRollback回滚完成后两个回调并存的原因前者用于在 Hudi 时间线中标记回滚动作前让插件清理/撤销其元数据状态后者用于回滚动作完成后让插件记录新的已完成 instant。5.6 布局Layout表格式如 Iceberg对应的元数据默认存储在.hoodie/目录下其位置记录在hoodie.properties中。这与第 7 节集成图中“Hudi 继续在.hoodie/timeline记录动作”的设计保持一致Hudi 数据文件与外部格式元数据文件共存于同一表目录。6. 端到端集成架构RFC-93 中的集成图展示了端到端集成方式关键要点如下Hudi 写入端继续使用原生文件系统管理Hudi 组织文件为文件组file-groups与文件片file-slices、以及 Hudi 索引的工作方式保持不变时间线记录不变Hudi 继续在.hoodie/timeline记录动作表属性驱动外部表格式作为表属性配置确保所有写入端都能识别动作完成即回调当 Hudi 的每个动作write、clean、rollback 等完成时Hudi 调用外部表格式插件由插件负责记录外部格式读取端查询该表所需的元数据插件时间线为事实来源Hudi 使用插件的时间线实现该实现以插件元数据中存储的状态为事实来源。例如 Iceberg 插件需要在动作完成时创建 manifest 文件并提供尊重其快照文件状态的时间线Hudi 用该时间线判断哪些动作成功、哪些失败用于回滚并发控制适配外部插件还需提供LockProvider与冲突消解实现使外部表格式支持的并发控制例如乐观并发控制得以生效元数据可被 Hudi 写入端使用插件提供能力让其元数据可被 Hudi 写入端使用混合可读外部表格式的元数据与 Hudi 数据文件相结合使得该表可以被当作直接用外部表格式写入的表来读取。7. 自定义插件的最小实现仓库中的参考实现与测试为了验证这套抽象的功能仓库提供了两个层面的参考7.1 测试插件TestTableFormathudi-common/src/test/java/org/apache/hudi/tableformat/TestTableFormat.java 是一个内存版的最小插件实现它以test-format为名称用ConcurrentHashMap记录每个 basePath 下所有完成/回滚/归档的 instantscommit、clean、savepoint、restore、completedRollback都会把对应 instant 追加到记录rollback与archive则做移除操作并分别通过TestTimelineFactory与TestTableMetadataFactory返回时间线与元数据工厂。该类的注释明确指出其用途是“对 HoodieTableFormat 进行功能测试”是实现插件的最小可运行样例。7.2 端到端写路径测试hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/hudi/TestHoodieSparkSqlWriterWithTestFormat.scala 是 Spark SQL 写路径接入test-format的完整测试套件其核心用法极具实操参考价值var fooTableModifier commonTableModifier.updated(hoodie.bulkinsert.shuffle.parallelism, 4) .updated(DataSourceWriteOptions.OPERATION.key, DataSourceWriteOptions.BULK_INSERT_OPERATION_OPT_VAL) ... .updated(HoodieTableConfig.TABLE_FORMAT.key, test-format) if (enableOCCConfigs) { fooTableModifier fooTableModifier .updated(hoodie.write.concurrency.mode, optimistic_concurrency_control) .updated(hoodie.clean.failed.writes.policy, LAZY) .updated(hoodie.write.lock.provider, org.apache.hudi.client.transaction.lock.InProcessLockProvider) }该测试验证了在hoodie.table.formattest-format下执行 bulk insert写入 1000 行并叠加 40 行更新以触发 preCombine、随后读取整个数据集校验记录完整性的完整链路同时展示了开启 OCC乐观并发控制时如何配合InProcessLockProvider与 RFC 中“外部插件需提供 LockProvider 与冲突消解以支持其并发控制如 OCC”的设计相印证。这为外部格式插件的接入方式提供了可直接对照的实操范式只需实现HoodieTableFormat的 SPI并通过hoodie.table.format指定名称即可被写路径识别。8. 适用范围与限制RFC 明确列出初始实现的范围限制这些限制在评估任何基于该 RFC 的落地时都需考虑仅支持 COW Spark初始实现只支持 Hudi 的 Copy On WriteCOW表格式且以 Spark 作为处理框架MOR 表不在初始范围内后续可扩展只写不互写表必须仅通过 Hudi 写入不保证经由外部表格式直接写入的互操作能力。9. 代码库归属与演进计划RFC 对代码归属与落地节奏做了规划接口与原生实现留在 Hudi 仓库可插拔接口与 Hudi 原生格式实现位于 Hudi 代码库即上文提到的hudi-common模块而其他系统的支持将在 Apache XTable孵化中中完成既有表适配工具为已有表提供首次构造表格式的实用工具反射配置插件通过反射配置插件以启用特定格式默认保持native1.x 版本纳入该能力计划在 Hudi 1.x 中加入。从当前仓库的源码结构可以确认RFC-93 的核心接口HoodieTableFormat、原生实现NativeTableFormat、表属性hoodie.table.format以及测试基础设施TestTableFormat与 Spark SQL 写路径测试均已落地说明该设计已从提案阶段进入实际实现与验证阶段。总结RFC-93 为 Hudi 打开了一扇门在保留原生高性能表格式的同时允许 Iceberg、Delta Lake 等既有格式作为“插件”接入 Hudi 的平台能力。其核心机制可概括为——Hudi 时间线依然是内部操作的中枢外部格式元数据作为覆盖层叠加其上通过hoodie.table.format表属性统一装配通过HoodieTableFormat的回调契约完成提交、清理、归档、回滚、savepoint/restore 等动作的同步并通过插件自带的 Timeline、Metadata、LockProvider 与冲突消解实现维持并发控制与快照隔离。对于希望把 Hudi 的写入、流式摄取与自动表管理能力复用到其他表格式的开发者而言本文介绍的接口契约、SPI 装配机制与测试范式即是最直接的入门路径。赞分享数据湖湖仓一体大数据数据存储【免费下载链接】hudiUpserts, Deletes And Incremental Processing on Big Data.项目地址https://gitcode.com/gh_mirrors/hud/hudi点击查看免费下载相关推荐在 Redwood 中使用 GoTrue 构建自托管身份认证Sign Up / Sign In / Sign Out 全流程在 Redwood 中使用 GoTrue 构建自托管身份认证Sign Up / Sign In / Sign Out 全流程 这篇指南将带你脱离 Netli数据湖湖仓一体大数据数据存储Apache Hudi RFC-100 深度解读BLOB 非结构化数据统一存储架构与读写设计Apache Hudi RFC 100 深度解读BLOB 非结构化数据统一存储架构与读写设计 导读 本文以 Apache Hudi 社区 RFC 100Un数据湖湖仓一体大数据数据存储hudi-architect面向 Apache Hudi 1.2.0 的交互式表设计顾问 Skill 架构解析hudi architect面向 Apache Hudi 1.2.0 的交互式表设计顾问 Skill 架构解析 导读 本文以 Apache Hudi 仓库中数据湖湖仓一体大数据数据存储上一篇如何快速开始使用Harden-Windows-Security个人用户入门教程下一篇iStoreOS自动化脚本定时任务和智能管理终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考