IT技术及科技前沿

中文IT博客,为IT专业技术人员提供最全面的信息传播和服务

首页 新随笔 订阅 管理
  • 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工作流程或原理,不想拘泥于细节,可以跳过下面的细节,如果你想修改源码,做优化,可以继续往下看)


    下面是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元数据数据库

    下图是SessionState的类结构:

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

    posted on 2010-12-20 22:09  孟和2012  阅读(435)  评论(0编辑  收藏  举报