Hive源码分析一

问题:
1、hive的入口程序
2、hive的local化
3、hivesql执行过程
4、hive的解析和鉴权—下节

一 :hive的入口程序
1、从 cli.sh文件我们可以看到,调用了类CliDriver进行初始化操作

  CLASS=org.apache.hadoop.hive.cli.CliDriver
  execHiveCmd $CLASS "$@"

2、 查看 CliDriver类的main方法

int ret = new CliDriver().run(args);

3、先做hive输入参数的判断是否符合要求

  OptionsProcessor oproc = new OptionsProcessor();
    if (!oproc.process_stage1(args)) {
      return 1;
}

4、初始化Log4j日志组件

 boolean logInitFailed = false;
    String logInitDetailMessage;
    try {
      logInitDetailMessage = LogUtils.initHiveLog4j();
    } catch (LogInitializationException e) {
      logInitFailed = true;
      logInitDetailMessage = e.getMessage();
    }

5、SessionState封装了一个会话的关联的数据
包括配置信息HiveConf,输入输出流,指令类型,用户名称、IP地址等等。SessionState 是一个与线程关联的静态本地变量ThreadLocal,任何一个线程都对应一个SessionState,能够在Hive代码的任何地方获取到(大量被使用到),以返回用户相关或者配置信息等。

