Hudi技术内幕:RecordPayload到RecordMerger

一、引言

Hudi 作为数据湖领域的核心组件,其 Record Payload 机制是实现数据增量处理、去重合并的关键抽象,它定义了当新旧记录发生冲突时,如何决定最终保留哪条数据、如何合并字段。

在 Hudi 1.x 版本中,Record Payload 体系经历了重要的架构演进------从早期的HoodieRecordPayload接口逐步迁移到基于HoodieRecordMerger的新合并架构。

本文将介绍 Record Payload 的核心机制原理、不同实现类型的适用场景,以及最佳实践。

二、Record Payload 核心机制

HoodieRecordPayload是 Hudi 早期版本定义的核心接口,位于org.apache.hudi.common.model包下:

csharp 复制代码
public interface HoodieRecordPayload<T extends HoodieRecordPayload> {

  /**
   * 与已有记录进行合并,返回合并后的记录。
   * currentValue: 存储中已有的旧记录
   */
  T preCombine(T oldValue);

  /**
   * 控制是否需要将该记录写入存储。
   * 可用于实现"软删除"等逻辑。
   */
  Option<IndexedRecord> combineAndGetUpdateValue(
      IndexedRecord currentValue, Schema schema) throws IOException;

  /**
   * 插入场景下,获取最终要写入的记录。
   */
  Option<IndexedRecord> getInsertValue(Schema schema) throws IOException;
}
方法 调用时机 作用
preCombine 同一批次内去重 在写入前,对相同 Key 的记录进行预合并
combineAndGetUpdateValue Upsert 时与存储中旧记录合并 决定最终更新值,返回 Option.empty() 可实现删除
getInsertValue Insert 时 决定最终插入值

简单示例:

vbnet 复制代码
                    ┌─────────────────────┐
                    │  一批数据进入 Hudi   │
                    └──────────┬──────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │  按 Record Key 分组  │
                    └──────────┬──────────┘
                               │
                ┌──────────────┼──────────────┐
                │              │              │
                ▼              ▼              ▼
        ┌──────────┐   ┌──────────┐   ┌──────────┐
        │ Key = A  │   │ Key = B  │   │ Key = C  │
        │ 2条记录  │   │ 1条记录  │   │ 3条记录  │
        └────┬─────┘   └────┬─────┘   └────┬─────┘
             │              │              │
             ▼              │              ▼
     ┌───────────────┐     │      ┌───────────────┐
     │  preCombine   │     │      │  preCombine   │
     │  (批内去重)   │     │      │  (批内去重)   │
     └───────┬───────┘     │      └───────┬───────┘
             │              │              │
             ▼              ▼              ▼
     ┌─────────────────────────────────────────┐
     │           Index 查询已有记录             │
     └──────────────────┬──────────────────────┘
                        │
           ┌────────────┼────────────┐
           │            │            │
           ▼            ▼            ▼
      已存在记录    不存在记录    已存在记录
           │            │            │
           ▼            ▼            ▼
  combineAndGet     getInsert    combineAndGet
  UpdateValue       Value        UpdateValue
           │            │            │
           ▼            ▼            ▼
     ┌─────────────────────────────────────────┐
     │          写入存储文件                    │
     └─────────────────────────────────────────┘

三、Record Merger 核心机制

经典 HoodieRecordPayload 存在以下局限:

  • 引擎耦合:Payload 内部直接操作 Avro IndexedRecord,使得 Spark/Flink 等引擎的原生数据结构无法高效利用
  • 性能瓶颈:每条记录都需要序列化/反序列化为 Avro 格式进行合并
  • 扩展性不足:合并逻辑绑定在记录本身(Payload 是记录的一部分),难以灵活插拔

Hudi 1.x 引入了HoodieRecordMerger接口,将合并逻辑从记录中解耦:

csharp 复制代码
public interface HoodieRecordMerger {

  // 合并两条记录,返回合并结果及操作类型
  Option<Pair<HoodieRecord, Schema>> merge(
      HoodieRecord older,
      Schema oldSchema,
      HoodieRecord newer,
      Schema newSchema,
      TypedProperties props) throws IOException;

  // 返回该 Merger 的唯一标识
  String getMergerStrategy();

  // 返回合并模式
  HoodieRecordType getRecordType();
}

HoodieRecordPayload VS HoodieRecordMerger

在 Hudi 1.x 中,通过以下配置指定合并策略(如果同时配置了hoodie.payload.classhoodie.record.merger.strategy,Merger 优先级更高):

配置项 说明
hoodie.payload.class 经典 Payload 类(向后兼容)
hoodie.record.merger.strategy 新 Merger 策略标识
hoodie.record.merger.impls Merger 实现类列表

