01. Hudi 數據模型分析

主題説明

Hudi 的數據模型是整個系統的核心抽象,説白了就是定義了數據記錄在系統中是怎麼表示的、怎麼操作的。理解數據模型是理解 Hudi 工作原理的基礎,就像蓋房子要先打地基一樣。

在 Hudi 裏,一條數據記錄不是簡單的字符串或者字節數組,而是一個結構化的對象,包含了記錄本身的數據、唯一標識、存儲位置等信息。這種設計讓 Hudi 能夠高效地管理數據,支持更新、刪除、合併等複雜操作。

細化內容

HoodieRecord - 記錄的基礎抽象

HoodieRecord 是所有記錄的抽象基類,它定義了記錄的基本結構。簡單來説,一條 HoodieRecord 包含:

  • key:記錄的鍵,類型是 HoodieKey,用來唯一標識這條記錄
  • data:記錄的實際數據,類型是泛型 T,通常是 HoodieRecordPayload 的實現
  • currentLocation:記錄當前在存儲中的位置,包含文件ID和時間戳
  • newLocation:記錄寫入後的新位置
  • operation:操作類型,比如 INSERT、UPDATE、DELETE

這個設計很巧妙,把記錄的標識、數據、位置信息都封裝在一起了。這樣在更新數據的時候,可以快速定位到記錄在哪裏,然後進行合併操作。

HoodieKey - 記錄的唯一標識

HoodieKey 是記錄的唯一標識,包含兩個字段:

  • recordKey:記錄的主鍵,比如用户ID、訂單號等
  • partitionPath:分區路徑,比如 2023/01/01 這樣的日期分區

這兩個字段組合起來就能唯一確定一條記錄。比如用户ID是 user123,分區是 2023/01/01,那麼這條記錄在表中的位置就確定了。

HoodieRecordPayload - 數據負載的抽象

HoodieRecordPayload 是一個接口,定義了數據合併的邏輯。這是 Hudi 最核心的部分之一,因為它決定了當同一條記錄有多個版本時,應該怎麼合併。

主要方法包括:

  • preCombine:在寫入前合併同一批次中的重複記錄
  • combineAndGetUpdateValue:合併存儲中的舊記錄和新的更新記錄
  • getInsertValue:獲取要插入的新記錄

不同的實現類有不同的合併策略:

  • OverwriteWithLatestAvroPayload:直接用新值覆蓋舊值
  • DefaultHoodieRecordPayload:根據排序字段選擇最新的值
  • EventTimeAvroPayload:基於事件時間合併

HoodieAvroRecord - 基於 Avro 的記錄實現

HoodieAvroRecordHoodieRecord 的具體實現,使用 Avro 格式來存儲數據。

什麼是 Avro?

Avro 是 Apache 的一個數據序列化系統,簡單説就是把數據轉換成二進制格式,方便存儲和傳輸。Avro 有幾個特點:

  1. Schema 驅動:數據結構和 Schema 是分開的,Schema 可以獨立存儲
  2. 緊湊的二進制格式:比 JSON 等文本格式更省空間
  3. 支持 Schema 演化:可以修改 Schema 而不影響已有數據
  4. 跨語言支持:Java、Python、C++ 等都能用

在 Hudi 裏,Avro 主要用於存儲增量日誌(LogFile),因為它是行式存儲,寫入性能好。而基礎文件(BaseFile)用的是 Parquet,列式存儲,查詢性能好。

元數據字段

每條 Hudi 記錄都包含一些元數據字段,這些字段是系統自動添加的:

  • _hoodie_commit_time:提交時間,記錄這條數據是什麼時候寫入的
  • _hoodie_commit_seqno:提交序列號,同一個提交內的記錄排序
  • _hoodie_record_key:記錄鍵
  • _hoodie_partition_path:分區路徑
  • _hoodie_file_name:文件名,記錄這條數據在哪個文件裏
  • _hoodie_operation:操作類型(INSERT、UPDATE、DELETE)

這些元數據字段讓 Hudi 能夠追蹤每條記錄的歷史,支持時間旅行查詢等功能。

關鍵技術

記錄的序列化和反序列化

Hudi 使用 Kryo 來序列化記錄,Kryo 是一個高效的 Java 序列化框架。序列化就是把對象轉換成字節數組,方便在網絡傳輸或者持久化存儲。

