陈略

好记性不如烂笔头。

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Hive学习笔记 之 Hive运行流程简析1

从传统纯C软件转行学习Hive已经有半年多点的时间了,从之前的一无所知到目前略知一二,在网上少有找到一些入门级的全流程解析资料,索性按照自己的理解,简单整理一下Hive的运行逻辑。

版权声明:本文为原创文章,欢迎转载,请注明出处。

Hive架构

image

这张图在网上非常多,清晰地展现了Hive的架构。此处节选自私塾在线。原文链接,链接被河蟹,将多余的空格需要去掉。这篇帖子还介绍了Hive的各个模块和数据精度等内容,除了与MR相关的部分,其余的在1.2.1版本上均未过时,这是Hive的first glance。

下半部分的HADOOP有所变化,不过这非本文关注的重点,关于Hadoop的学习笔记后续可能会补充进来。

Hive运行流程简析

Hive的功能定位为Hadoop之上的数据仓库,提供类SQL的接口供上层使用。


可以用一句话简单粗暴的说明Hive的功能:将用户提交的SQL语句转换成Hadoop上的MR任务执行,并向用户返回结果。

刚开始进入Hive的学习时,其他人就是这么告诉我的。这句话听起来简直碉堡了,简洁明了,但是刚开始我TM哪里知道,这简单的一句话,涉及了Hive自身的几十万行代码以及引入的若干第三方组件。作为一个连JAVA基础和数据库都没玩过的人,真不知道这货到底水有多深,好在无知者通常无畏,上手开干。

吐槽的话先扯开。言归正传,那么Hive到底是怎么干这些事情的呢?

首先,补一些SQL,补一些JAVA。然后就开始进入正题了。

环境信息

Hadoop/Hive的安装及配置网上也遍地都是,这里也不再赘述。我的环境是Macbook,Hadoop 2.7.1伪分布式部署,Hive元数据库使用本地多用户模式,部署在Mysql上。

chen@Air-Chen ~/app/hive/bin$ hadoop version
Hadoop 2.7.1
Subversion https://git-wip-us.apache.org/repos/asf/hadoop.git -r 15ecc87ccf4a0228f35af08fc56de536e6ce657a
Compiled by jenkins on 2015-06-29T06:04Z
Compiled with protoc 2.5.0
From source with checksum fc0a1a23fc1868e4d5ee7fa2b28a58a
This command was run using /Users/chen/Applications/hadoop-2.7.1/share/hadoop/common/hadoop-common-2.7.1.jar
chen@Air-Chen ~/app/hive/bin$ ./hive --version
Hive 1.2.1
Subversion git://Air-Chen.local/Users/chen/Documents/code/hive -r 1358e2def1fbc5dac9b18538c954c486c9252b75
Compiled by chen on Sun Aug 9 10:10:41 CST 2015
From source with checksum 7e419ef28d944a88c49973e8e2b5ee79

chen@Air-Chen ~/app/hive/bin$ mysql -uroot -phive
Warning: Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 356
Server version: 5.6.25 MySQL Community Server (GPL)

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
mysql> use hivemeta;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+---------------------------+
| Tables_in_hivemeta        |
+---------------------------+

Beeline客户端

Hive提供了多种客户端,这里先粗暴地选定beeline,当时的leader就是这么粗暴的告诉我,于是我也从这里开始。

相关脚本

Beeline客户端自身是一个脚本,位于Hive的bin目录下,其实这货基本啥也没干,就是调用了一下同一路径下的hive脚本。

hive脚本算是hive的一个大入口,平级目录的其他脚本(除了hive-config.sh配置脚本)都是调用了hive脚本。hive脚本本身也很简单。它设置Hive需要的环境变量、CLASSPATH,并ext目录下的所有脚本都source一次,然后根据其入参的参数调用ext目录下实际脚本中的函数,分别完成各自的任务。

