Apache Hudi初探(十)(与spark的结合)--hudi的Compaction操作

9 篇文章 0 订阅

背景

在之前的文章Apache Hudi初探(六)(与spark的结合)
中,我们没有过多的解释Spark中hudi Compaction的实现,在这里详细说一下
注意:在hudi中有同步,异步Compaction的概念,为了保证写入的及时性和数据读取的时效性,hudi在一步compaction的过程中会引入一个后台线程进行compaction,所以异步Compaction又分同步compaction计划生成和异步compaction计划生成和最终的Compaction计划的执行(而compaction计划的执行是在后台异步执行的)
对应的配置为:
hoodie.compact.inline 是否是同步进行Compaction
hoodie.compact.schedule.inline 是够是异步Compaction计划生成

详说杂谈

这里直接以同步Compaction为例,直接到BaseHoodieWriteClient runTableServicesInline方法

   if (config.inlineCompactionEnabled()) {
     runAnyPendingCompactions(table);
     metadata.addMetadata(HoodieCompactionConfig.INLINE_COMPACT.key(), "true");
     inlineCompaction(extraMetadata);
   } else {
     metadata.addMetadata(HoodieCompactionConfig.INLINE_COMPACT.key(), "false");
   }

假设hoodie.compact.inlinetrue,也就是config.inlineCompactionEnabled()true,那就直接执行Compaction流程:

  • runAnyPendingCompactions
    重新运行上次失败的Compaction计划

    • 过滤出没有完成的Compaction计划,并执行Compact操作,具体的为SparkRDDWriteClient.compact
      这里我们先略过,假设没有pending的Compaction操作,重点看inlineCompaction
  • metadata.addMetadata
    把属性hoodie.compact.inline记录到元数据中去

  • inlineCompaction执行当前Commit的compaction操作

      Option<String> compactionInstantTimeOpt = inlineScheduleCompaction(extraMetadata);
       compactionInstantTimeOpt.ifPresent(compactInstantTime -> {
         // inline compaction should auto commit as the user is never given control
         compact(compactInstantTime, true);
       });
    
    • inlineScheduleCompaction
      最终会调用到scheduleTableServiceInternal如下:
       ...
       case COMPACT:
         LOG.info("Scheduling compaction at instant time :" + instantTime);
         Option<HoodieCompactionPlan> compactionPlan = createTable(config, hadoopConf)
             .scheduleCompaction(context, instantTime, extraMetadata);
         return compactionPlan.isPresent() ? Option.of(instantTime) : Option.empty();
       ...
      
      这里会立马调度生成一个Compaction的计划,最终会调用ScheduleCompactionActionExecutor.execute方法
       HoodieCompactionPlan plan = scheduleCompaction();
       if (plan != null && (plan.getOperations() != null) && (!plan.getOperations().isEmpty())) {
         extraMetadata.ifPresent(plan::setExtraMetadata);
         HoodieInstant compactionInstant =
             new HoodieInstant(HoodieInstant.State.REQUESTED, HoodieTimeline.COMPACTION_ACTION, instantTime);
         try {
           table.getActiveTimeline().saveToCompactionRequested(compactionInstant,
               TimelineMetadataUtils.serializeCompactionPlan(plan));
         } catch (IOException ioe) {
           throw new HoodieIOException("Exception scheduling compaction", ioe);
         }
         return Option.of(plan);
       }
      
      • scheduleCompaction
        该方法主要是生成一个调度Compaction的计划

        boolean compactable = needCompact(config.getInlineCompactTriggerStrategy());
        if (compactable) {
          LOG.info("Generating compaction plan for merge on read table " + config.getBasePath());
          try {
            SyncableFileSystemView fileSystemView = (SyncableFileSystemView) table.getSliceView();
            Set<HoodieFileGroupId> fgInPendingCompactionAndClustering = fileSystemView.getPendingCompactionOperations()
                .map(instantTimeOpPair -> instantTimeOpPair.getValue().getFileGroupId())
                .collect(Collectors.toSet());
            // exclude files in pending clustering from compaction.
            fgInPendingCompactionAndClustering.addAll(fileSystemView.getFileGroupsInPendingClustering().map(Pair::getLeft).collect(Collectors.toSet()));
            context.setJobStatus(this.getClass().getSimpleName(), "Compaction: generating compaction plan: " + config.getTableName());
            return compactor.generateCompactionPlan(context, table, config, instantTime, fgInPendingCompactionAndClustering);
        
        • needCompact(config.getInlineCompactTriggerStrategy())
          hoodie.compact.inline.trigger.strategy默认是NUM_COMMITS策略,也就是按照delta commit的次数来的,次数也就是按照hoodie.compact.inline.max.delta.commits(默认是5),也就是5次delta commit的提交就会进行产生一个Compaction计划
        • fgInPendingCompactionAndClustering =
          获取一系列处于pending的FileGroupId,这在产生Compaction一系列的CompactionOperation的时候是会被排除掉的
        • compactor.generateCompactionPlan 产生一个Compaction的计划
          排除正在pending的FileGroupId后,生成由baseFile,partitionPath,logFiles,还有Compaction策略组成的CompactionOperation
          hoodie.compaction.strategy默认策略是LogFileSizeBasedCompactionStrategy,最后组装成HoodieCompactionOperation数据的数据结构,最后返回一个HoodieCompactionPlan
      • table.getActiveTimeline().saveToCompactionRequeste
        这步操作主要是把生成的Compaction plan序列化成字节,并保存在相应的文件中,并生成一个Compaction的Request

    • compact 真正的执行Compaction操作
      最终调用的是SparkRDDWriteClient的compact
         ...
        HoodieWriteMetadata<HoodieData<WriteStatus>> writeMetadata = table.compact(context, compactionInstantTime);
        HoodieWriteMetadata<JavaRDD<WriteStatus>> compactionMetadata = writeMetadata.clone(HoodieJavaRDD.getJavaRDD(writeMetadata.getWriteStatuses()));
        if (shouldComplete && compactionMetadata.getCommitMetadata().isPresent()) {
          completeTableService(TableServiceType.COMPACT, compactionMetadata.getCommitMetadata().get(), table, compactionInstantTime);
        }
        return compactionMetadata;
      
      • table.compact(context, compactionInstantTime) 实际进行Compaction的操作
        最终调用的是RunCompactionActionExecutor.execute方法
           HoodieCompactionPlan compactionPlan =
             CompactionUtils.getCompactionPlan(table.getMetaClient(), instantTime);
           ...
            HoodieData<WriteStatus> statuses = compactor.compact(
                   context, compactionPlan, table, configCopy, instantTime, compactionHandler);
           ...
           List<HoodieWriteStat> updateStatusMap = statuses.map(WriteStatus::getStat).collectAsList();
           HoodieCommitMetadata metadata = new HoodieCommitMetadata(true);
           return compactionMetadata
        
        
        • compactionPlan获取上面inlineScheduleCompaction生成的Compaction 计划
        • compactor.compact 执行 Compact操作
          最终调用的是HoodieSparkCopyOnWriteTable的handleUpdate和handleInsert方法完成数据的更新和插入
          这里同时会把Request状态的compcation变成Inflight的状态
        • compactionMetadata返回一些元数据信息
      • completeTableService
        该方法主要是把compaction的元数据信息写入到元数据,并把Inflight状态的compcation变成Complete的状态
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用maven对hudi进行构建并与spark集成的步骤: 1. 下载hudi源码 可以从hudi的官方github仓库中下载源码,链接为:https://github.com/apache/hudi 2. 构建hudi 进入hudi源码目录,使用以下命令进行构建: ``` mvn clean package -DskipTests -Dspark.version=3.1.1 -Dscala-2.12 ``` 其中,-DskipTests表示跳过测试,-Dspark.version指定spark版本,-Dscala-2.12指定scala版本。 构建完成后,会在hudi的target目录下生成hudi-xxx.jar包。 3. 将hudispark集成 将hudi-xxx.jar包加入到spark的classpath中,可以通过以下命令进行添加: ``` export SPARK_DIST_CLASSPATH=$(hadoop classpath):/path/to/hudi-xxx.jar ``` 其中,/path/to/hudi-xxx.jar需要替换为hudi-xxx.jar包的实际路径。 4. 使用spark-shell操作hudi 启动spark-shell,运行以下命令,可以创建一个hudi表: ``` import org.apache.spark.sql.SaveMode import org.apache.hudi.QuickstartUtils._ val tableName = "hudi_test" val basePath = "/tmp/hudi_test" val dataGen = new DataGenerator val inserts = dataGen.generateInserts(10) val df = spark.read.json(spark.sparkContext.parallelize(inserts, 2)) df.write.format("org.apache.hudi"). options(getQuickstartWriteConfigs). option(PRECOMBINE_FIELD_OPT_KEY, "ts"). option(RECORDKEY_FIELD_OPT_KEY, "uuid"). option(PARTITIONPATH_FIELD_OPT_KEY, "partitionpath"). option(TABLE_NAME, tableName). mode(SaveMode.Append). save(basePath) ``` 运行以上命令后,会在/tmp/hudi_test目录下创建一个hudihudi_test。 接下来,可以使用spark-shell的API对hudi表进行操作,例如: ``` import org.apache.spark.sql.functions._ val df = spark.read.format("org.apache.hudi"). load("/tmp/hudi_test/*/*/*/*") df.show() ``` 以上命令会读取hudi_test表的数据,并展示结果。 至此,使用maven对hudi进行构建并与spark集成的步骤结束,可以使用spark-shell对hudi进行操作了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值