四、内置 Payload/Merger 类型与适用场景

1.主要内置类型总览

Payload 类 对应 Merger 策略 排序依据 核心行为 典型场景
DefaultHoodieRecordPayload 默认 precombine 字段 取 precombine 值较大的记录 通用 Upsert
OverwriteWithLatestAvroPayload OVERWRITE_WITH_LATEST 无(总是新值覆盖) 新记录直接覆盖旧记录 全量快照覆盖
EventTimeAvroPayload --- 事件时间字段 基于事件时间去重,处理乱序 流式乱序数据
PartialUpdateAvroPayload --- precombine 字段 非 null 字段覆盖旧值 多流拼接/部分更新
ExpressionPayload 1.x 新增 CUSTOM 可配置表达式 灵活的字段级合并规则 复杂合并需求

2.DefaultHoodieRecordPayload

kotlin 复制代码
// 核心逻辑伪代码
preCombine(oldValue):
    if (this.orderingVal >= oldValue.orderingVal)
        return this
    else
        return oldValue

combineAndGetUpdateValue(currentValue):
    if (this.orderingVal >= currentValue.orderingVal)
        return this.record   // 新值胜出
    else
        return currentValue  // 旧值保留

特点:

  • 基于 hoodie.datasource.write.precombine.field 指定的排序字段进行比较
  • 整条记录级别的覆盖(非字段级)
  • 是 Hudi 的默认 Payload

适用场景:标准 CDC 入湖、基于时间戳的去重

3.OverwriteWithLatestAvroPayload

kotlin 复制代码
// 核心逻辑伪代码
preCombine(oldValue):
    return this  // 永远返回当前(新)记录

combineAndGetUpdateValue(currentValue):
    return this.record  // 永远覆盖

特点:

  • 不做任何比较,新来的数据无条件覆盖
  • 性能最优(无需读取比较字段)

适用场景:全量同步覆盖、确保数据源有序的场景

4.EventTimeAvroPayload

kotlin 复制代码
// 核心逻辑伪代码
combineAndGetUpdateValue(currentValue):
    if (isDeleteRecord(this) && this.eventTime > currentValue.eventTime)
        return empty()  // 执行删除
    if (this.eventTime >= currentValue.eventTime)
        return this.record
    else
        return currentValue  // 乱序数据被丢弃

特点:

  • 专为处理乱序设计
  • 支持基于事件时间的删除标记
  • 配合 hoodie.payload.event.time.field 使用

适用场景:Kafka 多分区消费乱序、事件驱动架构

5.PartialUpdateAvroPayload

csharp 复制代码
// 核心逻辑伪代码
combineAndGetUpdateValue(currentValue):
    mergedRecord = new Record(schema)
    for each field in schema:
        if (newRecord.get(field) != null)
            mergedRecord.set(field, newRecord.get(field))  // 用新值
        else
            mergedRecord.set(field, currentValue.get(field))  // 保留旧值
    return mergedRecord

特点:

  • 字段级别的合并
  • null 值表示"不更新该字段"
  • 支持多流写入同一张表的不同字段

五、Upsert数据合并流程详解

COW vs MOR 表的差异:

维度 Copy-On-Write (COW) Merge-On-Read (MOR)
合并时机 写入时立即合并 读取/Compaction 时合并
Payload 调用 写入阶段调用 combineAndGetUpdateValue Compaction 阶段调用
写入开销 高(需重写整个文件) 低(追加 Log 文件)
读取开销 低(直接读 Base 文件) 较高(需合并 Base + Log)
适用场景 读多写少 写多读少/近实时场景

六、选型最佳实践

相关推荐
SelectDB16 小时前
秒级弹性、最高降本 70%:SelectDB Serverless 如何重塑云数仓资源效率
大数据·后端·云原生
WhoAmI16 小时前
MapReduce框架原理解析一:InputFormat
大数据·hadoop
WhoAmI16 小时前
MapReduce框架原理解析三:OutputFormat
大数据·hadoop
WhoAmI16 小时前
MapReduce框架原理解析二:Shuffle
大数据·hadoop
大大大大晴天2 天前
Hudi技术内幕:Key Generation原理与实践
大数据
得物技术5 天前
从埋点需求到规则资产:Hermes Agent 重构得物数仓工作流
大数据·llm·ai编程
久美子5 天前
AI驱动数仓建设的Harness工程实践——本体建模、知识分层与上下文工程
大数据
大树886 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
大志哥1236 天前
ES和Logstash日志链路系统上线后遭遇切片爆炸(解决)
大数据·elasticsearch