Hive A Warehousing Solution Over a MapReduce Framework

什么是Hive,为什么需要Hive

Hadoop是一种流行的开源map-reduce实现,被用作在商品硬件上存储和处理极大的数据集的替代方案。然而,map-reduce编程模型非常低级,并且需要开发人员编写难以维护和重用的自定义程序。Hive是一个建立在Hadoop之上的开源数据仓库解决方案。Hive支持使用类似SQL的声明性语言HiveQL表示的查询,这些查询被编译成在Hadoop上执行的map-reduce作业。此外,HiveQL支持将自定义map-reduce脚本插入到查询中。该语言包括一个类型系统,支持包含原始类型、数组和映射等集合以及相同类型的嵌套组合的表。底层的IO库可以扩展以查询自定义格式的数据。Hive还包括一个系统目录,Hive-Metastore,其中包含模式和统计信息,这在数据探索和查询优化中非常有用。

HIVE DATABASE

Data Model

Hive中的数据组织方式如下:

- 这类似于关系数据库中的表。每个表都有一个对应的HDFS目录。表中的数据被序列化并存储在该目录中的文件中。用户可以将表与底层数据的序列化格式关联起来。Hive提供了内置的序列化格式,利用了压缩和惰性反序列化。用户还可以通过定义Java编写的自定义序列化和反序列化方法(称为SerDe)来添加对新数据格式的支持。每个表的序列化格式存储在系统目录中,并在查询编译和执行期间由Hive自动使用。Hive还支持在存储在HDFS、NFS或本地目录中的数据上的外部表。

分区 - 每个表可以有一个或多个分区,确定表目录的子目录中数据的分布。假设表T的数据在目录/wh/T中。如果T在列dsctry上进行分区,则具有特定ds值20090101和ctry值US的数据将存储在目录/wh/T/ds=20090101/ctry=US中的文件中。

- 每个分区中的数据可以根据表中某一列的哈希值再次分成桶。每个桶都作为一个文件存储在分区目录中。

Hive支持原始列类型(整数、浮点数、通用字符串、日期和布尔值)和可嵌套的集合类型(数组和映射)。用户还可以通过编程方式定义自己的类型。

Query Language

Hive提供了一个名为HiveQL的类似SQL的查询语言,支持在from子句中的select、project、join、aggregate、union all和子查询。HiveQL支持数据定义(DDL)语句,以创建具有特定序列化格式、分区和桶列的表。用户可以通过load和insert数据操作(DML)语句从外部源加载数据并将查询结果插入到Hive表中。HiveQL目前不支持更新和删除现有表中的行。HiveQL支持多表插入,用户可以使用单个HiveQL语句对相同的输入数据执行多个查询。Hive通过共享输入数据的扫描来优化这些查询。

HiveQL也非常可扩展。它支持在Java中实现的用户定义的列转换(UDF)和聚合(UDAF)函数。此外,用户可以使用简单的基于行的流接口嵌入使用任何语言编写的自定义map-reduce脚本,即从标准输入读取行并将行写入标准输出。

Running Example: StatusMeme

现在我们介绍一个高度简化的应用程序,名为Status-Meme,用户更新其状态时,更新会被记录在一个NFS目录/logs/status updates的日志文件中,这些文件每天轮换一次。我们每天使用如下的load语句将这些数据加载到Hive表*status updates(userid int,status string,ds string)*中。