chen@Air-Chen ~/app/hive/bin$ ll
total 80
drwxr-xr-x  12 chen  staff   408  1 18 22:55 .
drwxr-xr-x  12 chen  staff   408  9 21 22:12 ..
-rwxr-xr-x   1 chen  staff  1031  5 20  2015 beeline
drwxr-xr-x  31 chen  staff  1054  8  9 11:06 ext
-rwxr-xr-x   1 chen  staff  7844  5 20  2015 hive
-rwxr-xr-x   1 chen  staff  1900  5 20  2015 hive-config.sh
-rwxr-xr-x   1 chen  staff   885  5 20  2015 hiveserver2
-rwxr-xr-x   1 chen  staff   832  5 20  2015 metatool
-rwxr-xr-x   1 chen  staff   884  5 20  2015 schematool

废话不多说,调用beeline脚本最终的目的,就是运行org.apache.hive.beeline.Beeline类,当然,这个还调用了Hadoop的bin下面的hadoop脚本soso,总说有时间看看,但是最后就不了了之了。。

那么,从这里开始,就要开始接触Hive的运行流程了。

Beeline处理逻辑

1. 命令解析与分发

那么我们的主角Beeline类粉墨登场了。从main方法进入处理流程,Beeline主要干这几件事情:

  • 首先加载beeline配置文件、历史记录文件
  • 然后解析beeline参数,
  • 继而,无论是使用-e执行单条语句、-f执行单个SQL文件,或者是不指定参数进入交互模式,beeline最终都将各种输入源转换成逐行命令。这个过程中会引用第三方组件jline
  • 最终为上一步中解析出来的逐行命令依次调用dispatch方法。该方法会匹配Beeline支持的原生命令,或者sql,如果匹配到beeline命令,则使用反射的方式调用Commands对象中的对应方法,否则认为是sql,直接调用Commands对象中的sql方法,并返回执行结果,true or false。dispatch方法的核心处理代码如下:
   if (line.startsWith(COMMAND_PREFIX)) {
      Map<String, CommandHandler> cmdMap = new TreeMap<String, CommandHandler>();
      line = line.substring(1);
      for (int i = 0; i < commandHandlers.length; i++) {
        String match = commandHandlers[i].matches(line);
        if (match != null) {
          CommandHandler prev = cmdMap.put(match, commandHandlers[i]);
          if (prev != null) {
            return error(loc("multiple-matches",
                Arrays.asList(prev.getName(), commandHandlers[i].getName())));
          }
        }
      }

      if (cmdMap.size() == 0) {
        return error(loc("unknown-command", line));
      }
      if (cmdMap.size() > 1) {
        // any exact match?
        CommandHandler handler = cmdMap.get(line);
        if (handler == null) {
          return error(loc("multiple-matches", cmdMap.keySet().toString()));
        }
        return handler.execute(line);
      }
      return cmdMap.values().iterator().next()
          .execute(line);
    } else {
      return commands.sql(line);
    
2. 命令处理

经过上面的过程后,Beeline类自身的职责完成了,然后将命令分发给干活的部门Commands对象去了,该部门有大量的方法,每个方法都与Beeline原生的命令名称相同,由上面的反射代码调用起来,负责对应的逻辑。一个个都想被介绍,那是不科学的,下面主要说一下metadataconnectsql方法。

  • metadata:beeline可以直接使用tables,columns等命令获取信息,这些命令都不会经过HiveServer,而是直接调用本方法,通过JDBC驱动获取元数据信息。因此,在版本升级、版本迁移等场景中,元数据中记录的信息可能和show tables等DDL查询的内容不一致,可以通过这些接口来查询确认。
  • connect: 该方法将传入的jdbc字符串解析,查找对应的JDBC驱动,并建立与服务端的连接。Beeline支持管理多个连接,可以使用!go在多个连接中进行切换,具体管理连接的类有DatabaseConnectionsDatabaseConnection,都在rg.apache.hive.beeline包中。
  • sql: sql方法将传入的SQL语句,通过JDBC接口,向服务端发起请求,并向客户端返回结果。

3. 获取连接

那么beeline是如何通过JDBC连接到HiveServer的呢?执行beeline脚本后进入交互模式,使用connect命令,并指定JDBC URL,即可连接到HiveServer。

chen@Air-Chen ~/app/hive/bin$ ./beeline

Beeline version 1.2.1 by Apache Hive
beeline> 
beeline> !connect jdbc:hive2://localhost:10000
Connecting to jdbc:hive2://localhost:10000
Enter username for jdbc:hive2://localhost:10000: chen
Enter password for jdbc:hive2://localhost:10000: 
16/01/17 19:27:33 [main]: INFO jdbc.Utils: Supplied authorities: localhost:10000
16/01/17 19:27:33 [main]: INFO jdbc.Utils: Resolved authority: localhost:10000
16/01/17 19:27:33 [main]: INFO jdbc.HiveConnection: Will try to open client transport with JDBC Uri: jdbc:hive2://localhost:10000
Connected to: Apache Hive (version 1.2.1)
Driver: Hive JDBC (version 1.2.1)
Transaction isolation: TRANSACTION_REPEATABLE_READ
0: jdbc:hive2://localhost:10000> 
0: jdbc:hive2://localhost:10000> 

如上,使用!connect命令,经过Beeline的处理后,最终会调用到Commands对象的connect方法,

    try {
      beeLine.getDatabaseConnections().setConnection(
          new DatabaseConnection(beeLine, driver, url, props));
      beeLine.getDatabaseConnection().getConnection();

      beeLine.setCompletions();
      return true;
    } catch (SQLException sqle) {
      return beeLine.error(sqle);
    } catch (IOException ioe) {
      return beeLine.error(ioe);
    

其核心代码如上,Beeline是可以支持多连接的管理,并使用!close,!go,!connect命令进行关闭,切换或者连接的动作,相关管理类为DatabaseConnectionsDatabaseConnection,仅一字之差,一个复数形式,一个单数形式。复数形式的管理非常简单,保存了一个DatabaseConnection对象的list及当前连接的索引以供切换。

DatabaseConnectiongetConnection方法获取到连接对象时,会调用其内部的connect方法加载Hive JDBC驱动,并获取连接。相关代码节选如下:

  boolean connect() throws SQLException {
    try {
      if (driver != null && driver.length() != 0) {
        Class.forName(driver);
      }
    } catch (ClassNotFoundException cnfe) {
      return beeLine.error(cnfe);
    }
	
	// 一些容错和参数处理

    setConnection(DriverManager.getConnection(getUrl(), info));
    setDatabaseMetaData(getConnection().getMetaData());
    
    // xxx
}

但是这个过程到底发生了神马,之前也一直没有在意,最近抽空也把这一段仔细看了一下。那么,先从JDBC说起。

Hive JDBC

对于标准JDBC接口并不懂,也没有去深入研究过,当前只要知道从DriverManager.getConnection方法会将JDBC驱动类实例化即可。JDBC相关标准,JDK文档有详细介绍。

HiveConnection构造方法

HiveConnection的构造方法接受一个JDBC字符串和一个属性对象。

  • 第一步:通过Utils.parseURL(uri)方法构造一个连接参数集合对象connParams,该对象记录了本次连接的所有配置信息,在连接过程中一直保存并传递。
  • 第二步:调用openTransport打开thrift远程连接通道(通过socket)。如果是安全版本,这个过程还涉及到安全认证等处理。
  • 第三步:调用openSession向Thrift Server端发起RPC请求,服务端调用对应方法建立会话。

这里引入第三方组件Thrift。这个一个跨语言的远程调用框架。官网

这里了,JDBC建立会话时做的事情,相当于在银行窗口办理业务,将表格填写完毕后,交给窗口内的工作人员进行处理,然后等待工作人员返回处理结果。而此处窗内的工作人员就是HiveServer的服务端进程。

未完待续

posted on 2016-01-19 00:54  陈略  阅读(4069)  评论(0编辑  收藏  举报