CliSessionState ss = new CliSessionState(new HiveConf(SessionState.class));
    ss.in = System.in;
    try {
      ss.out = new PrintStream(System.out, true, "UTF-8");
      ss.info = new PrintStream(System.err, true, "UTF-8");
      ss.err = new CachingPrintStream(System.err, true, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      return 3;
    }

6、根据stage1解析的参数内容
填充CliSessionState的字符串,比如用户输入了-e 则这个stage就把-e 对应的字符串赋值给CliSessionState的 execString成员。

 if (!oproc.process_stage2(ss)) {
      return 2;
}

CliDriver cli = new CliDriver();
 cli.setHiveVariables(oproc.getHiveVariables());

7、在允许打印输出的模式下,如果日志初始化失败,打印失败信息

if (!ss.getIsSilent()) {
      if (logInitFailed) {
        System.err.println(logInitDetailMessage);
      } else {
        SessionState.getConsole().printInfo(logInitDetailMessage);
      }
}

8、将用户命令行输入的配置信息和变量等,覆盖HiveConf的默认值

HiveConf conf = ss.getConf();
    for (Map.Entry<Object, Object> item : ss.cmdProperties.entrySet()) {
      conf.set((String) item.getKey(), (String) item.getValue());
      ss.getOverriddenConfigurations().put((String) item.getKey(), (String) item.getValue());
}

9、设置当前回话状态,执行CLI驱动

 int ret = 0;
    try {
      ret = executeDriver(ss, conf, oproc);
    } catch (Exception e) {
      ss.close();
      throw e;
}

二、hive的local化
在进入executeDriver之前,我们可以认为Hive处理的是用户进入Hive程序的指令,到此用户已经进入了Hive,Cli的Driver将不断读取用户的HiveQL语句并解析,提交给Driver。executeDriver函数内部核心的代码是通过while循环不断按行读取用户的输入,然后调用ProcessLine拼接一条命令cmd,传递给processCmd处理用户输入。下面就来看看processCmd函数。

CliDriver cli = new CliDriver();
if (ss.execString != null) {
      int cmdProcessStatus = cli.processLine(ss.execString);
      return cmdProcessStatus;
}

首先是设置当前clisession的用户上一条指令,然后使用正则表达式,将用户输入的指令从空格,制表符等出断开(tokenizeCmd函数),得到token数组

CliSessionState ss = (CliSessionState) SessionState.get();
    ss.setLastCommand(cmd);
    // Flush the print stream, so it doesn't include output from the last command
    ss.err.flush();
    String cmd_trimmed = cmd.trim();
    String[] tokens = tokenizeCmd(cmd_trimmed);
int ret = 0;

然后根据用户的输入,进行不同的处理,这边的处理主要包括:
1、quit或exit: 关闭回话,退出hive
2、source: 文件处理
3、! 开头: 调用Linux系统的shell执行指令
4、本地模式:创建CommandProcessor, 执行用户指令
基于这些我们只分析local模式的

try {
        CommandProcessor proc = CommandProcessorFactory.get(tokens, (HiveConf) conf);
        ret = processLocalCmd(cmd, proc, ss);
      } catch (SQLException e) {
        console.printError("Failed processing command " + tokens[0] + " " + e.getLocalizedMessage(),
          org.apache.hadoop.util.StringUtils.stringifyException(e));
        ret = 1;
      }

其中,CommandProcessor是一个接口类,定义如下:

public interface CommandProcessor {

  void init();

  CommandProcessorResponse run(String command) throws CommandNeedRetryException;
}

CommandProcessorFactory根据用户指令生成的tokens和配置文件,返回CommandProcessor的一个具体实现
  public static CommandProcessor get(String[] cmd, HiveConf conf)
      throws SQLException {
    CommandProcessor result = getForHiveCommand(cmd, conf);
    if (result != null) {
      return result;
    }
    if (isBlank(cmd[0])) {
      return null;
    } else {
      if (conf == null) {
        return new Driver();
      }
      Driver drv = mapDrivers.get(conf);
      if (drv == null) {
        drv = new Driver();
        mapDrivers.put(conf, drv);
      }
      drv.init();
      return drv;
    }
  }

到此Hive对用户的一个指令cmd,配置了回话状态CliSessionState,选择了一个合适的CommandProcessor, CliDriver将进行他的最后一步操作,提交用户的查询到指定的CommandProcessor,并获取结果。这一切都是在processLocalCmd中执行的。
processLocalCmd函数的主体是一个如下的循环:

do {
      try {
        needRetry = false;
        if (proc != null) {
          //如果CommandProcessor是Driver实例
          if (proc instanceof Driver) {
            Driver qp = (Driver) proc;
            //获取标准输出流,打印结果信息
            PrintStream out = ss.out;
            long start = System.currentTimeMillis();
            if (ss.getIsVerbose()) {
              out.println(cmd);
            }

            qp.setTryCount(tryCount);
            //driver实例运行用户指令,获取运行结果响应码
            ret = qp.run(cmd).getResponseCode();
            if (ret != 0) {
              qp.close();
              return ret;
            }

            // 统计指令的运行时间
            long end = System.currentTimeMillis();
            double timeTaken = (end - start) / 1000.0;

            ArrayList<String> res = new ArrayList<String>();
             //打印查询结果的列名称
            printHeader(qp, out);

            // 打印查询结果
            int counter = 0;
            try {
              if (out instanceof FetchConverter) {
                ((FetchConverter)out).fetchStarted();
              }
              while (qp.getResults(res)) {
                for (String r : res) {
                  out.println(r);
                }

                counter += res.size();
                res.clear();
                if (out.checkError()) {
                  break;
                }
              }
            } catch (IOException e) {
              console.printError("Failed with exception " + e.getClass().getName() + ":"
                  + e.getMessage(), "\n"
                  + org.apache.hadoop.util.StringUtils.stringifyException(e));
              ret = 1;
            }
            //关闭结果
            int cret = qp.close();
            if (ret == 0) {
              ret = cret;
            }

            if (out instanceof FetchConverter) {
              ((FetchConverter)out).fetchFinished();
            }

            console.printInfo("Time taken: " + timeTaken + " seconds" +
                (counter == 0 ? "" : ", Fetched: " + counter + " row(s)"));
          } else {
            //如果proc不是Driver,也就是用户执行的是非SQL查询操作,直接执行语句,不自信FetchResult的操作
            String firstToken = tokenizeCmd(cmd.trim())[0];
            String cmd_1 = getFirstCmd(cmd.trim(), firstToken.length());

            if (ss.getIsVerbose()) {
              ss.out.println(firstToken + " " + cmd_1);
            }
            CommandProcessorResponse res = proc.run(cmd_1);
            if (res.getResponseCode() != 0) {
              ss.out.println("Query returned non-zero code: " + res.getResponseCode() +
                  ", cause: " + res.getErrorMessage());
            }
            ret = res.getResponseCode();
          }
        }
      } catch (CommandNeedRetryException e) {
        //如果执行过程中出现异常,修改needRetry标志,下次循环是retry。
        console.printInfo("Retry query with a different approach...");
        tryCount++;
        needRetry = true;
      }
    } while (needRetry);

前面对函数中关键的执行语句已经给出了注释,这里单独对printHeader进行一下说明。
printHeader函数通过调用driver.getSchema.getFiledSchema,获取查询结果的列集合 ,然后依次打印出列名。

private void printHeader(Driver qp, PrintStream out) {
    List<FieldSchema> fieldSchemas = qp.getSchema().getFieldSchemas();
    if (HiveConf.getBoolVar(conf, HiveConf.ConfVars.HIVE_CLI_PRINT_HEADER)
          && fieldSchemas != null) {
      // Print the column names
      boolean first_col = true;
      for (FieldSchema fs : fieldSchemas) {
        if (!first_col) {
          out.print('\t');
        }
        out.print(fs.getName());
        first_col = false;
      }
      out.println();
    }
  }

下节 我们重点分析 程序中的 ret = qp.run(cmd).getResponseCode(); 这段代码做了啥事情
主要将sql进行解析了鉴权

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值