Hive源码解读(3)命令行交互界面(Command Line Interface)

命令行界面,是Hive中很重要的一个服务,也是我们在使用hive的过程中使用最频繁的服务。

Cli入口类在哪里?

如果你读了前面的几篇文章,细心的话应该还记得在cli服务的脚本中,定义了主类org.apache.hadoop.hive.cli.CliDriver。入口即是main方法,main方法调用了run方法。

关键属性

// 提示信息
  public static String prompt = null;
// 如果没有以分号结尾时,暂存提示信息
  public static String prompt2 = null; // when ';' is not yet seen
// 从远程服务器一次拉取的行数
  public static final int LINES_TO_FETCH = 40; // number of lines to fetch in batch from remote hive server
  public static final int DELIMITED_CANDIDATE_THRESHOLD = 10;

// 启动客户端初始化的文件
  public static final String HIVERCFILE = ".hiverc";
// 日志相关
  private final LogHelper console;
// 负责从控制台接收用户输入的交互命令  
protected ConsoleReader reader;
// 配置
  private Configuration conf;

run方法

调用链:
【参数处理器(OptionsProcessor)第一阶段处理】
—— >
【log4j初始化(initHiveLog4j)】
——>
【初始化客户端会话状态(CliSessionState)】
——>
【参数处理器第二阶段处理】
——>
【初始化元数据的私有类】
——>
【初始化缓存视图】
——>
【运行Driver(executeDriver)】
看到这里,我们发现,核心在executeDriver方法。

executeDriver方法

  • 1,选择数据库database;
  • 2,如果是hive -e,则执行用户指定的命令。否则进入3;
  • 3, 如果是hive -f,则执行用户指定的文件。否则进入4;
  • 4, 如果计算引擎指定了mr,则发出警告提示(将在未来版本提供支持)。进入5;
  • 5,启动控制台读词器(ConsoleReader);
  • 6,读取命令行的输入,并执行。

选择数据库。

cli.processSelectDatabase(ss);

如果是hive -e,则执行用户指定的命令:

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

如果是hive -f,则执行用户指定的文件:

    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;
    }

如果是设定了执行引擎是mr,则告警提示:

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

启动命令行读词器,并读取命令执行。
这里是一个while循环,直到遇到分号的时候才执行命令。

