hudi索引

1.重点类

1.1.HoodieIndex

  索引实现的基类,核心方法是两个:tagLocation和updateLocation
  后续有不同的子类实现具体的索引
在这里插入图片描述

1.2.HoodieIndexFactory

  没有具体这个类,是创建HoodieIndex的工厂类。具体操作类的名字以这个为后缀:FlinkHoodieIndexFactory、SparkHoodieIndexFactory、JavaHoodieIndexFactory
  以FlinkHoodieIndexFactory来说,支持如下索引的创建

// TODO more indexes to be added
switch (config.getIndexType()) {
  case INMEMORY:
    return new FlinkInMemoryStateIndex(context, config);
  case BLOOM:
    return new HoodieBloomIndex(config, ListBasedHoodieBloomIndexHelper.getInstance());
  case GLOBAL_BLOOM:
    return new HoodieGlobalBloomIndex(config, ListBasedHoodieBloomIndexHelper.getInstance());
  case SIMPLE:
    return new HoodieSimpleIndex(config, Option.empty());
  case GLOBAL_SIMPLE:
    return new HoodieGlobalSimpleIndex(config, Option.empty());
  case BUCKET:
    return new HoodieSimpleBucketIndex(config);
  default:
    throw new HoodieIndexException("Unsupported index type " + config.getIndexType());
}

1.3.BaseHoodieBloomIndexHelper

  执行过滤步骤的类,两个实现类ListBasedHoodieBloomIndexHelper和SparkHoodieBloomIndexHelpe

1.4.HoodieBaseBloomIndexCheckFunction

  BaseHoodieBloomIndexHelper进行操作的核心,computeNext是进行bloom过滤的核心方法,关键点在于keyLookupHandle.addKey(recordKey)的调用

1.5.HoodieKeyLookupHandle

  就是1.4中的keyLookupHandle的类名,方法中利用了bloomFilter过滤

public void addKey(String recordKey) {
  // check record key against bloom filter of current file & add to possible keys if needed
  if (bloomFilter.mightContain(recordKey)) {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Record key " + recordKey + " matches bloom filter in  " + partitionPathFileIDPair);
    }
    candidateRecordKeys.add(recordKey);
  }
  totalKeysChecked++;
}

  bloomFilter的获取也在这个类当中

public HoodieKeyLookupHandle(HoodieWriteConfig config, HoodieTable<T, I, K, O> hoodieTable,
                             Pair<String, String> partitionPathFileIDPair) {
  super(config, hoodieTable, partitionPathFileIDPair);
  this.candidateRecordKeys = new ArrayList<>();
  this.totalKeysChecked = 0;
  this.bloomFilter = getBloomFilter();
}

1.6.HoodieFileReader

  文件读取类,有一个重点方法就是读取bloom过滤器readBloomFilter()
  有三个实现:HoodieParquetReader、HoodieOrcReader、HoodieHFileReader

1.7.BaseWriteHelper

  触发调用HoodieIndex的tagLocation接口的类,调用接口是tag(),tag接口由自身的write接口调用
  对Flink有点特殊,Flink重载了write接口,内部没有调用tag()。索引Flink流程可能没有使用bloom索引

2.流程

2.1.创建

  进行查询时相关类的构建的调用链

HoodieTable.getIndex -> HoodieIndexFactory.createIndex -> HoodieIndex

  其中,创建HoodieIndex时,参数中包含了BaseHoodieBloomIndexHelper
  相关配置项为hoodie.index.type

2.2.获取bloomFilter

  获取bloomFilter的整体调用流程为

BaseWriteHelper.write -> tag -> HoodieIndex.tagLocation -> lookupIndex -> BaseHoodieBloomIndexHelper.findMatchingFilesForRecordKeys -> HoodieBaseBloomIndexCheckFunction.apply -> LazyKeyCheckIterator.computeNext -> HoodieKeyLookupHandle.getBloomFilter -> HoodieFileReader.readBloomFilter -> BaseFileUtils.readBloomFilterFromMetadata

  获取bloomFilter以后,在HoodieKeyLookupHandle.addKey中使用

