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的路上,节省您的时间。谢谢~