setupConsoleReader();
...
while ((line = reader.readLine(curPrompt + "> ")) != null) {
      if (!prefix.equals("")) {
        prefix += '\n';
      }
      if (line.trim().startsWith("--")) {
        continue;
      }
      if (line.trim().endsWith(";") && !line.trim().endsWith("\\;")) {
        line = prefix + line;
        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命令之后,实质是调用了hive脚本,而hive脚本实质是调用了bin/ext目录下的所有.sh结尾的shell脚本。其中cli.sh脚本是命令行交互界面相关的脚本,在cli.sh脚本中,调用了org.apache.hadoop.hive.cli.CliDriver类的main方法,main方法调用了run方法。
在run方法中,根据输入命令的模式(hive -e,hive -f,hive等),选择相应的调用方法。当我们通过hive命令打开命令行交互界面时,底层通过ConsoleReader来接收用户输入的命令,然后通过while持续接收命令,知道遇到分号(;)时,将接收到的命令提交执行。

执行命令入口在哪里?

从上面我们发现,最终执行命令根据不同的模式,字符串命令调用了processLine方法,文件模式调用了processFile方法。
其中,这些方法有多个重写。

image.png

processLine方法

第二个参数是boolean,真为任务可以中断,假为任务不可中断。

   public int processLine(String line) {
    return processLine(line, false);
  }

processLine方法支持通过Ctrl+C命令中断。
执行中断:
首先通过java的Signal模块来实现接收用户信号来终止运行。

if (allowInterrupting) {
      // Remember all threads that were running at the time we started line processing.
      // Hook up the custom Ctrl+C handler while processing this line
      interruptSignal = new Signal("INT");
      oldSignal = Signal.handle(interruptSignal, new SignalHandler() {
        private boolean interruptRequested;

        @Override
        public void handle(Signal signal) {
          boolean initialRequest = !interruptRequested;
          interruptRequested = true;

          // Kill the VM on second ctrl+c
          if (!initialRequest) {
            console.printInfo("Exiting the JVM");
            System.exit(127);
          }

          // Interrupt the CLI thread to stop the current statement and return
          // to prompt
          console.printInfo("Interrupting... Be patient, this might take some time.");
          console.printInfo("Press Ctrl+C again to kill JVM");

          // First, kill any running MR jobs
          HadoopJobExecHelper.killRunningJobs();
          TezJobExecHelper.killRunningJobs();
          HiveInterruptUtils.interrupt();
        }
      });
    }

命令执行:
processLine方法对输入对命令字符串进行处理之后,最后调用processCmd方法执行。

public int processLine(String line, boolean allowInterrupting) {
    SignalHandler oldSignal = null;
    Signal interruptSignal = null;

   
...

    try {
      int lastRet = 0, ret = 0;

      // we can not use "split" function directly as ";" may be quoted
      List<String> commands = splitSemiColon(line);

      String command = "";
      for (String oneCmd : commands) {

        if (StringUtils.endsWith(oneCmd, "\\")) {
          command += StringUtils.chop(oneCmd) + ";";
          continue;
        } else {
          command += oneCmd;
        }
        if (StringUtils.isBlank(command)) {
          continue;
        }

        ret = processCmd(command);
        command = "";
        lastRet = ret;
        boolean ignoreErrors = HiveConf.getBoolVar(conf, HiveConf.ConfVars.CLIIGNOREERRORS);
        if (ret != 0 && !ignoreErrors) {
          return ret;
        }
      }
      return lastRet;
    } finally {
      // Once we are done processing the line, restore the old handler
      if (oldSignal != null && interruptSignal != null) {
        Signal.handle(interruptSignal, oldSignal);
      }
    }
  }

processCmd方法

在processCmd方法中,主要流程有:
*1,去除命令中的注释;
*2,如果命令是quit或exit,则退出;
*3,如果命令是source开头,则校验source命令后面的文件是否存在,存在则执行processFile;
*4,如果命令是以感叹号!开头,表明是shell命令,这时候直接调用shell命令执行器执行;
*5,如果以上都不是,则说明是本地模式。然后获得命令处理器,并调用processLocalCmd执行命令。

    try {
        try (CommandProcessor proc = CommandProcessorFactory.get(tokens, (HiveConf) conf)) {
          if (proc instanceof IDriver) {
            // Let Driver strip comments using sql parser
            ret = processLocalCmd(cmd, proc, ss);
          } else {
            ret = processLocalCmd(cmd_trimmed, proc, ss);
          }
        }
      } catch (SQLException e) {
        console.printError("Failed processing command " + tokens[0] + " " + e.getLocalizedMessage(),
          org.apache.hadoop.util.StringUtils.stringifyException(e));
        ret = 1;
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }

其实,我们发现这里有两个比较重要的动作:一是命令处理器;另一个是processLocalCmd方法。


下面暂时做个小结:

至此,我们发现了命令行交互界面的退出方式可以通过quit或exit命令;另外,HiveQL语法和关系型数据库语法很相似,也有source命令来调用文件,前面我们知道调用文件还有 hive -f 方式;另外,还有点让人比较意外的是,可以通过!前缀的方式在hive客户端执行shell命令。

在hive命令行客户端,执行shell命令。下面是我们执行ls命令的效果。

hive>!ls;
tools
stefan
hive>

processLocalCmd方法

这里根据命令处理器的类型,分两种处理。如果是IDriver处理器,则把注释处理等交给Driver完成;如果是其他的命令处理器,则将去掉注释的命令传递给处理器。最终的命令执行是处理器的run方法完成的。

命令处理器CommandProcessor

命令处理器是通过CommandProcessorFactory工厂类创建的。

CommandProcessor proc = CommandProcessorFactory.get(tokens, (HiveConf) conf)

命令处理器构建原则是:先尝试构建非Driver处理器;如果获取为空,且第一条命令不为空的情况,构建Driver处理器。

public static CommandProcessor get(String[] cmd, @Nonnull HiveConf conf) throws SQLException {
    CommandProcessor result = getForHiveCommand(cmd, conf);
    if (result != null) {
      return result;
    }
    if (isBlank(cmd[0])) {
      return null;
    } else {
      return DriverFactory.newDriver(conf);
    }
  }

总结

我们从Hive Cli命令行交互界面的实现原理出发,分析了在CliDriver类中,从main方法调用run方法,run方法调用executeDriver方法,executeDriver方法最后调用了processLine或processFile方法,processLine方法最后调用了processCmd方法,processCmd方法最后调用processLocalCmd方法,最终的落点在了命令处理器CommandProcess上,最终的命令执行是由命令处理器完成。后面我们主要分析一下命令处理器是如何工作的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容