2.3.bloomFilter写入文件

  写入数据时产生将Key写入bloomFilter

DataWriter.write -> BulkInsertDataInternalWriterHelper.write -> HoodieRowCreateHandle.writeRow -> HoodieInternalRowParquetWriter.writeRow -> HoodieRowParquetWriteSupport.add -> HoodieBloomFilterWriteSupport.addKey -> BloomFilter.add

  之后将bloomFilter写入文件,因为是写入Parquet的Footer,好像是直接基于Parquet提供的接口,并没有直接调用写文件的接口

HoodieRowParquetWriteSupport.finalizeWrite -> HoodieBloomFilterWriteSupport.finalizeMetadata -> BloomFilter.serializeToString
public Map<String, String> finalizeMetadata() {
  HashMap<String, String> extraMetadata = new HashMap<>();

  extraMetadata.put(HOODIE_AVRO_BLOOM_FILTER_METADATA_KEY, bloomFilter.serializeToString());
  if (bloomFilter.getBloomFilterTypeCode().name().contains(HoodieDynamicBoundedBloomFilter.TYPE_CODE_PREFIX)) {
    extraMetadata.put(HOODIE_BLOOM_FILTER_TYPE_CODE, bloomFilter.getBloomFilterTypeCode().name());
  }

  if (minRecordKey != null && maxRecordKey != null) {
    extraMetadata.put(HOODIE_MIN_RECORD_KEY_FOOTER, minRecordKey.toString());
    extraMetadata.put(HOODIE_MAX_RECORD_KEY_FOOTER, maxRecordKey.toString());
  }

  return extraMetadata;
}

  Parquet最终写基于ParquetFileWriter

FinalizedWriteContext finalWriteContext = writeSupport.finalizeWrite();
Map<String, String> finalMetadata = new HashMap<String, String>(extraMetaData);
String modelName = writeSupport.getName();
if (modelName != null) {
  finalMetadata.put(ParquetWriter.OBJECT_MODEL_NAME_PROP, modelName);
}
finalMetadata.putAll(finalWriteContext.getExtraMetaData());
parquetFileWriter.end(finalMetadata);

2.4.索引的key

  索引配置是一个全局配置,没有针对某列建索引,Key的来源有很多种:可能是uuid、partition、primaryKey(filed.get(0)获取)、还有基于计算引擎数据类型获取的,具体看HoodieKey的构造
  此外有一个配置,设置key取哪个列:hoodie.datasource.write.recordkey.field,默认uuid

3.Parquet writer类结构

3.1.创建

  以HoodieRowCreateHandle起点看,在构造函数中,创建了HoodieInternalRowFileWriter

this.fileWriter = HoodieInternalRowFileWriterFactory.getInternalRowFileWriter(path, table, writeConfig, structType);

  这里会创建WriteSupport,然后作为HoodieInternalRowParquetWriter的成员

HoodieRowParquetWriteSupport writeSupport =
        new HoodieRowParquetWriteSupport(table.getHadoopConf(), structType, bloomFilterOpt, writeConfig);

return new HoodieInternalRowParquetWriter(
    path,
    new HoodieParquetConfig<>(
        writeSupport,
        writeConfig.getParquetCompressionCodec(),
        writeConfig.getParquetBlockSize(),
        writeConfig.getParquetPageSize(),
        writeConfig.getParquetMaxFileSize(),
        writeSupport.getHadoopConf(),
        writeConfig.getParquetCompressionRatio(),
        writeConfig.parquetDictionaryEnabled()
    ));

  HoodieInternalRowParquetWriter的构造函数里可以看到作为了独立的成员

public HoodieInternalRowParquetWriter(Path file, HoodieParquetConfig<HoodieRowParquetWriteSupport> parquetConfig)
    throws IOException {
  super(file, parquetConfig);

  this.writeSupport = parquetConfig.getWriteSupport();
}

  HoodieInternalRowParquetWriter最终的父类就是第三方的ParquetWriter,ParquetWriter的构造函数的参数包含WriteSupport

