命令行界面,是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
方法。
其中,这些方法有多个重写。
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上,最终的命令执行是由命令处理器完成。后面我们主要分析一下命令处理器是如何工作的。