玩转大数据js css htmlJava

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

2022-10-25  本文已影响0人  AlienPaul

问题描述

我们通过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已合并,新版本中此问题已解决。

声明

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

上一篇下一篇

猜你喜欢

热点阅读