3.2.写数据

  写的时候,上层调用的是HoodieInternalRowParquetWriter,接口是writeRow,其中有对WriteSupport写接口的调用

public void writeRow(UTF8String key, InternalRow row) throws IOException {
  super.write(row);
  writeSupport.add(key);
}

  writer最终就是调用父类ParquetWriter的write接口
  writeSupport.add(key)是WriteSupport的接口,这里是做一个内存缓存

public void add(UTF8String recordKey) {
  this.bloomFilterWriteSupportOpt.ifPresent(bloomFilterWriteSupport ->
      bloomFilterWriteSupport.addKey(recordKey));
}

  缓存在finalizeWrite接口中使用

public WriteSupport.FinalizedWriteContext finalizeWrite() {
  Map<String, String> extraMetadata =
      bloomFilterWriteSupportOpt.map(HoodieBloomFilterWriteSupport::finalizeMetadata)
          .orElse(Collections.emptyMap());

  return new WriteSupport.FinalizedWriteContext(extraMetadata);
}

  finalizeWrite接口在第三方的InternalParquetRecordWriter的close方法调用
  HoodieBloomFilterWriteSupport::finalizeMetadata这一步是实现了写入自定义的元数据

public Map<String, String> finalizeMetadata() {
  HashMap<String, String> extraMetadata = new HashMap<>();

  extraMetadata.put(HOODIE_AVRO_BLOOM_FILTER_METADATA_KEY, bloomFilter.serializeToString());
  if (bloomFilter.getBloomFilterTypeCode().name().contains(HoodieDynamicBoundedBloomFilter.TYPE_CODE_PREFIX)) {
    extraMetadata.put(HOODIE_BLOOM_FILTER_TYPE_CODE, bloomFilter.getBloomFilterTypeCode().name());
  }

  if (minRecordKey != null && maxRecordKey != null) {
    extraMetadata.put(HOODIE_MIN_RECORD_KEY_FOOTER, minRecordKey.toString());
    extraMetadata.put(HOODIE_MAX_RECORD_KEY_FOOTER, maxRecordKey.toString());
  }

  return extraMetadata;
}

4.触发索引

4.1.建表语句

create table indexTest (
  id int,
  name string,
  price double
) using hudi
 location '/spark/hudi/'
 tblproperties (
  primaryKey ='id',
  type = 'mor',
  hoodie.index.type = 'BLOOM',
  hoodie.compact.inline = 'true'
 );

  HoodieIndexConfig控制配置项,Spark默认使用bloomFilter

private String getDefaultIndexType(EngineType engineType) {
  switch (engineType) {
    case SPARK:
      return HoodieIndex.IndexType.SIMPLE.name();
    case FLINK:
    case JAVA:
      return HoodieIndex.IndexType.INMEMORY.name();
    default:
      throw new HoodieNotSupportedException("Unsupported engine " + engineType);
  }
}

5.索引更新

