整套大数据学习资料(视频+笔记)百度网盘无门槛下载:http://www.edu360.cn/news/content?id=3377

12 关于Hive

hadoop 小红牛 34℃ 0评论

关于Hive

“信息平台和数据科学家的崛起” ®(Information Platforms and the Rise of the Data Scientist)—文中,Jeff Hammerbacher把“信息平台”描述为“企业摄取 (ingest)、处理(process)、生成(generate)信息的行为”与“帮助加速从经验 数据中学习”的“中心”。

Facebook, Jeff团队所构建的信息平台中,最庞大的组成部分是Hive。 Hive是一个构建在Hadoop上的数据仓库框架,是应Facebook每天产生的 海量新兴社会网络数据进行管理和(机器)学习的需求而产生和发展的。在尝 试了不同系统之后,团队选择Hadoop来存储和处理数据,因为Hadoop的 性价比髙,同时还能够满足他们的可伸缩性要求。®

Hive的设计目的是让精通SQL技能(但Java编程技能相对较弱)的分析师能 够对Facebook存放在HDFS中的大规模数据集执行査询。今天,Hive已经 是一个成功的Apache项目,很多组织把它用作一个通用的、可伸缩的数据处 理平台。

当然,SQL并不是所有大数据问题的理想工具。例如,它并不适合用来开 发复杂的机器学习算法。但它对很多分析任务非常有用,而且它的另一个 优势是业内人士都非常熟悉它。此外,SQL是商业智能工具的“通用语 言”(可以通过ODBC这一桥梁来用),Hive有条件和这些产品进行集成。

本章介绍如何使用Hive。我们假设你用过SQL和常见的数据库体系结构。 在介绍Hive特性的同时,我们会经常将这些特性与其传统RDBMS对应部 分进行比较。

12.1安装Hive

Hive —般在工作站上运行。它把SQL查询转换为一系列在Hadoop集群上 运行的MapReduce作业。Hive把数据组织为表,通过这种方式为存储在 HDFS的数据赋予结构。元数据(如表模式)存储在metastore数据库中。

刚开始使用Hive时,为了方便,可以让metastore运行在本地机器上。这 一设置是默认设置。此时,创建的Hive表的定义是在本地机器上,所以无 法和其他用户共享这些定义。在12.3.3节将介绍如何设置生产环境中常用 的远程共享metastore。

安装Hive的过程非常简单。首先必须有Java 6。如果在Windows环境下, 还需要Cygwin。需要在本地安装和集群上相同版本的Hadoop。®当然,在 刚开始使用Hive时,你可能会选择在本地以独立模式或伪分布模式运行 Hadoop。对于这些选项的介绍,可参见附录A。

Hive能和哪些版本的Hadoop共同工作?

每个Hive的发布版本都被设计为能够和多个版本的Hadoop共同工作.一 般而言,Hive支持Hadoop最新发布的稳定版本以及之前的老版本。这些 信息列在发布说明中。只要确保hadoop可执行文件在相应的路径中或设 置HADOOP_HOME环境变量,就不必另行告诉Hive当前正在使用哪个版本的Hadoop。

http://hive.apache.org/releases.html下载Hive的一个发布版本,然后把压 缩包解压到工作站上合适的位置:

% tar xzf hive-x.y.z.tar.gz

Hive放在你自己的路径下以便于访问:

   % export HIVE_INSTALL=/home/tom/hive-x.y.z-dev
  % export PATH=$PATH:$HIVE_INSTALL/bin

现在,键入hive启动Hive外壳环境(shell):

Hive外壳环境

Hive外壳环境是我们和Hive交互、发出HiveQL命令的主要方式。HiveQL 是Hive的査询语言。它是SQL的一种“方言”。它的设计在很大程度上深 受MySQL的影响。因此,如果熟悉MySQL,你会觉得Hive很亲切。

第一次启动Hive时,我们可以通过列出Hive的表来检査Hive是否正常工 作,此时应该没有任何表。命令必须以分号结束,告诉Hive立即执行该 命令:

hive> SHOW TABLES;
OK
Time taken: 10.425 seconds

SQL类似,HiveQL —般是大小写不敏感的(除了字符串比较以外),因此 show tables;和上面的命令效果相同。制表符(Tab)会自动补全Hive的关 键字和函数。

对于全新安装,这个命令会花几秒钟来执行。因为系统采用“懒”(lazy)策 略,所以直到此时才在机器上创建metastore数据库。(该数据库把相关文件 放在运行hive命令那个位置下的目录中。)

也可以以非交互式模式运行Hive外壳环境。使用-f选项可以运行指定文件 中的命令。在这个示例中,我们运行脚本文件script:q:

% hive -f script.q

对于较短的脚本,可用-e选项在行内嵌入命令。此时不需要表示结束的分号:

% hive -e ’SELECT * FROM dummy’Hive history file=/tmp/tom/hive_job_log_tom_201005042112_1906486281.txt
OK
X
Time taken: 4.734 seconds

有一个较小的数据表用于测试査询是很有用的。例如,我们可以用 文本数据测试SELECT表达式中的函数(参见12.5.2节对操作符和函 数的讨论)。下面是一个生成一个单行表的方法:

% echo 'X' > /tmp/dummy.txt
% hive -e ” CREATE TABLE dummy (value STRING); \
LOAD DATA LOCAL INPATH ’/tmp/dummy.txt, \
OVERWRITE INTO TABLE dummy”

无论是在交互式还是非交互式模式下,Hive都会把操作运行时的信息打印输出到标准错误输出(standard error)–例如运行一个查询所花的时间。可以在启动程序的时候使用-S选项强制不显示这些消息,只输出査询结果:

% hive -S -e 'SELECT * FROM dummy'
X

其他比较有用的Hive Shell的特性包括:使用!前缀来运行宿主操作系统的 命令;使用dfs命令来访问Hadoop文件系统。

12.2示例

让我们看一下如何用Hive查询我们在前面几章使用的气象数据集。第一个 步骤是把数据加载到Hive管理的存储。在这里,我们将让Hive把数据存 储在本地文件系统。稍后我们会介绍如何把表存储到HDFS。

RDBMS —样,Hive把数据组织成表。我们使用CREATE TABLE语句为气象数据新建一个表:

CREATE TABLE records (year STRING, temperature INT, quality INT)ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t';

第一行声明一个 records 表,包含三列:year, temperature 和 quality。还必须指明每一列的数据类型,在这里,年为字符串类型,另外两列为整 数型。

到目前为止,所用的SQL都是我们所熟悉的。但是接下来的ROW FORMAT 子句是HiveQL所特有的。这个子句所声明的是数据文件的每一行是由制表 符分隔的文本。Hive按照这一格式读取数据:每行三个字段,分别对应于 表中的三列,字段间以制表符分隔;每行以换行符分隔。

接下来,我们可以向Hive输入数据。这里出于探索的目的,只用一个很小 的样本数据集:

LOAD DATA LOCAL INPATH 'input/ncdc/micro-tab/sample.txt'
OVERWRITE INTO TABLE records;

这一命令告诉Hive把指定的本地文件放入其仓库目录中。这只是一个简单 的文件系统操作。这个操作并不解析文件或把它存储为内部数据库格式, 因为Hive并不强制使用任何特定文件格式。文件以原样逐字存储,Hive并

不会对文件进行修改。