HoodieRecord 中,實現了 KryoSerializable 接口,可以自定義序列化邏輯。這對於大數據場景很重要,因為序列化的性能直接影響整體性能。

記錄的合併策略

Hudi 支持多種合併策略,主要通過 HoodieRecordPayload 的不同實現來支持:

  1. OverwriteWithLatest:直接用新值覆蓋,最簡單粗暴
  2. EventTimeBased:基於事件時間,選擇時間最新的記錄
  3. PartialUpdate:部分更新,只更新指定字段
  4. Custom:自定義合併邏輯

合併策略的選擇取決於業務需求。比如訂單表,通常用 OverwriteWithLatest;而用户行為表,可能用 EventTimeBased。

記錄的排序和分區

記錄在寫入前會進行排序,排序的依據可以是:

  • 提交時間(默認)
  • 自定義排序字段
  • 分區路徑

排序的目的是優化寫入性能,把相同分區的記錄放在一起寫入,減少文件數量。

記錄的元數據管理

元數據字段是自動管理的,不需要用户手動設置。系統會在寫入時自動填充這些字段,在讀取時自動解析。

關鍵對象説明

類關係圖


關鍵類説明

  • HoodieRecord:抽象記錄類,定義了記錄的基本結構和方法。它是所有記錄類型的基類。
  • HoodieKey:記錄鍵,包含 recordKey 和 partitionPath。實現了 equals 和 hashCode,用於記錄的去重和查找。
  • HoodieRecordPayload:記錄負載接口,定義了數據合併的核心邏輯。不同的實現類有不同的合併策略。
  • HoodieAvroRecord:基於 Avro 的記錄實現,是 Hudi 中最常用的記錄類型。它把數據序列化成 Avro 格式存儲。
  • HoodieRecordLocation:記錄位置,包含 fileId(文件ID)和 instantTime(時間戳)。用於定位記錄在存儲中的位置。

關鍵操作時序圖

下面是一個記錄合併操作的時序圖,展示了當更新一條已存在的記錄時,各個類是如何協作的:


這個時序圖展示了:

  1. 客户端調用 upsert 方法
  2. 表通過索引查找記錄位置
  3. 合併器合併新舊記錄
  4. Payload 執行具體的合併邏輯
  5. 寫入合併後的記錄

代碼示例

創建一條記錄

// 創建記錄鍵
HoodieKey key = new HoodieKey("user123", "2023/01/01");

// 創建 Avro 記錄數據
GenericRecord avroRecord = new GenericData.Record(schema);
avroRecord.put("id", "user123");
avroRecord.put("name", "張三");
avroRecord.put("age", 25);

// 創建 Payload
HoodieAvroPayload payload = new HoodieAvroPayload(
    Option.of(avroRecord), 
    System.currentTimeMillis() // 排序值
);

// 創建 Hudi 記錄
HoodieRecord<HoodieAvroPayload> record = 
    new HoodieAvroRecord<>(key, payload);

記錄合併示例

// 假設存儲中有一條舊記錄
IndexedRecord oldRecord = ...; // 從存儲讀取

// 新來的更新記錄
HoodieAvroPayload newPayload = new HoodieAvroPayload(newRecord, timestamp);

// 執行合併
Option<IndexedRecord> merged = newPayload.combineAndGetUpdateValue(
    oldRecord, 
    schema, 
    properties
);

// merged 就是合併後的結果
if (merged.isPresent()) {
    // 寫入合併後的記錄
    writeRecord(merged.get());
} else {
    // 返回空表示刪除這條記錄
    deleteRecord(key);
}

總結

Hudi 的數據模型設計得很巧妙,把記錄的標識、數據、位置信息都封裝在一起。核心要點:

  1. HoodieRecord 是基礎抽象,包含 key、data、location 等核心屬性
  2. HoodieKey 是唯一標識,由 recordKey 和 partitionPath 組成
  3. HoodieRecordPayload 定義了合併邏輯,支持多種合併策略
  4. Avro 是行式存儲格式,適合增量日誌,寫入性能好
  5. 元數據字段 自動管理,支持時間旅行等高級功能

理解數據模型是理解 Hudi 的基礎,後續的存儲、索引、查詢等功能都建立在這個模型之上。