1
2
LOAD DATA LOCAL INPATH `/logs/status_updates'
INTO TABLE status_updates PARTITION (ds='2009-03-20')

每个状态更新记录包含用户标识符(userid)、实际状态字符串(status)以及状态更新发生的日期(ds)。该表在ds列上进行分区。用户详细资料信息,如用户的性别和所在学校,可在profiles(userid int,school string,gender int)表中找到。

我们首先想要根据用户所在的学校和性别计算每天状态更新频率的统计信息。以下多表插入语句使用一次连接status updatesprofiles表的扫描,生成按学校(插入到school summary(school string,cnt int,ds string)中)和性别(插入到gender summary(gender int,cnt int,ds string)中)计算的每日状态更新计数。请注意,输出表也在ds列上进行分区,而HiveQL允许用户将查询结果插入到输出表的特定分区中。

1
2
3
4
5
6
7
8
9
10
11
FROM (SELECT a.status, b.school, b.gender
FROM status_updates a JOIN profiles b
ON (a.userid = b.userid and
a.ds='2009-03-20' )
) subq1
INSERT OVERWRITE TABLE gender_summary
PARTITION(ds='2009-03-20')
SELECT subq1.gender, COUNT(1) GROUP BY subq1.gender
INSERT OVERWRITE TABLE school_summary
PARTITION(ds='2009-03-20')
SELECT subq1.school, COUNT(1) GROUP BY subq1.school

接下来,我们想要显示每个学校的十个最受欢迎的memes,这是由那些就读于该学校的用户的状态更新所确定的。现在我们展示如何使用HiveQL的map-reduce构造来完成这个计算。我们通过插入一个自定义的Python映射器脚本meme-extractor.py来解析状态更新和profiles表之间的连接结果,该脚本使用复杂的自然语言处理技术从状态字符串中提取meme。由于Hive尚不支持排名聚合函数,因此可以通过一个简单的自定义Python reduce脚本top10.py来计算每个学校的前10个memes

1
2
3
4
5
6
7
8
9
10
11
12
REDUCE subq2.school, subq2.meme, subq2.cnt
USING `top10.py' AS (school,meme,cnt)
FROM (SELECT subq1.school, subq1.meme, COUNT(1) AS cnt
FROM (MAP b.school, a.status
USING `meme-extractor.py' AS (school,meme)
FROM status_updates a JOIN profiles b
ON (a.userid = b.userid)
) subq1
GROUP BY subq1.school, subq1.meme
DISTRIBUTE BY school, meme
SORT BY school, meme, cnt desc
) subq2;

HIVE ARCHITECTURE

Hive的主要组件包括:

  • 外部接口
    Hive提供用户界面(如命令行(CLI)和Web UI)和应用程序编程接口(API)(如JDBC和ODBC)。
  • Hive Thrift Server
    暴露了一个非常简单的客户端API来执行HiveQL语句。在不同语言中生成的Thrift Hive客户端用于构建常见的驱动程序,如JDBC(Java)、ODBC(C++)和用php、perl、python等编写的脚本驱动程序。
  • Metastore
    Hive的所有其他组件都与元数据存储库交互
  • Driver
    管理HiveQL语句编译、优化和执行的生命周期。在从Thrift服务器或其他接口接收到HiveQL语句时,它创建一个会话句柄,稍后用于跟踪执行时间、输出行数等统计信息。编译器在接收到HiveQL语句时由驱动程序调用。编译器将此语句转换为一个计划,该计划由一组map-reduce作业的DAG组成。驱动程序按拓扑顺序将DAG中的单个map-reduce作业提交给执行引擎。Hive目前使用Hadoop作为其执行引擎。
graph LR

C1-1(CLI) --> C2(Thrift Sever)
C1-2(JDBC/ODBC) --> C2
C1-3(WebGUI) --> C2
C2 --> C3(Driver
Compiler, Optimizer, Executor) C3 --> C5(Hadoop) C4(MetaStore)

Metastore

Metastore是包含有关存储在Hive中的表的元数据的系统目录(System Catalog)。这些元数据在表创建期间被指定,并在每次在HiveQL中引用表时被重用。与基于map-reduce的类似数据处理系统相比,metastore将Hive区分为传统的仓储解决方案(如Oracle或DB2)。

Metastore包含以下对象:

  • 数据库 - 是表的命名空间。默认数据库用于没有用户提供的数据库名称的表。
  • - 表的元数据包含列及其类型、所有者、存储和SerDe信息的列表。它还可以包含任何用户提供的键值数据;这个功能可以用来在未来存储表统计信息。存储信息包括表数据在底层文件系统中的位置、数据格式和桶信息。SerDe元数据包括序列化器和反序列化器方法的实现类以及该实现所需的任何支持信息。所有这些信息都可以在创建表时提供。
  • 分区 - 每个分区可以有自己的列、SerDe和存储信息。这可以在未来用于支持Hive仓库中的模式演变。

Metastore应该针对随机访问和更新进行在线事务优化。像HDFS这样的文件系统不适合,因为它针对顺序扫描而不是随机访问进行了优化。因此,Metastore使用传统的关系型数据库(如MySQL、Oracle)或文件系统(如本地、NFS、AFS),而不是HDFS。因此,仅访问元数据对象的HiveQL语句具有非常低的延迟。但是,Hive必须明确地维护元数据和数据之间的一致性。

Compiler

Driver使用HiveQL字符串调用编译器,该字符串可以是DDL、DML或查询语句之一。编译器将字符串转换为计划。在DDL语句的情况下,计划仅包含元数据操作,在LOAD语句的情况下,计划包含HDFS操作。对于插入语句和查询,计划由一组map-reduce作业的有向无环图(DAG)组成。

  • 解析器将查询字符串转换为解析树表示。

  • 语义分析器将解析树转换为基于块的内部查询表示。它从元数据存储库中检索输入表的模式信息。使用此信息,它验证列名,展开select *并执行类型检查,包括添加隐式类型转换。

  • 逻辑计划生成器将内部查询表示转换为逻辑计划,该计划由逻辑运算符树组成。

  • 优化器对逻辑计划进行多次遍历,并以多种方式重写它:

    • 将共享连接键的多个连接合并为单个多路连接,因此只需一个map-reduce作业。
    • 为连接、group-by和自定义map-reduce运算符添加重新分区运算符(也称为ReduceSinkOperator)。这些重新分区运算符标记了物理计划生成期间的map阶段和reduce阶段之间的边界。
    • 尽早修剪列,并将谓词推到表扫描运算符附近,以最小化运算符之间传输的数据量。
    • 在分区表的情况下,修剪查询不需要的分区。
    • 在抽样查询的情况下,修剪不需要的桶。
  • 用户还可以向优化器提供提示,以处理大基数分组聚合:

    • 添加部分聚合运算符
    • 添加重新分区运算符以处理分组聚合中的偏斜
    • 在map阶段而不是reduce阶段执行连接
  • 物理计划生成器将逻辑计划转换为由一组map-reduce作业的有向无环图(DAG)组成的物理计划。它为逻辑计划中的每个标记运算符(如repartition和union all)创建一个新的map-reduce作业。然后,它将标记之间的逻辑计划部分分配给map-reduce作业的mappers和reducers。