在这个示例中,我们把Hive表存储在本地文件系统中(fs.default.name 设为默认值file:///)。在Hive的仓库目录中,表存储为目录。仓库目录 由选项 hive.metastore. warehouse.dir 控制,默认值/user/hive/warehouse。

这样,records表的文件便可以在本地文件系统的/user/hive/warehouse/records目录中找到:

% Is /user/hive/warehouse/record/ sample.txt

在这个示例中,我们现在只有一个文件sample.txt,但是在一般情况下,可 以有多个文件,而且Hive会在查询表的时候读入所有这些文件。

LOAD DATA语句中的OVERWRITE关键字告诉Hive删除表对应目录中已有 的所有文件。如果省去这一关键字,Hive就简单地把新的文件加入目录(除 非目录下正好有同名的文件,此时将替换掉原有的同名文件)。

数据现在已经在Hive中,我们可以对它运行一个査询:

hive> SELECT year, MAX(temperature)
    >FROM records
    >WHERE temperature != 9999
    >AND (quality = 0 OR quality = 1 OR quality = 4 OR quality = 5 OR quality = 9)
    >GROUP BY year;
    1949 111
    1950 22

这个SQL査询没什么特别的:它是一个带GROUP BY子句的SELECT语 句。这个査询根据年份对行进行分组,然后使用MAX()聚集函数在每个年 份组中找到最高气温。Hive的优势在于把这个査询转化为一个MapReduce 作业并为我们执行这个作业,然后把结果打印输出到控制台。虽然Hive和 其他数据库有一些细微的差别,例如Hive支持的SQL的结构以及可以査询 中数据的格式等——我们在本章中将探究差别——但能够在原始数据上执行 SQL查询,才彰显出Hive的强大功能。

12.3运行 Hive

这一节介绍运行Hive的一些更实用的技术,包括如何设置Hive使其能运 行在Hadoop集群和共享的metastore上。为此,我们会深入介绍Hive体 系结构。

12.3.1配置Hive

Hadoop类似,Hive使用XML配置文件进行设置。配置文件为hivesite.xml,它在Hive的conf目录下。通过这个文件,可以设置每次运行 Hive时希望Hive使用的选项。该目录下还包括hive-default.xml(其中记 录Hive使用的选项及其默认值)。

传递–config选项参数给hive命令,可以通过这种方式重新定义Hive查 找文件:

% hive --config /Users/tom/dev/hive-conf

注意,这个选项指定的是包含配置文件的自录,而不是配置文件hive-site.xml本身。这对于有(对应于多个集群的)多个站点文件时很有用,可以 方便地在这些站点文件之间进行切换。还有另一种方法,可以设置 HIVE_CONF_DIR环境变量来指定配置文件目录,效果相同。

Hive-site.xml文件最适合存放详细的集群连接信息,因为可以使用Hadoop 属性 fs.default.name 和 mapred.job.tracker来指定文件系统和 jobtracker(关于配置Hadcwp的详细信息,请参见附录A)。如果没有设定这 两个参数,它们就像在Hadoop中一样,被设为默认值,也就是使用本地文 件系统和本地(正在运行的)“作业运行器”(job runner)——这对于试着用 Hive来处理测试数据集非常方便。metastore的配置选项(参见12.3.3节对 metastore的讨论)一般也能在hive-site.xml中找到。

Hive还允许向hive命令传递-hiveconf选项来为单个会话(session)设置属性。例如,下面的命令设定在会话中使用一个(伪分布)集群:

% hive -hiveconf fs.default.name=localhost -hiveconf mapped.job.tracker=localhost:8021

如果准备让多个Hive用户共享一个Hadoop集群,则需要使Hive所 用的目录对所有用户可写。以下命令将创建目录,并设置合适的权限:

% hadoop fs -mkdir /tmp 
% hadoop fs -chmod a+w /tmp 
% hadoop fs -mkdir /user/hive/warehouse 
% hadoop fs -chmod a+w /user/hive/warehouse

如果所有用户在同一个用户组中,把仓库目录的权限设为g+w就 够了。

还可以在一个会话中使用SET命令更改设置。这对于为某个特定的查询修 改Hive或MapReduce作业设置非常有用。例如,以下命令确保表的定义中 都使用“桶”(bucket),详情可以参见12.6.2节。

hive> SET hive.enforce.bucketing=true;

可以用只带属性名的SET命令査看任何属性的当前值:

hive> SET hive.enforce.bucketing;
hive.enforce.bucketing=true

不带参数的SET命令会列出Hive所设置的所有属性(及其取值)。注意,这 个列表中不包含Hadoop的默认值,除非这个值用本节中介绍的某个方法重 写了。使用SET -V可以列出系统中的所有属性,包括Hadoop的默认值。

设置属性有一个优先级层次。在下面的列表中,越小的值表示优先级 越高。

(1)Hive SET 命令。
(2)命令行-hiveconf选项。
(3)hive-site.xml。
(4)hive-default.xml。
(5) hadoop-site.xml(或等价的 core-site.xml、hdfs-site.xml 与 mapred- site.xml)。
(6)hadoop-default.xml(或等价的 core-default.xml、hdfs-default.xml 与 mapred- default.xml)。

日志记录

可以在本地文件系统的/tmp/$USER/hive.log中找到Hive的错误日志。错误 日志对于诊断配置问题和其他错误非常有用。Hadoop的MapReduce任务日 志对于调试也非常有帮助,相关信息请参见5.5.6节对Hadoop用户日志的讨论。

日志的配置存放在中。可以通过编辑这个文件来 修改日志的级别和其他日志相关设置。但是,更方便的办法是在会话中对 日志配置进行设置。例如,下面的语句可以方便地将调试消息发送到控制台:

% hive -hiveconf hive.root.logger=DEBUG,console

12.3.2 Hive服务

Hive外壳环境只是hive命令提供的其中一项服务。可以在运行时使用– service选项指明要使用哪种服务。键入hive –service help可以获 得可用服务列表。下面介绍一些最有用的服务。

•cli Hive的命令行接口(外壳环境)。这是默认的服务。

•hiveserver让Hive以提供Thrift服务的服务器形式运行,允许用 不同语言编写的客户端进行访问。使用Thrift、JDBC和ODBC连 接器的客户端需要运行Hive服务器^和Hive进行通信。通过设置 HIVE_PORT环境变量来指明服务器所i;听的端口号(默认为10000)。

•hwi Hive的Web接口。参见后文的补充内容。

•jar与hadoop jar等价。这是运行类路径中同时包含Hadoop和 Hive类Java应用程序的简便方法。

• metastore默认情况下,metastore和Hive服务运行在同一个进程 里。使用这个服务,可以让metastore作为一个单独的(远程)进程运 行。通过设置METAST0RE_P0RT环境变量可以指定服务器监听的 端口号。

HWI

你可能希望用简洁直观的HWI(Hive Web丨nterface)来代替外壳环境。使用 下面的命令启动这一服务:

% export ANT_LIB=/path/to/ant/lib
 % hive --service hwi

只有在系统的/opt/ant/lib中没有找到Ant的库时,才需要设定ANT_LIB环 境变量。然后在浏览器里浏览http://localhost:9999/hwi。从这里可以查看 Hive数据库的模式并创建会话来发出命令和查询。

我们还可以以共享服务方式运行Web接口。这样一来,同一个组织内的用 户不需要安装任何客户端软件也能访问Hive。

图片.png 

12-1. Hive体系结构

• Hive客户端 如果以服务器方式运行Hive(hive –service hiveserver),可以在应用程序中以不同机制连接到服务器。Hive 客户端和服务之间的联系如图12-1所示。

•Thrift客户端 Hive Thrift客户端简化了在多种编程语言中运行 Hive 命令。Hive 的 Thrift 绑定支持 C++、Java、PHP、Python 和 Ruby。在Hive发布版本的src/service/src子目录下可以找到对这些 语言的Thrift绑定。

• JDBC驱动Hive提供了(Type 4)(纯Java)的JDBC驱动,定义在 org.apache.hadoop. hive.jdbc.HiveDriver 类中 。 在以 jdbc:hive://host:port/dbname 形式配置 JDBC URI 以后, Java应用程序可以在指定的主机和端口连接到在另一个进程中运行 的Hive服务器。(驱动使_用Java的Thrift绑定来调用由Hive Thrift 客户端实现的接口。)

你可能还希望通过URI jdbc:hive://用JDBC内嵌模式来连接 Hive。在这个模式下,Hive和发出调用的应用程序在同一个JVM 中运行。这时不需要以独立服务器方式运行Hive,这是因为此时 应用程序并不使用Thrift服务或Hive的Thrift客户端。

•ODBC驱动Hive的ODBC驱动允许支持ODBC协议的应用程序 连接到Hive。和JDBC驱动类似,ODBC驱动使用Thrift和Hive 服务器进行通信。ODBC驱动还处在开发阶段,所以请参考Hive wiki的最新指南以了解如何编译和运行。

Hive 维基页面(http://cwiki.apache.org/confluence/display/Hive/HiveClient)详 细介绍了如何使用这些客户端。

12.3.3 Metastore

metastore是Hive元数据的集中存放地。metastore包括两部分:服务和后台

数据的存储。默认情况下,metastore服务和Hive服务运行在同一个JVM中,它包含一个内嵌的以本地磁盘作为存储的Derby数据库实例。这称为“内嵌 metastore 配置”(embedded metastore configuration),参见图 12-2。

图片.png 

12-2. Metastore 的配置

使用内嵌metastore是Hive入门最简单的方法。但是,每次只有一个内嵌 Derby数据库可以访问某个磁盘上的数据库文件,这就意味着一次只能为每 个metastore打开一个Hive会话。如果要试着启动第二个会话,在它试图 连接metastore时,会得到以下错误信息:

Failed to start database 'metastore_db'

如果要支持多会话(以及多用户),需要使用一个独立的数据库。这种配置称 为“本地metastore”,因为metastore服务仍然和Hive服务运行在同一个 进程中,但连接的却是在另一个进程中运行的数据库,在同一台机器上或 在远程机器上。任何JDBC兼容的数据库都可以通过表12-1列出的 javax.jdo.option.*配置属性来供 metastore 使用。①

12-1.重要的metastore配置属性

属性名称

类型

默认值

描述

hive.metastore.warehouse.dir

URI

/user/hive/warehouse

相对于fs.default.name的目录,托管表就存储在这里

hive.metastore.local

布尔型

true

是使用内嵌的metastore(true),还 是连接到远程(false)。如果是 false,则必须设置hive, metastore.uris

hive.metastore.uris

逗号分 隔的URI

未设定

指定要连接的远程metastore服 务器的URI。如果有多个远程服 务器,则客户端便以轮询(round robin)方式连接

javax.jdo.option. ConnectionURL

URI

jdbc:derby:;database Name=metastored b; create=true

metastore 数据库的 JDBC URL

javax.jdo.option. ConnectionDriverName

字符串

org.apache.derby.jdbc.EmbeddedDriver

JDBC驱动器的类名

javax.jdo.option. ConnectionUserName

字符串

APP

JDBC用户名

javax.jdo.option. ConnectionPas sword

字符串

mine

JDBC密码

对于独立的metastore,MySQL是一种很受欢迎的选择。此时 javax. jdo .option .ConnectionURL 应该设为 jdbc:mysql://host/dbname?createDatabaselfNot Exist=t rue,而javax . jdo . option . ConnectionDriverName 则设为com.mysql.jdbc.Driver。(当然,还需要设置用户名和密 码。)MySQL的JDBC驱动的JAR文件(Connector/J)必须在Hive的类路径中,把这个文件放入Hive的/A目录即可。

 更进一步,还有一种metastore配置称为“远程metastore”。在这种配置 下,一个或多个metastore服务器和Hive服务运行在不同的进程内。这 样一来,数据库层可以完全置于防火墙后,客户端则不需要数据库凭据 (用户名和密码),从而提供了更好的可管理性和安全。

可以通过把 hive.metastore.local 设为 false, hive.metastore.uris设为metastore服务器URI(如果有多个服务器,各个URI之间用逗号分 隔),把Hive服务设为使用远程metastore。metastore服务器URI的形式为 thrift://host:port。这里,端口号对应于启动metastore服务器时所设 定的METASTORE_PORT值(参见12.3.2节对Hiye服务的讨论)。

12.4Hive与传统数据库相比

Hive在很多方面和传统数据库类似(例如支持SQL接口),但是其底层对 HDFS和MapReduce的依赖意味着它的体系结构有别于传统数据库,而这 些区别又影响着Hive所支持的特性,进而影响着Hive的使用。

12.4.1读时模式vs.写时模式

在传统数据库里,表的模式是在数据加载时强制确定的。如果在加载时发 现数据不符合模式,则拒绝加载数据。因为数据是在写入数据库时对照模 式进行检査,因此这一设计有时被称为“写时模式”(schema on write)。

在另一方面,Hive对数据的验证并不在加载数据时进行,而在査询时进 行。这称为“读时模式”(schema on read)。

用户需要在这两种方法之间进行权衡。读时模式可以使数据加载非常迅速。 这是因为它不需要读取数据,进行“解析”(parse),再进行序列化以数据 库内部格式存入磁盘。数据加载操作仅仅是文件复制或移动。这一方法 也更为灵活:试想,针对不同的分析任务,同一个数据可能会有两个模 式。Hive使用“外部表”(external table)时,这种情况是可能发生的,参见 12.6.1节对托管表和外部表的讨论。

写时模式有利于提升査询性能。因为数据库可以对列进行索引,并对数据 进行压缩。但是作为权衡,此时加载数据会花更多时间。此外,在很多加载时模式未知的情况下,因为査询尚未确定,因此不能决定使用何种索 引。这些情况正是Hive “长袖善舞”的地方。

 12.4.2更新、事务和索引

更新、事务和索引都是传统数据库最重要的特性。但是,直到最近,Hive 也还没有考虑支持这些特性,因为Hive被设计为用MapReduce操作 HDFS数据。在这样的环境下,“全表扫描”(full-table scan)是常态操作, 而表更新则是通过把数据变换后放入新表实现的。对于在大规模数据集上 运行的数据仓库应用,这一方式很见效。

Hive不支持更新(或删除),但支持INSERT INTO,所以可以向现有表中增 加新的行。

0.7.0发布版本中,Hive引入了索引,以在某些情况下加快査询的速度。 对于SELECT * from t WHERE x = a在这样的查询,因为只需要扫描表 文件的一小部分,因此可以利用在列x上的索引。目前Hive的索引分成两 类:紧凑(compact)索引和位图(bitmap)索引。(索引的实现被设计为可插拔 的(pluggable),所以为了其他目的而设计的索引实现会陆续出现。)

紧凑索引存储每个值的HDFS块号,而不是存储文件内偏移量。因此存储 不会占用过多的磁盘空间,且对于值被聚簇(clustered)存储于相近行的情 况,索引仍然有效。位图索引使用压缩的位集合(bitset)来高效存储具有某 个特殊值的行。这种索引一般适合于具有较少取值可能(low-cardinality)的 列(如性别或国别)。

Hive的0.7.0发布版本还引入了表级(table-level)和分区级(partition-level)的 锁。有了锁,就可以防止一个进程删除正在被另一个进程读取的表。锁由 ZooKeeper透明管理,因此用^不必执行获得和释放锁的操作,但仍然可以 通过SHOW LOCKS语句获取已经获得了哪些锁的信息。默认情况下,并未 启用锁的功能。

改变也来自另一个方向:HBase集成。HBase(第13章)和HDFS相比,有着 不同的存储特性,如行更新和列索引。因此,我们可以希望Hive在后续的 发布版本里利用这些HBase的特性。

12.5HiveQL

Hive的SQL “方言”,称为HiveQL,并不完全支持SQL-92标准,因为 SQL-92兼容本来就不是Hive项目的目标。作为一个开源项目,开发者不 断增加新的功能,以满足用户的需要,从而使得Hive的SQL支持越来越丰 富。此外,Hive还有一些SQL-92所没有的扩展。这些扩展受到其他数据 库系统(特别是MySQL)语法的启发。事实上,HiveQL可以勉强看作是对 MySQL的SQL方言的模仿。

Hive对SQL-92的有些扩展是受MapReduce启发而来的,如多表插入(详情 参见 12.6.4 节)和 TRANSFORM, MAP 和 REDUCE f 句(详情参见 12.7.2 节)。

本章并不提供HiveQL的完整介绍,我们只聚 焦于常用特性,并特别关注那些不同于SQL-92或像MySQL这样常用数据 库的特性。表12-2给出了 SQL和HiveQL特性的较高层次的比较。

12-2. SQL和HiveQL的概要比较

特性

 SQL

HiveQL

HiveQL

更新

UPDATE, INSERT, DELETE

INSERT

12.6.4 节和 12.4.2 节

事务

支持

支持(表级和分区级)

索引

支持

支持

延迟

亚秒级

分钟级

数据类型

整数、浮点数、定点 数、文本和二进制串、 时间

整数、浮点数、布尔 型、文本和二进制串、 时间戳、数组、映射、 结构

12.5.1 节

函数

数百个内置函数

几十个内置函数

12.5.2 节

多表插入

不支持

支持

12.6.4 节

Create table SQL-92 中不支持, as select 但有些数据库支持

支持

12.6.4 节

选择

SQL-92

FROM子句中只能有一 个表或视图。支持偏序 的SORT BY。可限制 返回行数量的LIMIT

12.7 节

 

特性

SQL

HiveQL

参考

连接

SQL-92支持或变相支 持(FROM子句中列出 连接表,在WHERE 子句中列出连接条件)

内连接、外连接、半连 接、映射连接(带提示 的SQL-92语法)

12.7.3

子査询

在任何子句中支持“相 关”的(correlated)或不相 (noncorrelated)

只能在FROM子句中 (不支持相关子査询)

12.7.4

视图

可更新(是物化的或非 物化的)

只读(不支持物化视图)

12.7.5

扩展点

用户定义函数

用户定义函数

12.8 节和]2.7.2

存储过程

MapReduce 脚本

 

12.5.1数据类型

Hive支持原子和复杂数据类型。原子数据类型包括数值型、布尔型、字符 串类型和时间戳类型。复杂数据类型包括数组、映射和结构。Hive的数据 类型在表12-3中列出。注意,列出的是它们在HiveQL中使用的形式而不 是它们在表中序列化存储的格式(参见12.6.3节)。

12-3. Hive的数据类型

类别

类型

描述

文字示例

基本数

TINYINT

1字节(8)有符号整数,从-128127

1

据类型

SMALLINT

2字节(16)有符号整数,从-32 76832 767

1

INT

4字节(32)有符号整数,从-2 147 483 6482 147 483 64^7

1

BIGINT

8字节(64)有符号整数,从-9 223 372 036 854 775 808 9 223 372 036 854 775 807

1

FLOAT

4字节(32)单精度浮点数

1.0

DOUBLE

8字节(64)双精度浮点数

1.0

BOOLEAN

true/false

TRUE

STRING

字符串

'a', "a"

BINARY

字节数组

不支持

TIMESTAMP

精度到纳秒的时间戳

1325502245000, '2012-01-02 03:04:05.123456789'

 

类别

类型

描述

文字示例

复杂数据类型

ARRAY

—组有序字段。字段的类型必须相同

Array(1,2)

MAP

一组无序的键/值对。键的类型必须是原子的;值可以是任意类型。同一个映射的键的类型必须相同,值的类型也必须相同

Map(‘a’,1,’b’,2)

STRUCT

一组命名的字段。字段的类型可以不同

Struct(‘a’,1,1.0)

 

①数组、映射和结构的文字形式可以通过函数得到:array()、map()、struct()三个函数都是Hive的 内置函数。

②列命名为coll、col2、col3等。

1.基本类型

虽然有些Hive的原子数据类型的命名受到MygQL数据类型名称(其中有些 和SQL-92相同)的影响,但这些数据类型基本对应于Java中的类型。有四 种有符号整数类型:TINYINT、SMALLINT、INT以及BIGINT,分别等价于 Java的byte、short、int和long原子数据类型。它们分别为1字节、2字节、4字节和8字节有符号整数。

Hive的浮点数据类型FLOAT和DOUBLE对应于Java的float和double类型,分别为32位和64位浮点数。和有些数据库不同,它不提供控制浮点 数值有效数字或小数位位数的选项。

Hive提供BOOLEAN数据类型用于存储真值(true)和假值(false)。

Hive只提供了一种存储文本的数据类型STRING。该类型是一个变长字符 串。Hive的STRING类似于其他数据库的VARCHAR,但不能声明其中最多 能存储多少个字符。(理论上其中最多能存储2GB的字符数,但如果真要物 化存储那么大的值,效率肯定很低。Sqoop提供了大对象的支持,详见15.7 节对导入大对象的讨论。)

BINARY数据类型用于存储变长的二进制数据。

TIMESTAMP数据类型存储精度为纳秒的时间戳。Hive提供了在Hive时间 戳、Unix时间戳(从UNIX纪元开始的秒数)、字符串之间进行转换的 UDF,这样就使得常用的日期操作较为容易进行处理。但是TIMESTAMP中 并未封装时区信息。可以使用to_utc_timestamp和from_utc_timestamp

函数来进行时区转换。

2.复杂类型

Hive有三种复杂数据类型:ARRAY、MAP和STRUCT。ARRAY和MAP与Java 中的同名数据类型类似,而STRUCT是一种记录类型,它封装了一个命名的 字段集合。复杂数据类型允许任意层次的嵌套。复杂数据类型声明必须使 用尖括号符号指明其中数据字段的类型。如下所示的表定义有三列,每一 列对应一种复杂数据类型:

CREATE TABLE complex ( 
coll ARRAY<INT>,
co12 MAP<STRING, INT>,
co13 STRUCT<a:STRING, b:INT, c:DOUBLE>
);

如果把表12-3中“文字示例”列中所示ARRAY、MAP和STRCUT类型的数 据加载到表中(12.6.3节要介绍需要什么格式的文件),则如下的査询展示了 每种类型的字段访问操作:

hive> SELECT collf[O], co12[‘b'], col3.c FROM complex;
1 2 1.0

12.5.2操作与函数

Hive提供了普通SQL操作,包括:关系操作(例如等值判断x =a,空值 判断x IS NULL,模式匹配x LIKE 'a%),算术操作(例如加法x+1),以 及逻辑操作(例如逻辑或(OR) x OR y))。这些操作和MySQL的操作一样, 而和SQL-92不同:||是逻辑或(OR),而不是字符串“连接”(concatenation)。在 MySQL和Hive中,字符串连接应该用concat函数。

Hive提供的内置函数太多,以至于这里无法一一列举。这些函数分成几个大 类,包括数学和统计函数、字符•串函数、日期函数(用于操作表示日期的字符 串)、条件函数、聚集函数以及处理XML(使用xpath函数)和JSON的函数。

可以在Hive外壳环境中输入SHOW FUNCTIONS以获取函数列表。®要想了 解某个特定函数的使用帮助,可以使用DESCRIBE命令:

hive> DESCRIBE FUNCTION length;
length(str | binary) - Returns the length of str or number of bytes in binary data

如果没有你需要的内置函数,那么可以自己动后写,详情参见12.8节。

类型转换

原子数据类型形成了一个Hive函数和操作表达式进行隐式类型转换的层 次。例如,如果某个表达式要使用INT,那么TINYINT会被转换为INT。 但是,Hive不会进行反向转换,它会返回错误,除非使用CAST操作。

隐式类型转换规则概述如下:任何整数类型都可以隐式地转换为一个范围 更广的类型。所有整数类型、FLOAT和(可能令人惊讶的)STRING类型都能 隐式转换为DOUBLE。TINYINT、SMALLINT和INT都可以转换为FLOAT。 BOOLEAN类型不能转换为其他任何数据类型。了IMESTAMP可以被隐式转换 为 STRING。

你可以使用CAST操作显式进行数据类型转换。例如,CAST('l' AS INT)将把字符串1转换成整数值1。如果强制类型转换失败,例如执行CAST(X AS INT),表达式就会返回空值NULL。

12.6

Hive的表在逻辑上由存储的数据和描述表中数据形式的相关元数据组成。 数据一般存放在HDFS中,但它也可以放在其他任何Hadoop文件系统中, 包括本地文件系统或S3。Hive把元数据存放在关系数据库中,而不是放在 HDFS中,详情参见12.2.3节对metastore的讨论。

在这一节中,我们将进一步了解如何创建表格、Hive提供的不同物理存储 格式以及如何导入这些不同格式的数据。

多数据库/模式支持

很多关系数据库提供了多个“命名空间”(namespace)的支持.这样,用户 和应用就可以被隔离到不同的数据库或模式中。Hive也对此提供了同样的 支持,可用的命令包括CREATE DATABASE dbname、USE dbnaroe以及 DROP DATABASE c/bname 这样的语句。可以通过 dbname.tablename 来完全限定某一张表。如果没有指明数据库,那么所指的是在default数据库 中的表。

12.6.1托管表和外部表

Hive中创建表时,默认情况下Hive负责管理数据。这意味着Hive把数据移入它的“仓库目录”(warehouse directory)。另一种选择是创建一个“外部表”(external table)。这会让Hive到仓库目录以外的位置访问数据。

这两种表的区别表现在LOAD和DROP命令的语义上。先来看托管表 (managed table)。

加载数据到托管表时,Hive把数据移到仓库目录。例如:

CREATE TABLE managed_table (dummy STRING);
LOAD DATA INPATH '/user/tom/data.txt' INTO table managed_table;

把文件hdfs://user/tom/data.txt移动到Hive的managed_table表的仓库目

录中,即 hdfs://user/hive/warehouse/managed_table0 。

由于加载操作就是文件系统中的文件移动或文件重命名,因此它的 执行速度很快。但记住,即使是托管表,Hive也并不检査表目录中 的文件是否符合为表所声明的模式。如果有数据和模式不匹配,只 有在査询时才会知道。我们通常要通过査询为缺失字段返回的空值 NULL才知道存在不匹配的行。可以发出一个简单的SELECT语句来 査询表中的若干行数据,从而检查数据是否能够被正确解析。

如果随后要丢弃一个表,可使用以下语句:

DROP TABLE managed_table;

这个表,包括它的元数据和数据,会被一起删除。在此我们要重复强调, 因为最初的LOAD是一个移动操作,而DROP是一个删除操作,所以数据会 彻底消失。这就是Hive所谓的托管数据”的含义。

对于外部表而言,这两个操作的结果就不一样了:由你来控制数据的创建 和删除。外部数据的位置需要在创建表的时候指明:

CREATE EXTERNAL TABLE external_table (dummy STRING)
LOCATION '/user/tom/external_table';
LOAD DATA INPATH ’/user/tom/data.txt’ INTO TABLE external_table;

使用EXTERNAL关键字以后,Hive知道数据并不由自己管理,因此不会把 数据移到自己的仓库目录。事实上,在定义时,它甚至不会检查这一外部 位置是否存在。这是一个非常有用的特性,因为这意味着你可以把创建数 据推迟到创建表之后才进行。

丢弃外部表时,Hive不会碰数据,而只会删除元数据。

那么,应该如何选择使用哪种表呢?在多数情况下,这两种方式没有太大 的区别(当然DROP语义除外),因此这只是个人喜好问题。作为一个经验法 则,如果所有处理都由Hive完成,应该使用托管表。但如果要用Hive和 其他工具来处理同一个数据集,应该使用外部表。普遍的用法是把存放在 HDFS(由其他进程创建)的初始数据集用作外部表进行使用,然后用Hive的 变换功能把数据移到托管的Hive表。这一方法反之也成立一一外部表(未必 在HDFS中)可以用于从Hive导出数据供其他应用程序使用。®

需要使用外部表的另一个原因是你想为同一个数据集关联不同的模式。

12.6.2分区和桶

Hive把表组织成“分区”(partition)。这是一种根据“分区列”(partition column,如日期)的值对表进行粗略划分的机制。使用分区可以加快数据 分片(slice)的査询速度。

表或分区可以进一步分为“桶”(bucket)。它会为数据提供额外的结构以获 得更高效的查询处理。例如,通过根据用户ID来划分桶,我们可以在所有 用户集合的随机样本上快速计算基于用户的查询。

1.分区

以分区的常用情况为例。考虑日志文件,其中每条记录包含一个时间戳。 如果我们根据日期来对它进行分区,那么同一天的记录就会被存放在同一 个分区中。这样做的优点是:对于限制到某个或某些特定日期的査询,它 们的处理可以变得非常高效。因为它们只需要扫描査询范围内分区中的文

件。注意,使用分区并不会影响大范围查询的执行:我们仍然可以查询跨 多个分区的整个数据集。

一个表可以以多个维度来进行分区。例如,在根据日期对日志进行分区以 外,我们可能还要进一步根据国家对每个分区进行子分区(subpartition),以 加速根据地理位置进行的査询。 分区是在创建表的时候用PARTITIONED BY子句定义的®。该子句需要定义 列的列表。例如,对前面提到的假想的日志文件,我们可能要把表记录定 义为由时间戳和日志行构成:

CREATE TABLE logs (ts BIGINT, line STRING)
PARTITIONED BY (dt STRING, country STRING);

在我们把数据加载到分区表的时候,要显式指定分区值:

LOAD DATA LOCAL INPATH ’input/hive/partitions/filel'
INTO TABLE logs
PARTITION (dt=’2001-01-01’,, country’GB’);

在文件系统级別,分区只是表目录下嵌套的子目录。把更多文件加载到 logs表以后,目录结构可能像下面这样:

/user/hive/warehouse/logs

图片.png

日志表有两个日期分区:2001-01-01和2001-01-02,分别对应于子目录 dt=2001-01-01和出=2001-01-02;和两个国家分区:GB和US,分别对应 于嵌套子目录country=GB和country=US。数据文件则存放在底层目录中。

可以用SHOW PARTITIONS命令让Hive告诉我们表中有哪些分区:

hive> SHOW PARTITIONS logs;

dt=2001-01-01/country=GB

dt=2001-01-01/country=US

dt=2001-01-02/country=GB

dt=2001-01-02/country=US

记住,PARTITIONED BY子句中的列定义是表中正式的列,称为“分区 列”(partition column),但是,数据文件并不包含这些列的值,因为它们源于目录名。

可以在SELECT语句中以通常的方式使用分区列。Hive会对输入进行修剪,从而只扫描相关的分区。例如:

SELECT ts, dt, line
FROM logs ,
WHERE country='GB';

将只扫描filel、file2和file4。还要注意,这个査询返回dt分区列的 值。这个值是Hive从目录名中读取的,因为它们在数据文件中并不存在。

2.桶

把表(或分区)组织成桶(bucket)有两个理由。第一个理由是获得更高的査询 处理效率。桶为表加上了额外的结构。Hive在处理有些查询时能够利用这 个结构。具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可 以使用map端连接(map-side join)高效地实现。

把表划分成桶的第二个理由是使“取样”(sampling)更高效。在处理大规模 数据集时,在开发和修改査询的阶段,如果能在数据集的一小部分数据上 试运行查询,会带来很多方便。我们在本节的最后将看看如何高效地进行 取样。

首先,我们来看如何告诉Hive 一个表应该被划分成桶。我们使用 CLUSTERED BY子句来指定划分桶所用的列和要划分的桶的个数:

CREATE TABLE bucketed_users (id INT, name STRING)
CLUSTERED BY (id) INTO 4 BUCKETS;

在这里,我们使用用户ID来确定如何划分桶(Hive对值进行哈希并将结果 除以桶的个数取余数。这样,任何一桶里都会有一个随机的用户集合。

对于map端连接的情况,首先两个表以相同方式划分桶,处理左边表内某 个桶的mapper知道右边表内相匹配的行在对应的桶内,这样,mapper只需要获取那个桶(这只是右边表内存储数据的一小部分)即可进行连接。这一优 化方法并不一定要求两个表必须具有相同的桶的个数,两个表的桶个数是 倍数关系也可以。用HiveQL对两个划分了桶的表进行连接,更多细节可参 见12.7.3节对map连接的讨论。

桶中的数据可以根据一个或多个列另外进行排序。由于这样对每个桶的连 接变成了高效的归并排序(merge-sort),因此可以进一步提升map端连接的 效率。以下语法声明一个表使其使用排序桶:

CREATE TABLE bucketed_users (id INT, name STRING) CLUSTERED BY (id) SORTED BY (id ASC) INTO 4 BUCKETS;

我们如何保证表中的数据都划分成桶了呢?把在Hive外生成的数据加载到 划分成桶的表中,当然是可以的。其实让Hive来划分桶更容易。这一操作 通常针对已有的表。

 Hive并不检査数据文件中的桶是否和表定义中的桶一致(无论是对于桶的数量或用于划分桶的列)。如果两者不匹配,在査询时可能会碰到错误或未定义的结果。因此,建议让Hive来进行划分桶的操作。

有一个没有划分桶的用户表:

hive> SELECT * FROM users;
0 Nat
2 Joe
3 Kay
4 Ann

要向分桶后的表中填充成员,需要将hive.enforce.bucketing属性设置 为true。这样,Hive就知道用表定义中声明的数量来创建桶,然后使用 INSERT命令即可:

INSERT OVERWRITE TABLE bucketed_users 
SELECT * FROM users;

物理上,每个桶就是表(或分区)目录里的一个文件。它的文件名并不重要, 但是桶《是按照字典序排列的第《个文件。事实上,桶对应于MapReduce 的输出文件分区:一个作业产生的桶(输出文件)和reduce任务个数相同。 我们可以通过査看刚才创建的bucketed_users表的布局来了解这一情 况。运行如下命令:

hive> dfs -Is /user/hive/warehouse/bucketed__usens;

将显示有4个新建的文件。文件名如下(文件名由Hive产生):

000000_0

000001_0

000002_0

000003_0

第一个桶里包括用户ID 0和4,因为一个INT的哈希值就是这个整数本 身,在这里即除以桶数(4)以后的余数:®

hive> dfs -cat /user/hive/warehouse/bucketed_users/000000_0;
0Nat
4Ann

TABLESAMPLE子句对表进行取样,我们可以获得相同的结果。这个子 句会将查询限定在表的一部分桶内,而不是使用整个表:

hive〉SELECT * FROM bucketed__users
> TABLESAMPLE(BUCKET 1 OUT OF 4 ON id);
0 Nat
4 Ann

桶的个数从1开始计数。因此,前面的査询从4个桶的第一个中获取所有 的用户。对于一个大规模的、均匀分布的数据集,这会返回表中约1/4的数 据行。我们也可以用其他比例对若干个桶进行取样(因为取样并不是一个精 确的操作,因此这个比例不一定是桶数的整数倍)。例如,下面的査询返回一半 的桶:

hive> SELECT * FROM bucketed_users
> TABLESAMPLE(BUCKET 1 OUT OF 2 ON id);
0 Nat 
4 Ann
2 3oe

因为査询只需要读取和TABLESAMPLE子句匹配的桶,所以取样分桶表是非 常高效的操作。如果使用rand()函数对没有划分成桶的表进行取样,即使 只需要读取很小一部分样本,也要扫描整个输入数据集:

hive> SELECT * FROM users
> TABLESAHPLE(BUCKET 1 OUT OF 4 ON rand());
2 Joe

12.6.3存储格式

Hive从两个维度对表的存储进行管理:“行格式”(row format)和“文件格 式”(file format)。行格式指行和一行中的字段如何存储。按照Hive的术 语,行格式的定义由SerDe定义。SerDe是“序列化和反序列化工具” (Serializer-Deserializer)的合成词。

当作为反序列化工具进行使用时——也就是査询表时——SerDe将把文件中 字节形式的数据行反序列化为Hive内部操作数据行时所使用的对象形式。 使用序列化工具时——也就是执行INSERT或CTAS(参见12.6.4节)时一表 的SerDe会把Hive的数据行内部表示形式序列化成字节形式并写到输出文 件中。

文件格式指一行中字段容器的格式。最简单的格式是纯文本文件,但是也 可以使用面向行的和面向列的二进制格式。

1.默认存储格式:分隔的文本如果在创建表时没有用ROW FORMAT或STORED AS子句,那么Hive所使用 的默认格式是分隔的文本(delimited text),每行(line)存储一个数据行(row)。

默认的行内分隔符不是制表符,而是ASCII控制码集合中的Control-A(它 的ASCII码为1)。选择Contml-A(在文档中有时记作"A)作为分隔符是因为 和制表符相比,它出现在字段文本中的可能性比较小。在Hive中无法对分 隔符进行转义,因此,挑选一个不会在数据字段中用到的字符作为分隔符 非常重要。

“集合”(collection)元素的默认分隔符为字符Control-B。它用于分隔 ARRAY或STRUCT或MAP的键/值对中的元素。默认的映射键(map key)分隔符为 字符Control-C。它用于分隔MAP的键和值。表中各行之间用换行符分隔。

前面对分隔符的描述对一般情况下的平面数据结构——即只包含原子数据类型的复杂数据类型—都是没有问题的。但是,对于嵌套数据 类型,这还不够。事实上,嵌套的层次(level)决定了使用哪种分隔符。

例如,对于数组的数组,外层数组的分隔符如前所述是Control-B字 符,但内层数组则使用分隔符列表中的下一项(Control-C字符)作为分隔符。如果不确定Hive使用哪个字符作为某个嵌套结构的分隔 符,可以运行下面的命令:

CREATE TABLE nested 
AS
SELECT array(array(l, 2), array(3, 4))
FROM dummy;

然后再使用hexdump或类似的命令来査看输出文件的分隔符。

实际上,Hive支持8级分隔符,分别对应于ASCII编码的1, 2,……,8。但是你只能重载其中的前三个。

因此,以下语句:

CREATE TABLE …;

等价于下面显式说明的语句:

CREATE TABLE ...
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE;

注意,我们可以使用八进制形式来表示分隔符—例如,001表示Control-A。

Hive在内部使用一个名为LazySimpleSerDe的SerDe来处理这种分隔格 式以及我们在第7章看到的面向行的MapReduce文本输入和输出格式。这 里使用前缀“lazy”的原因是这个SerDe对字段的反序列化是延迟处理的一 一只有在访问字段时才进行反序列化。但是,由于文本以冗长的形式进行 存放,所以这种存储格式并不紧凑。比如,一个布尔值事实上是以文本 字符串true或false的形式存放的。

这种简单的格式有很多好处,例如,使用其他工具(包括MapReduce程序或 Streaming)来处理这样的格式非常容易。但是还可以选择一些更紧凑和髙效 的二进制SerDe。表12-4列出了一部分可用的SerDe。

二进制SerDe不应该和默认的TEXTFILE格式(或显式的STORED AS TEXTFILE子句)一起使用。二进制数据行中几乎总是包含换行符, 这会导致Hive把行截断,从而在反序列化时失败。

12-4. Hive 的 SerDe

SerDe名称

Java 包

描述

LazySimpleSerDe

org.apache.hadoop.hive. serde2.lazy

这是默认的SerDe。采用分 隔的文本格式和延迟的字段 访问

LazyBinarySerDe

org.apache.hadoop.hive. serde2.lazybinary

LazySimpleSerDe 的一个更高效的实现。二进制形式的 延迟字段访问。供临时表这 样的表内部使用

BinarySortableSerDe

org.apache.hadoop.hive. serde2.binarysortable

类似于 LazyBinarySerDe 的 二进制SerDe,但它针对排序进 行了优化,损失了部分空间(但仍 然比 LazySimpleSerDe 精简

很多)

ColumnarSerDe

org.apache.hadoop. hive.serde2.columnar

针对RCFile格式的基于列存 储的 LazySimpleSerDe 变种

RegexSerDe

org.apache.hadoop. hive.contrib.serde2

一种根据用正则表达式给出 的列读取文本数据的 SerDe。它也能使用格式表达 式写入数据。对于读取日志 文件有用。它的效率不高, 因此不适合普通的存储

ThriftByteStreamTypedSerDe

org. apache. hadoop.hive.serde2.thrift

读取Thrift编码的二进制数据 的 SerDe

2.二进制存储格式:顺序文件、Avro数据文件以及RCFile

Hadoop的顺序文件格式(参见4.5.1)是一种针对顺序和记录(/值对)的通 用二进制格式。在Hive中,可以在CREATE TABLE语句中通过声明 STORED AS SEQUENCEFILE来使用顺序文件。

使用序列文件的一个主要的优点是它们支持可分割(splittable)的压缩。如果 你有一系列在Hive外创建的序列文件,则无须额外设置,Hive也能读取它 们。另一方面,如果你想使用压缩顺序文件来存储Hive产生的表,则需要 设置几个相应的属性来使用压缩(参见4.2.3节):

hive> CREATE TABLE compressed__users (id INT, name STRING)
>STORED AS SEQUENCEFILE;
hive> SET hive.exec.compress.output=true;
hive> SET mapred.output.compress=true;
hive> SET mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; 
hive> INSERT OVERWRITE TABLE compressed__users
>SELECT * FROM users;

顺序文件是面向行的。这意味着同一行中的字段作为顺序文件中的一条记 录被存储在一起。

Avro数据文件和顺序文件相似:可分割、可压缩、面向行。不同点是, Avro数据文件支持模式演化以及多种编程语言的绑定(参见4.4节)。Hive 可以使用Wajvvreo SerDe读取或写入Avro数据文件。Haivvreo SerDe可从 http://github.com/jghoman/haivvreo 获得。

Hive提供了另一种二进制存储格式,称为RCFile,表示按列记录文件 (Record Columnar File)。RCFile除了按列的方式存储数据以外,其他方面 都和序列文件类似。RCFile把表分成行分片(row split),在每一个分片中先 存所有行的第一列,再存它们的第二列,依此类推。图12-3用图示说明了 这种存储方式。

面向列的存储布局(colunm-oriented layout)方式可以使一个査询跳过那些不 必访问的列。让我们考虑一个只需要处理图12-3中表的第2列的査询。在 像顺序文件这样面向行的存储中,即使其实只需要读取第二列,整个数据 行(存储在顺序文件的一条记录中)也都会被加载到内存中。虽说在某种程度 上“迟反序列化”(lazy deserialization)策略通过只反序列化那些被访问的列 字段能节省一些处理开销,但这仍然不能避免从磁盘读入一个数据行所有 字节而额外付出的开销。

如果使用面向列的存储,只需要把文件中第2列所对应的那部分(图中的阴 影部分)读入内存。

图片.png 

12-3.面向行的和面向列的存储

一般来说,面向列的存储格式对于那些只访问表中一小部分行的查询比较 有效。相反,面向行的存储格式适合同时处理一行中很多列的情况。如果 存储空间足够,可以使用12.6.4节的CREATE TABLE…AS SELECT子句来 复制一个表,创建它的另一种储格式,从而直观地比较査询负载下两种 存储格式在性能上的差异。

Hive中,可以使用如下的CREATE TABLE子句来启用面向列的存储:

CREATE TABLE …
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe'
 STORED AS RCFILE;

3.示例:RegexSerDe

现在让我们看看如何使用另一种SerDe来进行存储。我们将使用一个用户 贡献的SerDe,它采用一个正则表达式从一个文本文件中读取定长的观测站

元数据:

CREATE TABLE stations (usaf STRING, wban STRING, name STRING)
ROW FORMAT SERDE 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
"input, regex" = n(\\d{6}) (\\d{5}) (.{29}) .*”
);

在前面的示例中,我们在ROW FORMAT子句中使用DELIMITED关键字来说 明文本是如何分隔的。在这个示例中,我们用另一种方式,用SERDE关键 字和实现SerDe的Java类的完整类名(即org.apache.hadoop.hive.colrtrib.serde2.RegexSer'De), 来指明使用哪个 SerDe。

SerDe可以用WITH SERDEPROPERTIES子句考设置额外的属性。在这里, 我们要设置RegexSerDe特有的input, regex属性。

input, regex是在反序列化期间将要使用的正则表达式模式,用来将数据 行(row)中的部分文本转化为列的集合。正则表达式匹配时使用java的正则 轰ih式语法(参见 http://java.sm.eom/javase/6/docs/api/java/util/fegex/Pattern.html)。教 们通过识别一组一组的括号来确定列(称为捕获组,即capturing group)。® 在这个示例中,有三个捕获组:usaf(六位数的标识符)、wban(五位数的标 识符)以及name(29个字符的定长列)。

我们像以前一样,用如下LOAD DATA语句向表中输入数据:

LOAD DATA LOCAL INPATH ”input/ncdc/metadata/stations-fixed-width.txt’J INTO TABLE stations;

回想一下,LOAD DATA把文件复制或移动到Hive的仓库目录中。在这里, 由于源在本地文件系统中,所以使用的是复制操作。加载操作并不使用表 的 SerDe。

从文件中检索数据时,如下面这个简单査询所示,反序列化会调用SerDe, 从而使每一行的字段都能被正确解析出来:

hive> SELECT * FROM stations LIMIT 4;
10000 99999 BOGUS NORWAY
 
010003 99999 BOGUS NORWAY
010010 99999 DAN MAYEN
010013 99999 ROST

12.6.4导入数据

我们已经见过如何使用LOAD DATA操作,通过把文件复制或移到表的目录 中,从而把数据导入Hive的表(或分区)。也可以用INSERT语句把数据从 一个Hive表填充到另一个;或在新建表的时候使用C7XS结构,CTAS是 CREATE TABLE……AS SELECT 的缩写。

如果想把数据从一个关系数据库直接导入Hive,可以看一下Sqoop。详情 参见15.6_1节。

1. INSERT 语句

下面是INSERT语句的一个示例:

INSERT OVERWRITE TABLE target 
SELECT  coll, co12
 FROM source;

对于分区的表,可以使用PARTITION子句来指明数据要插入哪个分区:

INSERT OVERWRITE TABLE target 
PARTITION (dt=,2001-01-01J)
SELECT coll, co12 
FROM source;

OVERWRITE关键字在这两种情况下都是强制的。这意味着目标表(对于前面 的第一个例子)或2001-01-01分区(对于第二个例子)中的内容会被 SELECT语句的结果替换掉。如果你要向已经填充了内容的非分区表或分 区添加记录,那么可以使用INSERT INTO TABLE。

你可以在SELECT语句中通过使用分区值来动态指明分区:

INSERT OVERWRITE TABLE target
 PARTITION (dt)
SELECT coll, col2, dt 
FROM source;

这种方法称为“动态分区插入”(dynamic-partition insert)。这一特性默认是 关闭的,所以在使用前需要先把hive.exec.dynamic.partition设为True。

和其他数据库不同,Hive(现在)不支持在INSERT语句中直接以文字 形式给出一组记录的形式。也就是说,Hive不允许INSERT INTO…VALUES…形式的语句。

2.多表插入

HiveQL中,可以把INSERT语句倒过来,把FROM子句放在最前面, 査询的效果是一样的:

FROM source
INSERT OVERWRITE TABLE target 
SELECT coll, col2;

可以在同一个查询中使用多个INSERT子句。•此时,这样的语法会让査询 的含义更清楚。这种“多表插入”(multitable insert)方法比使用多个单独的 INSERT语句效率更高,因为只需要扫描一遍源表就可以生成多个不相交的 输出。

下面的例子根据气象数据集来计算多种不同的统计数据:

FROM records2
INSERT OVERWRITE TABLE stations__by_year 
SELECT year, 
COUNT(DISTINCT station)
GROUP BY year
INSERT OVERWRITE TABLE records_by_year 
SELECT year, COUNT(l)
GROUP BY year
INSERT OVERWRITE TABLE good_records_by_year 
SELECT year, COUNT(l)
WHERE temperature != 9999
AND (quality = 0 OR quality = 1 OR quality = 4 OR quality = 5 OR quality = 9)
GROUP BY year;

这里只有一个源表(records2),但有三个表用于存放针对这个源表的三个 不同査询所产生的结果。

3.CREATE TABLE…AS SELECT 语句

Hive査询的输出结果存放到一个新的表往往非常方便,这可能是因为输 出结果太多,不适宜于显示在控制台上或基于输出结果还有其他后续处理。

新表的列的定义是从SELECT子句所检索的列导出的。在下面的査询中, target表有两列,分别名为coll和col2,它们的数据类型和源表中对应 的列相同:

CREATE TABLE target
 AS
SELECT coll, col2 
FROM source;

CTAS操作是原子的,因此如果SELECT査询由于某种原因失败,新表就不 会被创建。

12.6.5表的修改

 由于Hive使用“读时模式”(schema on read),所以在创建表以后,它非常 灵活地支持对表定义的修改。但一般需要警惕,在很多情况下,要由你来 确保修改数据以符合新的结构。

可以使用ALTER TABLE语句来重命名表:

ALTER TABLE source RENAME TO target;

在更新表的元数据以外,ALTER TABLE语句还把表目录移到新名称所对应 的目录下。在这个示例中,/user/hive/warehouse/source被重命名为/user/hive/warehouse/target。 对于外部表,这个操作只更新元数据,而不会移动目录。

Hive允许修改列的定义,添加新的列,甚至用一组新的列替换表内已有 的列。

例如,考虑添加一个新列:

ALTER TABLE target ADD COLUMNS (col3 STRING);

新的列co13添加在已有(非分区)列的后面。数据文件并没有被更新,因此 原来的査询会为col3的所有值返回空值null(当然,除非文件中原来就已 经有额外的字段)。因为Hive并不允许更新已有的记录,所以需要使用其他 机制来更新底层的文件。为冗,更常用的做法是创建一个定义了新列的新 表,然后使用SELECT语句把数据填充进去。

如果我们设想原来的数据类型可以用新的数据类型来进行解释,那么修改 一个表的元数据(如列名或数据类型)就变得更为直观。

要想更进一步了解如何修改表的结构,包括添加或丢弃分区、修改和替换 列,修改表和SerDe的属性,可访问Hive的英文维基页面,网址为 http://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL。

12.6.6表的丢弃

DROP TABLE语句用于删除表的数据和元数据。如果是外部表,就只删除元数据数据不会受到影响。

如果要删除表内的所有数据,但要保留表的定义(如MySQL的DELETE或 TRUNCATE),删除数据文件即可。例如:

hive>
dfs -rmr /user/hive/warehouse/my_table;

Hive把缺少文件(或根本没有表对应的目录)的表认为是空表。

另一种达到类似目的的方法是使用LIKE关键矣创建-个与第一个表模式相 同的新表:

CREATE TABLE new—table LIKE existing_table;

12.7查询数据

这一节讨论如何使用SELECT语句的各种形式从Hive中检索数据。

12.7.1排序和聚集

Hive中可以使用标准的ORDER BY子句对数据进行排序。但这里有一 个潜在的不利因素。ORDER BY能够预期产生完全排序的结果,但它是通 过只用一个reducer来做到这一点的。所以对于大规模的数据集,它的效率 非常低。Hive将来的发布版本有望使用8.2.3节介绍的技术来支持高效的

并行排序。

在很多情况下,并不需要结果是全局排序的。此时,可以换用Hive的非标 准的扩展SORT BY。SORT BY为每个reducer产生一个排序文件。

在有些情况下,你需要控制某个特定行应该到哪个reducer,其目的通常是 为了进行后续的聚集操作。这就是Hive的DISTRIBUTE BY子句所做的事 情。下面的例子根据年份和气温对气象数据集进行排序,以确保所有具有 相同年份的行最终都在同一个reducer分区中:®

hive> FROM records2 
>SELECT year, temperature
>DISTRIBUTE BY year
>SORT BY year ASC, temperature DESC;
1949  111
1949  78
1950  22
1950  0
1950  -11

后续的査询(或把这个査询作为内嵌子査询的某个査询,详情参见12.7.4节) 可以利用在同一文件中已经分好组并(以降序)排好序的年份气温。

如果SORT BY和DISTRIBUTE BY中所用的列相同,可以缩写为CLUSTER BY以便同时指定两者所用的列。

12.7.2MapReduce 脚本

Hadoop Streaming 类似,TRANSFORM、MAP 和 REDUCE 子句可以在 Hive中调用外部脚本。假设我们像范例12-1那样,用一个脚本来过滤不符 合某个条件的行,即删除低质量的气温读数。

范例12-1.过滤低质量气象记录的Python脚本

#!/usr/bin/env python
import re 
import sys
for line in sys.stdin:
(year, temp, q) = line.strip().split() 
if (temp != "9999" and re.match("[01459]", q)): 
print "%s\t%s" % (year, temp)

我们可以像范例12-2这样使用这个脚本。

范例12-2.使用Python脚本

hive> ADD FILE /Users/tom/book-workspace/hadoop-book/chl2/src/main/python/ is_gocxJ_quality.py;
 hive> FROM records2
    >SELECT TRANSFORM(year,temperature,quality)
    >USING ’is_good_quality.py’
    >AS year, temperature;
1950     0
1950     22
1950    -11
1949    111
1949    78

在运行査询之前,我们需要在Hive中注册脚本。通过这一操作,Hive知道 需要把脚本文件传输到Hadoop集群上(参见8.4.2节对分布式缓存的讨论)。

査询本身把year, temperature和quality这些字段以制表符分隔的行 的形式流式传递给脚本并把制表符分隔的输出解析为 year和temperature字段,最终形成査询的输出。

这一示例并不使用reducer。如果要用査询的嵌套形式,我们可以指定map 和reduce函数。这一次我们用MAP和REDUCE关键字。但在这两个地方用 SELECT TRANSFORM 也能达到同样的效果。max_temperature_reduce.py脚本的内容参见范例2-11:

FROM (  
        FROM records2
        MAP year, temperature, quality 
        USING 'is_good_quality.py'
        AS year, temperature) map__output 
    REDUCE year, temperature
    USING 'max_temperatureareduce.py'
    AS year, temperature;

12.7.3连接

和直接使用MapReduce相比,使用Hive的一个好处在于Hive简化了常用 操作。对比在MapReduce中实现“连接”(join)要做的事情(参见8.3节), 在Hive中进行连接操作就能充分体现这个好处。

1.内连接

内连接是最简单的一种连接。输入表之间的每次匹配都会在输出表里生成 一行。让我们来考虑两个演示用的小表:sales列出了人名及其所购商品 的ID; things列出商品的ID和名称:

hive> SELECT * FROM sales;
Doe     2
Hank     4
Ali     0
Eve     3
Hank     2
hive> SELECT * FROM things;
2     Tie 
4     Coat
3     Hat
1     Scarf

我们可以像下面这样对两个表进行内连接:

hive> SELECT sales.things.*
> FROM sales 30IN things ON (sales.id = things.id);
Joe   2   2   Tie
Hank  2   2   Tie
Eve   3   3   Hat
Hank  4   4   Coat

FROM子句中的表sales和JOIN子句中的表things用ON子句中的谓词进 行连接。Hive只支持等值连接(equijoin),这意味着在连接谓词中只能使用 等号。在这个示例中,等值条件是两个表的id列必须相同。

有些数据库,例如MySQL和Oracle,允许在SELECT语句的FROM 子句中列出要连接的表,而在WHERE子句中指定连接条件。但是 Hive并不支持这种语法,所以下面的语句会由于解析出错而导致运行 失败:

SELECT sales.*, things.*
FROM sales, things
WHERE sales.id = things.id;

Hive只允许在FROM子句中出现一个表。要进行连接操作,必须遵 循SQL-92中JOIN子句的语法。

Hive中,可以在连接谓词中使用AND关键字分隔的一系列表达式来连接 多个列。还可以在査询中使用多个JOIN…ON…子句来连接多个表。Hive 会智能地以最少MapReduce作业数来执行连接。

单个的连接用一个MapReduce作业实现。但是,如果多个连接的连接条件 中使用了相同的列,那么平均每个连接可以用少于一个MapReduce作业来 实现。®你可以在査询前使用EXPLAIN关键字来査看Hive将为某个査询使 用多少个MapReduce作业:

EXPLAIN
SELECT sales.things.*
FROM sales JOIN things ON (sales.id = things.id);

EXPLAIN的输出中有很多査询执行计划的详细信息,包括抽象语法树、 Hive执行各阶段之间的依赖图以及每个阶段的信息。一个阶段可能是像

MapReduce作业文件移动这样的一个操作。如果要査看更详细的信息,可以在 查询前使用 EXPLAIN EXTENDED。

Hive目前使用基于规则的査询优化器来确定査询是如何执行的。但在将 来,Hive很有可能会增加一个基于代价的优化器。

 2.外连接

外连接可以让你找到连接表中不能匹配的数据行。在前面的示例里,我们 在进行内连接时,AH那一行没有出现在输出中。因为她所购商品的ID没 有在things表中出现。如果我们把连接的类型改为LEFT OUTER JOIN, 即使左侧表(sales)中的有些行无法与所要连接的表(things)中的任何数据 行对应,査询还是会返回这个表中的每一个数据行:

hive> SELECT sales.*, things.*
> FROM sales LEFT OUTER JOIN things ON (sales.id = things.id)
Ali   0   NULL   NULL
Joe   2   2      Tie
Hank  2   2      Tie
Eve   3   3      Hat
Hank  4   4      Coat

注意,此时返回了 Ali所在的数据行,但因为这一行无匹配,所以things 表的对应列为空值NULL。

Hive也支持“右外连接”(right outer join),即和左连接相比,交换两个表 的角色。在这里,things表中的所有商品,即使没有任何人购买它们 (scarf),也都会被返回:

hive> SELECT sales.*, things.*
> FROM sales RIGHT OUTER 30IN things ON (sales.id = things.id);
NULL   NULL   1  Scarf
Joe     2     2  Tie
Hank    2     2  Tie
Eve     3     3  Hat
Hank    4     4  Coat

最后,还有一种“全外连接”(full outer join),即两个连接表中的所有行在 输出中都有对应的行:

hive> SELECT sales.*, things.*
> FROM sales FULL OUTER JOIN things ON (sales.id = things.id);
Ali   0   NULL   NULL
NULL  NULL 1     Scarf
Joe   2    2     Tie
Hank  2    2     Tie
Eve   3    3     Hat
Hank  4    4     Coat

3.半连接

Hive(在本书这一版写作时)并不支持IN子査询,但可以使用LEFT SEMI JOIN来达到相同的效果。

考虑如下IN子査询,它能够査找things表中在sales表中出现过的所有 商品:

SELECT *
FROM things
WHERE things.id IN (SELECT id from sales);

我们可以像下面这样重写这个査询:

hive〉 SELECT *
>FROM things LEFT SEMI DOIN sales ON (sales.id = things.id);
2   Tie
3   Hat
4   Coat

LEFT SEMI JOIN査询时必须遵循一个限制:右表(sales)只能在ON子 句中出现。例如,我们不能在SELECT表达式中引用右表。

4.map连接

如果有一个连接表小到足以放入内存,Hive就可以把较小的表放入每个 mapper的内存来执行连接操作。如果要指定使用map连接,需要在SQL中 使用C语言风格的注释,以给出提示:

hive> SELECT /*+ MAP30IN(things) */ sales.*, things.*
>FROM sales DOIN things ON (sales.id = things.id);
Joe  2    2    Tie
Hank 4    4   Coat
Eve  3    3    Hat
Hank 2    2   Tie

执行这个査询不使用reducer,因此这个査询对RIGHT或FULL OUTER JOIN无效,因为只有在对所有输入上进行聚集的步骤(即reduce)才能检测 到哪个数据行无法匹配。

Map连接可以利用分桶的表(参见12.6.2节),因为作用于桶的mapper加载 右侧表中对应的桶即可执行连接。这时使用的语法和前面提到的在内存中 进行连接是一样的,只不过还需要用下面的语法启用优化选项:

SET hive.optimize.bucketmapjoin=true;

12.7.4子查询

子査询是内嵌在另一个SQL语句中的SELECT语句。Hive对子査询的支持 很有限,它只允许子査询出现在SELECT语句的FROM子句中。

其他数据允许子查询出现在几乎任何表达式可以出现的地方,例如在SELECT语句带检索值的列表中或WHERE子句中。很多使用子查询的地方都可以重写为连接操作。因此,如果发现Hive不支持你写的子查询,可以看看能不能把它写成连接操作。例如,一个IN子查询可以写成一个半连接或内连接(参见12.7.3节)。

下面的査询可以找到每年每个气象站最高气温的均值:

SELECT station, year, AVG(max—temperature)
FROM (
    SELECT station, year, MAX(temperature) AS max一temperature
    FROM records2
    WHERE temperature != 9999
     AND (quality = 0 OR quality = 1 OR quality = 4 OR quality = 5 OR quality = 9) GROUP BY station, year 
)mt
GROUP BY station, year;

这里的子查询用于计算每个气象站/日期组合中的最高气温,然后外层査询 使用AVG聚集函数计算这些最高读数的均值。

外层査询像访问表那样访问子査询的结果,这是为什么必须为子査询赋予 一个别名(mt)的原因。子査询中的列必须有唯一的名称,以便外层査询可以 引用这些列。

12.7.5视图

视图是一种用SELECT语句定义的“虚表”(virtual table)。视图可以用来以 一种不同于磁盘实际存储的形式把数据呈现给用户。现有表中的数据常常 需要以一种特殊的方式进行简化和聚集以便于后期处理。视图也可以用来 限制用户,使其只能访问被授权可以看到的表的子集。

Hive中,创建视图时并不把视图“物化”(materialize)存储到磁盘上。相 反,视图的SELECT语句只是在执行引用视图的语句时才执行。如果一个视 图要对“基表”(base table)进行大规模的变换,或视图的査询会频繁执行,你可以选择新建一个表,并把视图的内容存储到新表中,以此来手工物化 它(参见 12.6.4 节对 CREATE TABLE…AS SELECT 语句的讨论)。

我们可以用视图重写前一节中的査询,它用于査找每年各个气象站气温最 大值的均值。首先,让我们为有效记录(即有特定quality值的记录)创建 一个视图:

CREATE VIEW valid_records 
AS
SELECT *
FROM records2
WHERE temperature != 9999
AND (quality = 0 OR quality = 1 OR quality = 4 OR quality = 5 OR quality = 9);

创建视图时并不执行査询,査询只是存储在metastore中。SHOW TABLES命 令的输出结果里包括视图。可以使用DESCRIBE EXTENDED view_name命令来査看某个视图的详细信息,包括用于定义它的那个査询。

接下来,让我们为每个观测站每年的最高气温创建第二个视图。这个视图 基于 valid_record 视图:

CREATE VIEW max_temperatures (station, year, max__temperature)
AS
SELECT station, year, MAX(temperature) FROM valid_records GROUP BY station, year;

在这个视图定义中,我们显式地列出了列的名称。我们这么做是因为最髙 气温列是一个聚集表达式,如果不指明,Hive会自己创建一个别名(例如 _c2)。我们也可以在SELECT语句中使用AS子句来为列命名。

有了这两个视图,现在我们就可以执行査询了:

SELECT station, year, AVG(max_temperature)
FROM max_temperatures 
GROUP BY station, year;

这个査询的结果和前面使用子査询的査询是一样的。特别地,Hive为它们 所创建的MapReduce作业的个数也是一样的:都是两个,每个GROUP BY 使用一个。从这个例子可以看到,Hive可以把使用视图的査询组织成一系 列作业,效果与不使用视图的査询一样。换句话说,即使在在执行时, Hive也不会在不必要的情况下物化视图。

Hive中的视图是只读的,所以无法通过视图为基表加载或插入数据。

12.8用户定义函数

你要写的査询有时无法轻松(或根本不能)使用Hive提供的内置函数来表 示。通过写“用户定义函数”(user-defined function, UDF), Hive可以方便 地插入用户写的处理代码并在査询中调用它们。

UDF必须用Java语言编写。Hive本身也是用Java写的。对于其他编程语 言,可以考虑使用SELECT TRANSFORM査询,有了它,可以让数据流式 通过用户定义脚本(参见12.7.2节)。

Hive中有三种UDF:(普通)UDF、用户定义聚集函数(user-defined aggregate function , UDAF)以及用户定义表生成函数(user-defined table-generating function, UDTF)。它们所接受的输入和产生的输出的数据行的数量是不 同的。

•UDF操作作用于单个数据行,巨产生一个数据行作为输出。大多 数函数(例如数学函数和字符串函数)都属于这一类。

•UDAF接受多个输入数据行,并产生一个输出数据行。像COUNT 和MAX这样的函数都是聚集函数。

• UDTF操作作用于单个数据行,且产生多个数据行 —一个表—作为输出。

和其他两种类型相比,表生成函数的知名度较低。所以让我们来看一个示 例。考虑这样一个表,它只有一列X,包含的是字符串数组。回头看看表的 定义和填充方式是很有启发的:

CREATE TABLE arrays (x ARRAY<STRING>) ROW
FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY ‘\002';

注意,ROW FORMAT子句指定数组中的项用Control-B字符分隔。我们要加载的示例文件内容如下,为了显示方便,用AB表示Control-B字符:

a^Bb
c^Bd^Be

在运行LOAD DATA命令以后,下面的査询可以确认数据已正确加载:

hive> SELECT * FROM arrays;
["a","b"]
["c","d","e"]

接下来我们可以使用explode UDTF对表进行变换。这个函数为数组中 的每一项输出一行。因此,在这里,输出的列y的数据类型为STRING。其 结果是,表被“平面化”(flattened)成五行:

hive> SELECT explode(x) AS y FROM arrays;
a
b 
c 
d  
e

UDTF的SELECT语句在使用时有一些限制(例如,它们不能检索额外的列表达式),使实际使用时这些语句的用处并不大。为此,Hive支持 LATERAL VIEW査询。这一语句的功能更强大。这里不介绍LATERAL VIEW 査询。

12.8.1UDF

为了演示如何写和使用UDF,我们将写一个简单的剪除字符串尾字符的 UDF。Hive已经有一个内置的名为trim的函数,所以我们把自己的函数 称为strip。Strip Java类的代码如范例12-3所示。

范例12-3.剪除字符串尾字符的UDF

package com.hadoopbook.hive;
 
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.ql.exec.UDF; 
import org.apache.hadoop.io.Text;
public class Strip extends UDF { 
    private Text result = new Text();
    
    public Text evaluate(Text str) { 
        if (str == null) { 
            return null;
        }
        result.set(StringUtiIs.strip(str.toString()));
        return result;
    }
    public Text evaluate(Text str, String stripChars) {
        if (str == null) {
        return null;
        }
    result.set(StringUtils.strip(str.toString()^ stripChars));
    return result;
    }
}

一个UDF必须满足下面两个条件:

•一个 UDF 必须是 org. apache . hadoop. hive. ql. exec.UDF 的子类

•一个UDF必须至少实现了 evaluate()方法

evaluate()方法不是由接口定义的,因为它可接受的参数的个数、它们的 数据类型及其返回值的数据类型都是不确定的。Hive会检査UDF,看能否 找到和函数调用相匹配的evaluate()方法。

这个Strip类有两个evaluate。方法。第一个方法去除输入的前导和结 束的空白字符;而第二个方法则去除字符串尾出现在指定字符集合中的任 何字符。实际的字符处理工作交由Apache Commons项目里的 StringUtils类来完成。所以代码中唯一值得一提的是对Hadoop Writable 库中Text的使用。实际上,Hive支持在UDF中使用Java的基本类型(以 及其他一些像java.util.List和;java.util.Map这样的类型),所以, 下面这样函数签名(signature)的效果是一样的:

public String evaluate(String str)

但是,通过使用Text,我们可以利用对象重用的优势,增效节支。因此一 般推荐使用这种方法。

为了在Hive中使用UDF,我们需要把编译后的Java类打包(package)成一 个JAR文件(可以用本书所附代码输入ant hive来完成),并在Hive中注 册这个文件:

ADD JAR /path/to/hive-examples.jar;

我们还需要为Java的类名起一个別名:

CREATE TEMPORARY FUNCTION strip AS com.hadoopbook.hive.Strip;

这里的TEMPORARY关键字强调了这样的事实:UDF只是为这个Hive会话 过程定义的(它们并没有在metastore中持久化存储)。事实上,这意味着你 需要在每个脚本的最前面添加JAR文件或定义函数,或者在主目录创建包 含这些命令的.hiverc文件,以使得这些UDF会在每个Hive会话开始时 运行。

要想在开始时调用ADD JAR,还可以在Hive启动时指定査找附加 JAR文件的路径,这个路径会被加入Hive的类路径(也包括 MapReduce的类路径)。这种技术对于毎次运行Hive时自动添加你 的UDF库是很有用的。

有两种指明路径的办法:在hive命令后传递–auxpath选项:

% hive --auxpath /path/to/hive-examples.jar

或在运行Hive前设置HIVE_AUX_]ARS_PATH环境变量。附加路径可以是一个用逗号分隔的MR文件路径列表或包含JAR文件的 目录。

现在像使用内置函数一样使用UDF:

hive> SELECT strip('bee') FROM dummy; 
bee
hive〉 SELECT strip('banana', ’ab') FROM dummy; 
nan
注意,UDF名不是大小写敏感的:
hive> SELECT STRIP('bee') FROM dummy;
bee

12.8.2UDAF

聚集函数比普通的UDF难写。因为值是在块内进行聚集的(这些块可能分布 在很多map或reduce任务中),从而实现时要能够把部分的聚集值组合成最 终结果。实现此功能的代码最好用示例来进行解释。让我们来看一个简单 的UDAF的实现,它用于计算一组整数的最大值(范例12-4)。

范例12-4.计算一组整数中最大值的UDAF

package com.hadoopbook.hive; 
import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator; 
import org.apache.hadoop.io.IntWritable;
public class Maximum extends UDAF {
    public static class MaximumlntUDAFEvaluator implements UDAFEvaluaton {
        private IntWritable result;
        public void init() { 
            result = null;
        }
        public boolean iterate(IntWritable value){
            if (value == null) {
                return true;
            if (result == null) {
                result = new IntWritable(value.get());
            } else {
                result.set(Math.max(result.get(), value.get()));
            }
            return true;
            }
        public IntWritable terminatePartial() {
            return result;
        }
        public boolean merge(IntWritable other) { 
            return iterate(other);
        }
        public IntWritable terminate { 
            return result;
        }
    }
}

这个类的结构和UDF的稍有不同。UDAF必须是org.apache.hadoop. hive.ql.exec.UDAF(注意UDAF中的“A”)的子类,且包含一个或多个 嵌套的、实现了 org.apache.hadoop.hive.ql.UDAFEvaluator 的静态 类。在这个示例中,只有一个嵌套类,MaximumlntUDAFEvaluator。但是 我们也可以添加更多的计算函数(如MaximumLongUDAFEvaluator和 MaximumFloatUDAFEvaluator)来提供计算长整型、浮点型等类型数最大 值的UDAF的重载。

一个计算函数必须实现下面5个方法(处理流程如图12-4所示)。

• init()方法 init()方法负责初始化计算函数并重设它的内部状态。在 MaximumlntUDAFEvaluator中,我们把存放最终结果的 IntWritable对象设为null。我们使用null来表示目前还没有 对任何值进行聚集计算,这和对空集NULL计算最大值应有的结果是一致的。

图片.png 

12-4.包含UDAF部分结果的数据流

•iterate()方法每次对一个新值进行聚集计算时都会调用iterate() 方法。计算函数要根据聚集计算的结果更新其内部状态。 iterate()接受的参数和Hive中被调用函数的参数是对应的。在 这个示例中,只有一个参数。方法首先检査参数值是否为null, 如果是,则将其忽略。否则,resul变量实例就被设为value的整 数值(如果这是方法第一次接受输入),或设为当前值和value值中 的较大值(如果已经接受一些值)。如果输入值合法,我们就让方法 返回true0

•terminatePartial()方法 Hive需要部分聚集结果时会调用 terminatePartial()方法。这个方法必须返回一个封装了聚集计 算当前状态的对象。在这里,因为只需要对已知的最大值或在没有

值时的空值null进行封装,所以使用一个intWritable即可。

•merge()方法在Hive决定要合并一个部分聚集值和另一个部分聚 集值时会调用merge()方法。该方法接受一个对象作为输入。这个对 象的类型必须和terminatePartial()方法的返回类型一致。在这 个示例里,merge()方法可以直接使用iterate()方法,因为部分 结果的聚集和原始值的聚集的表达方法是相同的。但一般情况下不 能这样做(我们后面会看到更普遍的示例),这个方法实现的逻辑会 合并计算函数和部分聚集的状态。

•terminate()方法 Hive需要最终聚集结果时会调用terminate()方法。计算函数需要把状态作为一个值返回。在这里,我们返回实 例变量result。

现在让我们来执行这个新写的函数:

hive> CREATE TEMPORARY FUNCTION maximum AS’com.hadoopbook.hive.Maximum’;
hive> SELECT maximum(temperature) FROM records;
111

1.—个更复杂的UDAF

前面的示例有一个特别的现象:部分聚集结果可以使用和最终结果相同的 类型(IntWritable)来表示。对于更复杂的聚集函数,情况并非如此。考虑 一个计算一组double类型值均值的UDAF,就可以看出这一点。从数学角 度来看,要把两个部分的均值合并成最终的均值是不可能的(参见2.4.2节

combiner函数的讨论)。作为替代,我们可以用一个数对 目前已经处理过的double值的累积和,以及目前已经处理过的数的个数一来表示部分 聚集结果。

这个思路在UDAF中的实现如范例12-5所示。注意,部分聚集结果用一个 嵌套的静态类struct实现,类名是PartialResult,由于我们使用了 Hive 能够处理的字段类型(Java原子数据类型),所以Hive足够“聪明”,能够 自己对这个类进行序列化和反序列化。

范例12-5.计算一组double值均值的UDAF

package com.hadoopbook.hive;
 
import org.apache.hadoop.hive.ql.exec.UDAF;
import org.apache.hadoop.hive.ql.exec.UDAFEvaluator;
import org.apache.hadoop.hive.serde2.io.DoubleWritable;
public class Mean extends UDAF{
    public static class MeanDoubleUDAFEvaluator implements UDAFEvaluator {
        public static class PartialResult {
            double sum; 
            long count;
    }
    private PartialResult partial;
    
    public void init() { 
        partial = null;
    }
    public boolean iterate(DoubleWritable value) { 
     if (value == null) { 
            return true;
        }
        if (partial == null) {
            partial = new PartialResult();
        }
        partial.sum += value.get();
        partial.count++;
        return true;
    }
    
    public PartialResult terminatePartial() {
        return partial;
    }
    public boolean merge(PartialResult other) { 
        if (other == null) { 
            return true;
        }
        if (partial == null) { 
            partial = new PartialResult();
        }
        partial.sum += other.sum;
        partial.count += other.count; 
        return true;
    }
    public DoubleWritable terminate() { 
        if (partial == null) {
            return null;
        }
        return new DoubleWritable(partial.sum / partial.count);
        }
    }
}

在这个示例中,merge()方法和iterate()方法不同,因为它把“部分 和”(partial sum)和“部分计数值”(partial count)分别进行成对的加法合 并。此外,terminatePartial()的返回类型为PartialResult,这个类 型当然不会给调用函数的用户看到,terminate()的返回类型则是最终用户 可以看到的DoubleWritable。

转载请注明:全栈大数据 » 12 关于Hive

喜欢 (1)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址