美文网首页玩转大数据js css htmlJava
HUDI 0.11.1 cli使用问题和分析解决

HUDI 0.11.1 cli使用问题和分析解决

作者: AlienPaul | 来源:发表于2022-10-25 12:19 被阅读0次

    问题描述

    我们通过Flink插入一张演示用Hudi表,SQL语句如下:

    CREATE TABLE t1(
      uuid VARCHAR(20),
      name VARCHAR(10),
      age INT,
      ts TIMESTAMP(3),
      `partition` VARCHAR(20)
    )
    PARTITIONED BY (`partition`)
    WITH (
      'connector' = 'hudi',
      'path' = 'hdfs:///path/to/table/',
      'table.type' = 'COPY_ON_WRITE'
    );
    
    -- insert data using values
    INSERT INTO t1 VALUES
      ('id1','Danny',23,TIMESTAMP '1970-01-01 00:00:01','par1'),
      ('id2','Stephen',33,TIMESTAMP '1970-01-01 00:00:02','par1'),
      ('id3','Julian',53,TIMESTAMP '1970-01-01 00:00:03','par2'),
      ('id4','Fabian',31,TIMESTAMP '1970-01-01 00:00:04','par2'),
      ('id5','Sophia',18,TIMESTAMP '1970-01-01 00:00:05','par3'),
      ('id6','Emma',20,TIMESTAMP '1970-01-01 00:00:06','par3'),
      ('id7','Bob',44,TIMESTAMP '1970-01-01 00:00:07','par4'),
      ('id8','Han',56,TIMESTAMP '1970-01-01 00:00:08','par4');
    

    然后我们进入Hudi cli,执行show fsview all命令,查看HUDI数据目录的文件存储结构。我们发现无法查询到任何内容。但是通过hdfs dfs -ls /path/to/table命令查看,数据文件确实是存在的。

    问题截图

    问题分析和临时解决方案

    第一部分

    我们首先怀疑的地方是HUDI。找到HUDI处理这条命令的源代码。如下所示:

    @CliCommand(value = "show fsview all", help = "Show entire file-system view")
      public String showAllFileSlices(
          @CliOption(key = {"pathRegex"}, help = "regex to select files, eg: 2016/08/02",
              unspecifiedDefaultValue = "*/*/*") String globRegex,
          @CliOption(key = {"baseFileOnly"}, help = "Only display base files view",
              unspecifiedDefaultValue = "false") boolean baseFileOnly,
          @CliOption(key = {"maxInstant"}, help = "File-Slices upto this instant are displayed",
              unspecifiedDefaultValue = "") String maxInstant,
          @CliOption(key = {"includeMax"}, help = "Include Max Instant",
              unspecifiedDefaultValue = "false") boolean includeMaxInstant,
          @CliOption(key = {"includeInflight"}, help = "Include Inflight Instants",
              unspecifiedDefaultValue = "false") boolean includeInflight,
          @CliOption(key = {"excludeCompaction"}, help = "Exclude compaction Instants",
              unspecifiedDefaultValue = "false") boolean excludeCompaction,
          @CliOption(key = {"limit"}, help = "Limit rows to be displayed", unspecifiedDefaultValue = "-1") Integer limit,
          @CliOption(key = {"sortBy"}, help = "Sorting Field", unspecifiedDefaultValue = "") final String sortByField,
          @CliOption(key = {"desc"}, help = "Ordering", unspecifiedDefaultValue = "false") final boolean descending,
          @CliOption(key = {"headeronly"}, help = "Print Header Only",
              unspecifiedDefaultValue = "false") final boolean headerOnly)
          throws IOException {
    
        HoodieTableFileSystemView fsView = buildFileSystemView(globRegex, maxInstant, baseFileOnly, includeMaxInstant,
            includeInflight, excludeCompaction);
        // ...
        // 后面的代码省略
      }
    

    我们注意到pathRegex这个参数(在方法中的参数名为globRegex)。这个参数的默认值提供的是有3个分区字段的情况。和我们上面的例子不同。上面的例子中只有1个分区字段。为了验证方便,我们修改源代码,将globRegex变量通过日志打印出来(中间步骤省略)。编译后再次执行show fsview all命令。我们惊奇的发现,globRegex变量和我们输入的pathRegex参数不一致。我们传入*/*/*,但globRegex得到的却是*/**。到这里,我们开始怀疑Shell框架也有问题。

    结合@CliOption注解这种写法和pom文件中的依赖,不难发现Hudi cli使用的Shell框架为spring-shell 1.2.0.RELEASE。我们尝试着找spring-shell源代码中是否有处理符号相关的内容。发现有这么一段代码。它位于org.springframework.shell.core.AbstractShell::executeCommand,spring shell本意用这段代码处理shell命令中的块注释和单行注释。碰巧我们的参数*/*/*就包含了块注释开始和结束的符号。spring-shell的这个功能在此场景下明显是“画蛇添足”了。

    我们分析下spring-shell是如何处理块注释的。org.springframework.shell.core.AbstractShell::executeCommand方法代码如下所示:

    // We support simple block comments; ie a single pair per line
    // spring shell只支持行内的块注释
    // inBlockComment标记了目前解析到了块注释之内
    // 如果不在块注释之内,并且命令行字符串同时包含/*和*/
    if (!inBlockComment && line.contains("/*") && line.contains("*/")) {
        blockCommentBegin();
        // 我们将最后一个/*,连同它后面的字符串都裁剪掉
        String lhs = line.substring(0, line.lastIndexOf("/*"));
        // 如果含有块注释结束*/符号
        if (line.contains("*/")) {
            // 裁剪掉命令行最有一个*/及其之前的所有内容,和lhs拼接在一起,这样就得到了/*和*/之外的字符串内容
            line = lhs + line.substring(line.lastIndexOf("*/") + 2);
            blockCommentFinish();
        } else {
            line = lhs;
        }
    }
    if (inBlockComment) {
        if (!line.contains("*/")) {
            return new CommandResult(true);
        }
        blockCommentFinish();
        line = line.substring(line.lastIndexOf("*/") + 2);
    }
    // We also support inline comments (but only at start of line, otherwise valid
    // command options like http://www.helloworld.com will fail as per ROO-517)
    if (!inBlockComment && (line.trim().startsWith("//") || line.trim().startsWith("#"))) { // # support in ROO-1116
        line = "";
    }
    

    spring-shell针对块注释的处理逻辑在上面代码注释中我们已分析,但是它还有bug。出现bug的地方正是我们的场景。对于*/*/*。首先裁剪掉最后一个/*及其后面的字符串,此时lhs*/*。然后裁剪掉最后一个*/及其前面的所有内容,得到*,然后将二者拼接在一起,最终结果为*/**。这明显是有问题的。spring-shell没考虑到*/会在/*之前这种情况。上面是分析这段代码时候顺便发现的问题。不过不用去纠结这一点,回到本篇开篇的问题,我们只需要将spring-shell的块注释解析功能屏蔽掉就可以了。

    阅读了spring-shell 1.2.0.RELEASE的官方文档和查看注释解析的源代码,并没有发现官方为我们预留了什么配置,可以用来关闭注释解析这个特性。我们只能自己动手。将上面代码片段的内容注释掉,重新编译spring-shell。将编译好的jar包覆盖Hudi cli的同名文件后,注释解析问题解决。获取到的globRegex终于和pathRegex传入的参数值相同。

    第二部分

    解决了上面的问题,我们需要分析pathRegex默认参数和我们例子不匹配的问题。pathRegex的默认值为*/*/*。对于一个有三个分区字段的表(比如按照时间的年、月、日分区)。这种表在存储的时候,表数据文件以年/月/日这种三级目录的结构组织。目录结构正好和*/*/*正则表达式匹配。但是对于我们的例子呢?只有一个分区字段,明显不是*/*/*这种目录结构。所以说我们需要手工指定pathRegex参数。参数中的目录层级需要和分区表的分区字段数匹配。这一点非常重要。

    最终解决

    本人提交了Hudi社区issue和patch:[HUDI-4485] Hudi cli got empty result for command show fsview all - ASF JIRA (apache.org)。由于spring-shell 1.2.0.RELEASE版本过于老旧,和Spring社区联系后表示这个版本不再维护,建议升级到spring-shell 2.x最新版本。一直使用老版本也不利于以后新功能的开发。经过版本特性调研和试用发现spring-shell 2.1.1版本默认不解析块注释。因此,最终决定通过升级spring-shell版本的方式,解决了这个问题。目前PR已合并,新版本中此问题已解决。

    声明

    本博客为作者原创,欢迎大家参与讨论和批评指正。如需转载请注明出处。

    相关文章

      网友评论

        本文标题:HUDI 0.11.1 cli使用问题和分析解决

        本文链接:https://www.haomeiwen.com/subject/zjgnzrtx.html