Hive源码解析—之—hive的入口:
初衷:hi,大家好,我叫红门,在hive方面是个菜鸟,现在读hive源码希望能够更了解底层,尤其是hive与Hadoop切换这块。但在读hive源码时发现比Hadoop源码难读一些,虽然Hadoop源码量比较大,但是整体很规范,命名规范,关键地方注释的比较明确。
去年在读和修改Hadoop源码时都感觉比较清晰,可读性比较好一些,往往可以望文生义,可能也有自己对hive不熟的原因在里面吧!
想必别人应该也有人在关注hive底层,所以决定拿出来与大家分享,共同学习,如有理解不到位的地方,欢迎拍砖,更欢迎交流。
一直在思索应该从哪里写,不能一大堆代码粘上来,一通狂砍,大家会不知所云,有点装大。
后来给自己定了个原则:
1。hive执行过程为主线,尽量把关键的一些部分提出来,每次定一个主题。
2.怎么描述才能让别人更容易理解,更容易理清思路,尽量图形化描述。(我用的是mindManager画的图,不知道这种图形化的是不是可以让你看的更舒服。)
废话说得差不多了,现在开始!!
我们先从hive入口聊起,一路按着hive的执行过程主线走下来,
这次的主题就叫做: hive的入口!! (该图借用网上的,不知出处):
CliDriver可以说是hive的入口,对应上图中的UI部分。大家看它的结构就可以明白了,main()函数!对!你猜的没错就是从main()开始。
下图是类结构,总共有五个关键的函数。
这个类可以说是用户和hive交互的平台,你可以把它认为是hive客户端。总共有4个key函数:
下图是这个CliDriver类在整个Hive执行过程中的作用的地位。
如图,hive执行流程_按正常步骤走:
1.—CliDriver.classz中main()开始,初始化Hive环境变量,获取客户端提供的string或者file。
2 —将其代码送入processLine(cmd),这步主要是读入cmd:‘;’之前的所有字符串都读入(不做任何检查),之后的会忽略。读完后,传入processCmd()处理
3 —调用processCmd(cmd),分情况处理
//– 读入cmd,并分情况处理,总共分为以下五种情况,根据命令的开头字符串来确定用什么方法处理。
// 1.set.. 设置operator参数,hive环境参数
// 2.quit or exit — 退出Hive环境
// 3.! 开头
// 4.dfs 开头 交给FsShell处理
// 5.hivesql 正常hivesql执行语句,我们最关心的是这里。语句交给了、、Hive真正的核心引擎 Driver。返回ret = Driver.run(cmd);
4.—不同情况不同处理方法。我们关心的第五种情况:正常的HiveSQL如何处理?其实是进入driver.class里面run(),
//读入hivesql ,词法分析,语法分析,直到执行结束
//1.ParseDriver 返回 词法树 CommonTree
//2.BaseSemanticAnalyzer sem.analyze(tree, ctx);//语义解释,生成执行计划
5.—。。。etc
今天的主题是hive的入口,我们只聊前三步。
现在我们细化主要函数,看hive实际是怎么处理的。(如果你只想了解hive工作流程或原理,不想拘泥于细节,可以跳过下面的细节,如果你想修改源码,做优化,可以继续往下看)
——————————-类CliDriver —
由于这个类,可以说贯彻Hive的整个流程架构,所以我聊的比较细。
————————————————main()
public static void main(String[] args) throws IOException {
OptionsProcessor oproc = new OptionsProcessor();
if(! oproc.process_stage1(args)) {
System.exit(1);
}
// NOTE: It is critical to do this here so that log4j is reinitialized before
// any of the other core hive classes are loaded
SessionState.initHiveLog4j();
//建立客户端sesssion
CliSessionState ss = new CliSessionState (new HiveConf(SessionState.class));
ss.in = System.in;//标准输入
try {
ss.out = new PrintStream(System.out, true, "UTF-8");//??
ss.err = new PrintStream(System.err, true, "UTF-8");//??
} catch (UnsupportedEncodingException e) {
System.exit(3);
}
SessionState.start(ss);// -- start session 通过复制当前CliSessionState新建立SessionState
if(! oproc.process_stage2(ss)) {
System.exit(2);
}
// set all properties specified via command line
HiveConf conf = ss.getConf();//设置所有配置属性
for(Map.Entry item: ss.cmdProperties.entrySet()) {
conf.set((String) item.getKey(), (String) item.getValue());
}
sp = new SetProcessor();//?? what is proccessor
qp = new Driver(); // 正常hiveSql的处理引擎
dfs = new FsShell(ss.getConf());//dfs接口,用于 dfs命令处理
if(ss.execString != null) {// 输入的是命令行,按命令执行
System.exit(processLine(ss.execString));
}
try {
if(ss.fileName != null) {// 输入的是文件名,读文件执行
System.exit(processReader(new BufferedReader(new FileReader(ss.fileName))));
}
} catch (FileNotFoundException e) {//没有找到该文件
System.err.println("Could not open input file for reading. ("+e.getMessage()+")");
System.exit(3);
}
Character mask = null;
String trigger = null;
ConsoleReader reader = new ConsoleReader();//hive Console控制台命令读取器
reader.setBellEnabled(false);
//reader.setDebug(new PrintWriter(new FileWriter("writer.debug", true)));
List completors = new LinkedList();
completors.add(new SimpleCompletor(new String[] { "set", "from",
"create", "load",
"describe", "quit", "exit" }));
reader.addCompletor(new ArgumentCompletor(completors));
String line;
PrintWriter out = new PrintWriter(System.out);
final String HISTORYFILE = ".hivehistory";//建立历史文件,记录所有的命令行
String historyFile = System.getProperty("user.home") + File.separator + HISTORYFILE;
reader.setHistory(new History(new File(historyFile)));
int ret = 0;
Log LOG = LogFactory.getLog("CliDriver");//建立日志
LogHelper console = new LogHelper(LOG);
String prefix = "";
String curPrompt = prompt;// -- is "hive"
//不断地获取hiveSql,读取;之前的所有内容,传个processLine处理
while ((line = reader.readLine(curPrompt+"> ")) != null) {//--循环开始读命令
long start = System.currentTimeMillis();// 命令计时开始
if(line.trim().endsWith(";")) {//如果碰见';'表示结束,该
line = prefix + " " + line;
ret = processLine(line);// ----重点: 把命令行传入给解析,执行
prefix = "";// 把前缀重置为空
curPrompt = prompt;// "hive"
} else {
prefix = prefix + line;
curPrompt = prompt2;// 应该是 " "
continue;
}
long end = System.currentTimeMillis();
if (end > start) {//统计开始到结束的时间,如:命令开始执行所用的时间,
//console reader需要可以添加很多屏幕操作
double timeTaken = (double)(end-start)/1000.0;
console.printInfo("Time taken: " + timeTaken + " seconds", null); //对应在Hive Session上。
}
}
System.exit(ret);
—————————— processLine(Cmd)
// 读入cmd:‘;’之前的所有字符串都读入(不做任何检查),之后的都会忽略。读完后,传入processCmd处理.
public static int processLine(String line) { int ret = 0; for(String oneCmd: line.split(";")) { oneCmd = oneCmd.trim(); if(oneCmd.equals("")) continue; ret = processCmd(oneCmd);//--执行命令 if(ret != 0) { // ignore anything after the first failed command return ret; } } return 0; }
—————————— processCmd()
//– 读入cmd,并分情况处理,总共分为以下五种情况,根据命令的开头字符串来确定用什么方法处理。
// 1.set.. 设置operator参数,hive环境参数
// 2.quit or exit — 退出Hive环境
// 3.! 开头
// 4.dfs 开头 交给FsShell处理
// 5.hivesql 正常hivesql执行语句,我们最关心的是这里。语句交给了、、Hive真正的核心引
public static int processCmd(String cmd) {
String[] tokens = cmd.split("\\s+");
String cmd_1 = cmd.substring(tokens[0].length());
int ret = 0;
if(tokens[0].equals("set")) { //1
ret = sp.run(cmd_1);// 调用这句就可以更改hadoop配置
} else if (cmd.equals("quit") || cmd.equals("exit")) {//2
//退出Hive环境
System.exit(0);
} else if (cmd.startsWith("!")) {//3 :! 开头的命令
SessionState ss = SessionState.get();
String shell_cmd = cmd.substring(1);
if (shell_cmd.endsWith(";")) {
shell_cmd = shell_cmd.substring(0, shell_cmd.length()-1);
}//--除掉';'??
//shell_cmd = "/bin/bash -c \'" + shell_cmd + "\'";
try {
Process executor = Runtime.getRuntime().exec(shell_cmd);//!!??这句得好好 跟踪
StreamPrinter outPrinter = new StreamPrinter(executor.getInputStream(), null, ss.out);
StreamPrinter errPrinter = new StreamPrinter(executor.getErrorStream(), null, ss.err);
outPrinter.start();
errPrinter.start();
int exitVal = executor.waitFor();//?? look executor
if (exitVal != 0) {
ss.err.write((new String("Command failed with exit code = " + exitVal)).getBytes());
}
}
catch (Exception e) {
e.printStackTrace();
}
} else if (cmd.startsWith("dfs")) {//4 "dfs" 开头解析方法 -- cmd.
// Hadoop DFS 操作接口 处理!
SessionState ss = SessionState.get();
if(dfs == null)
dfs = new FsShell(ss.getConf());
String hadoopCmd = cmd.replaceFirst("dfs\\s+", "");
hadoopCmd = hadoopCmd.trim();
if (hadoopCmd.endsWith(";")) {
hadoopCmd = hadoopCmd.substring(0, hadoopCmd.length()-1);
}
String[] args = hadoopCmd.split("\\s+");//
try {
PrintStream oldOut = System.out;
System.setOut(ss.out);
int val = dfs.run(args);//??
System.setOut(oldOut);
if (val != 0) {
ss.err.write((new String("Command failed with exit code = " + val)).getBytes());
}
} catch (Exception e) {
ss.err.println("Exception raised from DFSShell.run " + e.getLocalizedMessage());
}
} else {//5 hivesql 正常运行,重点在这里
ret = qp.run(cmd);//正常执行hive命令,如:select .. ; addfile ..;
Vector res = new Vector();
while (qp.getResults(res)) {//获得执行结果result
for (String r:res) {
SessionState ss = SessionState.get();
PrintStream out = ss.out;
out.println(r);
}
res.clear();
}
int cret = qp.close();
if (ret == 0) {
ret = cret;
}
}
return ret;
}
———————–类CliSessionState
CliSessionState 除了做了个初始化,基本都是上继承了SessionState的实现方法,可能是作者为了低耦合。
public class CliSessionState extends SessionState
所以我们直接看SessionState。
SessionState可以说你是你自己当前的Hive环境,建立、初始化你Hive的session,一方面它来自conf的初始化设置,一方面来自你手动set。可以通过命令行形式,也可以通过file,这都取决于你的选择。
它会连接Hive元数据数据库,得到现有的元数据信息。
此类主要关键功能:
1 主要是生成session,并赋予一个唯一id(设置规则:用户名_年月日分秒 即 user_id + “_”+ yyyymmddHHmm)的session,
生成session有两种方式:1.直接新建session ,2. 通过拷贝方式复制一个session。
第一种我们最常用,但第二种很有用,我们可以确保我们两个环境是完全一致的,而且避免琐碎设置工作。
2 给每个cmd给予一个queryID,可以通过queryID得到命令行,也可以反过来得到id
3 每个sessionState 都会有一个logHelper,用于日志记录
其中 clude : hiveconf , 连接db元数据数据库
关键函数:
String makeSessionId() ——— //生成sessionID : user_id+”_” + yyyyMMDDhhmm
setCmd(String cmdString)—— //给命令cmd设置query Id
protected final static HiveConf.ConfVars [] metaVars —-//获取元数据系统,路径等
public String getCmd() —— // –通过queryID获取命令代码cmd ——————————–类 CommandProcessor
CommandProcessor类的很简单,是个接口类。 public interface CommandProcessor {
public int run(String command);
}
你会奇怪为什么先聊这个接口类,因为有三个类实现了这个接口(如下图),其中setProcessor, MetadataProcessor是我们Hive入口的关键类。
———–类MetadataProcessor
Hive部分元信息提取与处理
// run()中 得到表的元信息,如果出错返回1,如:找不到表名等情况
public int run(String command) {
SessionState ss = SessionState.get();
String table_name = command.trim();
if(table_name.equals("")) {
return 0;
}
try {
MetaStoreClient msc = new MetaStoreClient(ss.getConf());
if(!msc.tableExists(table_name)) {//表不存在
ss.err.println("table does not exist: " + table_name);
return 1;
} else {
List fields = msc.get_fields(table_name);//获得表信息
for(FieldSchema f: fields) {
ss.out.println(f.getName() + ": " + f.getType());
}
}
} catch (MetaException err) {
ss.err.println("Got meta exception: " + err.getMessage());
return 1;
} catch (Exception err) {
ss.err.println("Got exception: " + err.getMessage());
return 1;
}
return 0;
}
——————- 类SetProcessor
主要是设置Hive环境,总共分为两大类:
1. set session为安全模式,
如:set silent = true;
2.set 该session的conf配置,即调用hadoop时的配置参数,以及改变执行时的具体实现。 如:set hive.exec.compress.output=’false’;
// 我们可以调用这里run(String command)更改hadoop配置 ,hive执行参数等,
public int run(String command) {
SessionState ss = SessionState.get();//建一个SessionState对象
String nwcmd = command.trim();//去空格
if(nwcmd.equals("")) {
dumpOptions(ss.getConf().getChangedProperties());
return 0;
}
if(nwcmd.equals("-v")) {
dumpOptions(ss.getConf().getAllProperties());
return 0;
}
String[] part = new String [2];
int eqIndex = nwcmd.indexOf('=');
if(eqIndex == -1) {
// no equality sign - print the property out
dumpOption(ss.getConf().getAllProperties(), nwcmd);
return (0);
} else if (eqIndex == nwcmd.length()-1) {
part[0] = nwcmd.substring(0, nwcmd.length()-1);
part[1] = "";
} else {
part[0] = nwcmd.substring(0, eqIndex);// 中间=号隔开的 set cmd
part[1] = nwcmd.substring(eqIndex+1);
}
try {//
if (part[0].equals("silent")) {// 设置silent模式
boolean val = getBoolean(part[1]);//
ss.setIsSilent(val);//
} else {
ss.getConf().set(part[0], part[1]);// 设置key - value(如:.gmmt = ture)修改该session的conf里配置
}
Hive的入口先到这里,以后会陆续更新,欢迎交流。希望能这个Hive源码分析系列能够在你探索Hive的路上,节省您的时间。谢谢~