<think>好的,我需要帮助用户了解如何在Hive中使用HudiHudi与Hive的集成方案。首先,我应该回顾用户提供的引用内容,看看里面有哪些关键信息。 根据引用[1],Hudi数据可以手动创建Hive表来映射,并且使用Hive SQL操作Hudi数据,尤其是MOR模式。引用[2]提到Hudi表在Hive Metastore中注册为标准表,可以像普通Hive表一样查询,同时支持时间旅行和增量查询。引用[3]提供了环境准备的步骤,比如拷贝必要的jar包到Hive的lib目录下。引用[4]则提到在写入数据时同步Hive参数,生成对应的Hive表。 首先,我需要整理集成步骤。环境准备是第一步,需要将Hudi相关的jar包复制到Hive的lib目录,如hudi-hadoop-mr-bundle和hudi-hive-sync-bundle,然后重启Hive服务。接下来,手动创建Hive表来映射Hudi数据,注意指定存储格式为org.apache.hudi,以及输入输出格式。对于自动同步表结构的情况,需要在写入Hudi数据时配置Hive Sync参数,比如hoodie.datasource.hive_sync.*相关参数,这样在写入时自动同步到Hive Metastore。 然后,查询Hudi表时,用户可以直接使用Hive SQL,但要注意可能需要设置一些参数,比如使用Hive的InputFormat。对于增量查询,需要设置hoodie.*.consume模式和相关参数,以获取特定时间后的变更数据。 用户可能的问题可能涉及性能优化、增量查询的具体配置,或者如何处理不同存储模式(如COW和MOR)在Hive中的表现。需要确保回答结构清晰,分步骤说明,并引用提供的资料来支持每个步骤。 最后,生成相关问题时要围绕Hive与Hudi集成的常见疑问,比如性能分析、优化方法、应用场景等。确保问题具体,能够帮助用户深入理解。</think>### Hive与Hudi集成方案详解 #### 1. **环境准备** 在Hive中集成Hudi需要先完成以下步骤: - **拷贝JAR包**:将Hudi的依赖包(如`hudi-hadoop-mr-bundle`和`hudi-hive-sync-bundle`)复制到Hive的`lib`目录,例如: ```bash cp /path/to/hudi-hadoop-mr-bundle-*.jar /opt/module/hive/lib/ cp /path/to/hudi-hive-sync-bundle-*.jar /opt/module/hive/lib/ ``` 完成后需重启Hive服务以加载新依赖[^3]。 #### 2. **手动创建Hive表映射Hudi数据** 若Hudi数据已存在,可通过手动建表实现映射。例如,对HDFS中存储的MOR模式表: ```sql CREATE EXTERNAL TABLE hudi_table STORED BY 'org.apache.hudi' LOCATION '/hdfs/path/to/hudi_table' TBLPROPERTIES ( 'input.format' = 'org.apache.hudi.hadoop.HoodieParquetInputFormat', 'output.format' = 'org.apache.hudi.hadoop.HoodieParquetOutputFormat' ); ``` 通过`STORED BY`指定Hudi存储格式,`LOCATION`指向Hudi数据路径[^1]。 #### 3. **自动同步表结构** 在写入Hudi数据时,可通过配置参数自动同步元数据到Hive: ```java // 示例写入配置(Spark) .writeOptions( hoodie.datasource.write.recordkey.field = "id", hoodie.datasource.hive_sync.database = "default", hoodie.datasource.hive_sync.table = "hudi_table", hoodie.datasource.hive_sync.enable = "true" ) ``` 此配置会同步表名、字段、分区等信息到Hive Metastore,无需手动建表[^4]。 #### 4. **查询Hudi表** - **全量查询**:直接使用标准Hive SQL: ```sql SELECT * FROM hudi_table WHERE partition_col='value'; ``` - **增量查询**:需设置Hive参数指定时间范围或提交版本: ```sql SET hoodie.hudi_table.consume.mode=INCREMENTAL; SET hoodie.hudi_table.consume.start.timestamp=20230101000000; SELECT * FROM hudi_table; ``` 此模式仅拉取指定时间后的增量数据[^2][^4]。 #### 5. **存储模式兼容性** - **COW表**:Hive直接读取最新版本数据,无中间状态。 - **MOR表**:需通过`hoodie.*.input.format`指定合并逻辑,例如: ```sql SET hive.input.format=org.apache.hudi.hadoop.hive.HoodieCombineHiveInputFormat; ``` 确保查询时合并实时文件和日志文件。 #### 6. **性能优化建议** - **分区剪枝**:利用Hive的分区过滤减少数据扫描范围。 - **压缩调度**:对MOR表定期执行压缩(`compaction`),减少小文件数量。 - **索引同步**:启用`hoodie.index.type=HIVE`保持Hive元数据与Hudi索引一致。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值