Hive学习笔记 之 Hive运行流程简析1
从传统纯C软件转行学习Hive已经有半年多点的时间了,从之前的一无所知到目前略知一二,在网上少有找到一些入门级的全流程解析资料,索性按照自己的理解,简单整理一下Hive的运行逻辑。
版权声明:本文为原创文章,欢迎转载,请注明出处。
Hive架构
这张图在网上非常多,清晰地展现了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原生的命令名称相同,由上面的反射代码调用起来,负责对应的逻辑。一个个都想被介绍,那是不科学的,下面主要说一下metadata
,connect
,sql
方法。
metadata
:beeline可以直接使用tables
,columns
等命令获取信息,这些命令都不会经过HiveServer,而是直接调用本方法,通过JDBC驱动获取元数据信息。因此,在版本升级、版本迁移等场景中,元数据中记录的信息可能和show tables等DDL查询的内容不一致,可以通过这些接口来查询确认。connect
: 该方法将传入的jdbc字符串解析,查找对应的JDBC驱动,并建立与服务端的连接。Beeline支持管理多个连接,可以使用!go
在多个连接中进行切换,具体管理连接的类有DatabaseConnections
和DatabaseConnection
,都在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
命令进行关闭,切换或者连接的动作,相关管理类为DatabaseConnections
和DatabaseConnection
,仅一字之差,一个复数形式,一个单数形式。复数形式的管理非常简单,保存了一个DatabaseConnection
对象的list及当前连接的索引以供切换。
从DatabaseConnection
的getConnection
方法获取到连接对象时,会调用其内部的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的服务端进程。