Hive 源码解读 CLI 入口 CliDriver

Posted @SmartSi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Hive 源码解读 CLI 入口 CliDriver相关的知识,希望对你有一定的参考价值。

Hive 版本:2.3.4

1. 主入口 main

在上篇文章介绍 cli.sh 脚本时,我们了解到 CLI 的主入口类为 org.apache.hadoop.hive.cli.CliDriver,其中入口即是 main 方法:

public static void main(String[] args) throws Exception 
  int ret = new CliDriver().run(args);
  System.exit(ret);

在 main 方法中创建了 CliDriver 对象并进行一些初始化:

public CliDriver() 
  // 创建一个 SessionState 内部保存大量配置信息
  SessionState ss = SessionState.get();
  // 配置对象
  conf = (ss != null) ? ss.getConf() : new Configuration();
  // 初始化 Logger
  Logger LOG = LoggerFactory.getLogger("CliDriver");
  if (LOG.isDebugEnabled()) 
    LOG.debug("CliDriver inited with classpath ", System.getProperty("java.class.path"));
  
  console = new LogHelper(LOG);

2. 执行入口 run

在 main 方法调用了 CliDriver 的 run 方法。首先构造了一个参数解析器,来解析用户命令行 CLI 选项,Hive CLI 选项具体用法可查阅 Hive CLI 命令解读。在第一阶段 CLI 选项解析 process_stage1 中主要解析 hiveconf、define 以及 hivevar 选项定义的自定义变量和属性:

// 参数解析器
OptionsProcessor oproc = new OptionsProcessor();
if (!oproc.process_stage1(args)) 
  return 1;

public boolean process_stage1(String[] argv) 
    try 
      // 调用解析器的 parse 方法解析参数
      commandLine = new GnuParser().parse(options, argv);
      // --hiveconf 属性选项
      Properties confProps = commandLine.getOptionProperties("hiveconf");
      for (String propKey : confProps.stringPropertyNames()) 
        if (propKey.equalsIgnoreCase("hive.root.logger")) 
          CommonCliOptions.splitAndSetLogger(propKey, confProps);
         else 
          System.setProperty(propKey, confProps.getProperty(propKey));
        
      
      // --define 自定义变量选项
      Properties hiveVars = commandLine.getOptionProperties("define");
      for (String propKey : hiveVars.stringPropertyNames()) 
        hiveVariables.put(propKey, hiveVars.getProperty(propKey));
      
      // --hivevar 自定义变量选项
      Properties hiveVars2 = commandLine.getOptionProperties("hivevar");
      for (String propKey : hiveVars2.stringPropertyNames()) 
        hiveVariables.put(propKey, hiveVars2.getProperty(propKey));
      
     catch (ParseException e) 
      System.err.println(e.getMessage());
      printUsage();
      return false;
    
    return true;

OptionsProcessor 如何解析 Hive CLI 选项可查阅 Apache Common CLI 如何实现命令行解析

然后定义标准化输入输出和错误输出流保存在 CliSessionState 会话对象中:

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;

CliSessionState 使用 HiveConf 进行实例化,继承了 SessionState 类,是专门为 Hive CLI 创建的一个会话对象。用来记录 -database-e-f-hiveconf 以及 -i 选项指定的值。

在第二阶段 CLI 选项解析 process_stage2 中主要解析 -H-S--database-e-f-v 以及-i 选项:

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

public boolean process_stage2(CliSessionState ss) 
    ss.getConf();
    // -H 帮助选项
    if (commandLine.hasOption('H')) 
      printUsage();
      return false;
    
    // -S 静默选项
    ss.setIsSilent(commandLine.hasOption('S'));
    // --database 数据库选项
    ss.database = commandLine.getOptionValue("database");
    // -e 单 HQL 命令执行选项
    ss.execString = commandLine.getOptionValue('e');
    // -f HQL 文件执行选项
    ss.fileName = commandLine.getOptionValue('f');
    // -v 详细选项
    ss.setIsVerbose(commandLine.hasOption('v'));
    // -i 初始化选项
    String[] initFiles = commandLine.getOptionValues('i');
    if (null != initFiles) 
      ss.initFiles = Arrays.asList(initFiles);
    
    // -e 和 -f 不能同时指定
    if (ss.execString != null && ss.fileName != null) 
      System.err.println("The '-e' and '-f' options cannot be specified simultaneously");
      printUsage();
      return false;
    
    //
    if (commandLine.hasOption("hiveconf")) 
      Properties confProps = commandLine.getOptionProperties("hiveconf");
      for (String propKey : confProps.stringPropertyNames()) 
        ss.cmdProperties.setProperty(propKey, confProps.getProperty(propKey));
      
    
    return true;

将解析出的选项内容填充 CliSessionState 会话对象。例如,用户输入了 -e,就把 -e 对应的字符串赋值给 CliSessionState 的 execString 成员。

然后将用户命令行输入的配置信息和变量等覆盖 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());

读取配置提示和替换变量。用户可以在查询中引用变量,Hive 会先使用变量值替换掉查询中的变量引用,然后才会将查询语句提交给查询处理器:

// 提示符
prompt = conf.getVar(HiveConf.ConfVars.CLIPROMPT);
// 变量替换
prompt = new VariableSubstitution(new HiveVariableSource() 
  @Override
  public Map<String, String> getHiveVariable() 
    return SessionState.get().getHiveVariables();
  
).substitute(conf, prompt);
prompt2 = spacesForString(prompt);

开启会话状态:

if (HiveConf.getBoolVar(conf, ConfVars.HIVE_CLI_TEZ_SESSION_ASYNC)) 
  SessionState.beginStart(ss, console);
 else 
  SessionState.start(ss);

最重要的的一步就是执行 CLI 驱动程序:

try 
  return executeDriver(ss, conf, oproc);
 finally 
  ss.resetThreadName();
  ss.close();

3. 执行 CLI 驱动

在上面说到最重要的的一步就是执行 CLI 驱动程序方法 executeDriver。下面具体看一下如何执行的。首先从 OptionsProcessor 解析器中获取解析到的 Hive 变量并存储到 SessionState 的 hiveVariables 变量中:

// oproc 为 OptionsProcessor
cli.setHiveVariables(oproc.getHiveVariables());
// 存储到 SessionState 的 hiveVariables 变量中
public void setHiveVariables(Map<String, String> hiveVariables) 
  SessionState.get().setHiveVariables(hiveVariables);

如果 Hive CLI 命令行中指定了 --database 选项,需要使用指定的数据库,即执行 use <database>; HQL 语句:

cli.processSelectDatabase(ss);
// 执行切换数据库HQL语句
public void processSelectDatabase(CliSessionState ss) throws IOException 
  String database = ss.database;
  if (database != null) 
    int rc = processLine("use " + database + ";");
    if (rc != 0) 
      System.exit(rc);
    
  

如果 Hive CLI 命令行中指定了 -e '<quoted-query-string>' 选项,需要调用 processLine 来执行 HQL 语句:

if (ss.execString != null) 
  int cmdProcessStatus = cli.processLine(ss.execString);
  return cmdProcessStatus;

processLine 如何处理 HQL 语句后续会详细介绍

如果 Hive CLI 命令行中指定了 -f <query-file> 选项,需要调用 processFile 来执行文件中的一个或者多个 HQL 语句:

try 
  if (ss.fileName != null) 
    return cli.processFile(ss.fileName);
  
 catch (FileNotFoundException e) 
  System.err.println("Could not open input file for reading. (" + e.getMessage() + ")");
  return 3;

processFile 如何处理文件中的一个或者多个 HQL 语句后续会详细介绍

如果 Hive 的执行引擎选择的是 mr 会打印警告信息 Hive-on-MR is deprecated in Hive 2 and may not be available in the future versions. Consider using a different execution engine (i.e. spark, tez) or using Hive 1.X releases.,提示你要切换到 Spark 或者 Tez 执行引擎上:

if ("mr".equals(HiveConf.getVar(conf, ConfVars.HIVE_EXECUTION_ENGINE))) 
  console.printInfo(HiveConf.generateMrDeprecationWarning());

最后最重要的是从标准化输入中解析出一个个完整 HQL 语句交由 processLine 来执行:

String line;
int ret = 0;
String prefix = "";
String curDB = getFormattedDb(conf, ss);
String curPrompt = prompt + curDB;
String dbSpaces = spacesForString(curDB);

while ((line = reader.readLine(curPrompt + "> ")) != null) 
  // 换行
  if (!prefix.equals("")) 
    prefix += '\\n';
  
  // 遇到注释跳过
  if (line.trim().startsWith("--")) 
    continue;
  
  // 直到遇到分号结尾 表示一个完整 HQL 语句
  if (line.trim().endsWith(";") && !line.trim().endsWith("\\\\;")) 
    line = prefix + line;
    // HQL 语句的执行实际上交由 processLine 处理
    ret = cli.processLine(line, true);
    prefix = "";
    curDB = getFormattedDb(conf, ss);
    curPrompt = prompt + curDB;
    dbSpaces = dbSpaces.length() == curDB.length() ? dbSpaces : spacesForString(curDB);
   else 
    prefix = prefix + line;
    curPrompt = prompt2 + dbSpaces;
    continue;
  

以上是关于Hive 源码解读 CLI 入口 CliDriver的主要内容,如果未能解决你的问题,请参考以下文章

Hive 源码解读 CliDriver HQL 语句拆分

Hive 源码解读 CliDriver HQL 语句拆分

Hive 源码解读 CliDriver HQL 语句拆分

Hive CLI 命令解读

Hive CLI 命令解读

Hive 源码解读 CliDriver HQL 命令处理