什么是数据仓库?
数据仓库,是由数据仓库之父比尔·恩门于1990年提出,他在1991年出版的 “Building the Data Warehouse” 一书中定义了数据仓库 —— 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。也就是说,数据仓库的根本目的是支持企业内部的商业分析和决策,企业的管理者可以基于数据仓库的分析结果,做出相关的经营决策,在企业中有着很广泛的应用。从上面数据仓库的定义中,小伙伴们会发现数据仓库的一个重要特征就是 相对稳定,这也是数据仓库跟传统的数据库有一个本质上的区别,也就是说,数据仓库中的数据不会频繁发生变化,或者根本不会发生变化。我们从不同的数据源中采集数据,经清洗、转换、加载到数据仓库之后,很多情况下,数据是只读的,我们就不再对数据进行变更。这一点就与传统的数据库有很大的差别,传统的数据库往往只能存储某个时刻的数据状态。举个小例子,马上要到春节了,漂泊在外的小伙伴们要抢票回家过年。那么铁路局的余票就是存储在传统的数据库中,每卖出一张票,余票都会减少1。也就是说,售票这张表里的余票字段只会记录最新时刻的数字,历史的信息不会被保留在这张表中。而数据仓库则会保留历史的状态,比如哪个用户在哪个时间买了哪个车次的哪个……车票都会被保存在数据仓库中。那就有小伙伴会问了,我们花费这么多存储能力去保留这些看似没啥用的数据真的有必要吗?有用,非常有用!数据仓库不单单具备业务上的价值,更可以支持管理层做出某些决策。比如说,一个超市可以根据大量的历史销售信息,进行多维数据分析,超市可以通过数据仓库分析过去几年里在某个区域的销售情况。到这里,又有小伙伴可能会问了,历史数据存在传统的数据库不也行吗,为啥还要非另立门户搞个数据仓库呢?这就要讲到传统的数据库在大数据时代所面临的挑战了:1、传统的数据库无法满足企业快速增长的海量数据存储需求;2、传统的数据库无法有效处理不同类型的数据(RDBMS只能支持结构化数据存储);3、传统数据库的计算能力严重不足,纵向扩展能力有限,横向扩展能力基本没有。总起来说,传统的数据库在大数据时代真的很无力啊,只能请本篇的主角 数据仓库 出场了!
Hive vs. 传统数据库
正如上面介绍数据仓库时所提到的那样,随着大数据时代的到来,企业的数据量猛增,传统的数据库无法更好地满足企业的应用需求。所以,基于 Hadoop 平台构建的 Hive 数据仓库产品,很自然地填补了市场的空白,受到各大公司的青睐。
我们知道,传统数据库即能支持数据存储,又能支持数据处理分析。但是 Hive 本身并不支持数据存储和数据处理,我们可以把 Hive 看做一个架构在 Hadoop 之上的顶层编程接口,只需要用户使用类似于 SQL 语言的 HiveQL 即可实现对数据的查询和分析。Hive 借助 Hadoop 的 HDFS 进行分布式文件存储,借助 MapReduce 进行并行计算。
HiveQL 是一种类似 SQL 的查询语言,学习成本很低,原先使用过传统数据库的开发人员,也能很容易接受 HiveQL。Hive 会将 HiveQL 转换成具体的 MapReduce 任务,利用 Hadoop 集群的计算资源,非常适合处理分析大规模数据。
Hive 的基本架构以及访问方式
先上一张 Hive 的架构图:
小伙伴们可以从上面的架构图中看出,Hive 对外提供了三种访问方式,包括 CLI、HWI 和 Thrift 协议(支持 JDBC/ODBC):
-
CLI 是一种命令行工具(Command Line Interface),也是小伙伴们自学时常用的一种方式,只需要在 Client 机上输入
hive
即可访问 Hive:
- HWI 是 Hive 的 Web 接口(Hive Web Interface),通过 Web 的方式访问 Hive,具有简单的展示功能,生产环境一般推荐选择使用 Hue。具体的配置使用,这里就不再演示,感兴趣的小伙伴们,可以在这里找到:https://cwiki.apache.org/confluence/display/Hive/GettingStarted
- Thrift Server 是基于 Thrift 架构开发的接口,允许外界通过这个接口,实现对 Hive 仓库的 RPC 调用。
Hive 中另一个非常重要的模块是 Driver,其主要负责把用户输入的 HiveQL 语句转换成一系列的 MapReduce 作业。Driver 驱动模块包括编译器、优化器和执行器:
- 解析器 将 SQL 语句转换成抽象语法树 AST,并对 AST 进行语法分析,比如表是否存在、字段是否存在,SQL 语义是否有误等。
- 优化器 是对解析器产生的逻辑执行计划进行优化。
- 执行器 是把逻辑执行计划转换成可以运行的物理计划,如上图的 MapReduce、Tez 和 Spark。Hive 本身并不会生成 MapReduce 程序,而是通过一个表示 “Job 执行计划” 的 XML 文件驱动执行内置的、原生的 Mapper 和 Reducer 模块。
Hive 的元数据存储 Metastore:Hive 的默认元数据信息是存储在 Derby 里面。元数据包括,表名,表所属的数据库,表的拥有者,列,分区字段,表的类型和表中数据所在目录等。但是 Hive 内置的 Derby 是单 Session 的,生产环境中,我们往往将 Hive 的元数据存储到 MySQL 中。
这里有一个坑,需要小伙伴们注意,Hive 是 Read on Schema
,只有读数据的时候才会将 Schema 与数据映射,在写数据的时候,只是写到一个目录下,无论数据是否可以与 Schema 映射,都可以写入成功。但是,MySQL 是 Write on Schema
在数据插入的时候如果无法映射,则会插入失败。所以,小伙伴们不要误以为,数据成功写入就万事大吉了!
Hive 的工作原理
在前面我们简单提到,Hive 本身不做具体的数据处理和存储,而是将 SQL 语句转换成 MapReduce 任务,对存储在 HDFS 上的数据进行操作,那整个转换过程是如何实现的呢?下面我们举例来详细介绍一下其工作原理:
案例一:在 MySQL 中,多表连接查询是非常常见的操作,Hive 是如何将 JOIN 操作转换成 MapReduce 呢?
我们这里有两张表,User
表和 Order
表,我们可以通过字段 uid
执行对这两张表的连接操作 SELECT table1.name, table2.order FROM user table1 JOIN order table2 ON table1.uid = table2.uid;
。
-
Map 阶段
在 MapReduce 任务中,首先把两张表分别进行 Map 操作,生成 <key, value> 的形式,key
即是User
表和Order
表的uid
,而value
则分别对应name
和order
字段,然后,User
表经过 Map 阶段,会生成 <key = uid, value = <表号, name>> 这种数据格式,由于User
表是我们执行 JOIN 的第一张表,标记为1
。所以,对于User
表中的第一条记录,<uid = 1, name = Lily> 经过 Map 阶段转换为 <key = 1, value = <1, Lily>>;对于Order
表中的第一条记录,<uid = 1, order = 皮皮虾> 经过 Map 阶段转换为 <key = 1, value = <2, 皮皮虾>>。 -
Shuffle 阶段
Shuffle 阶段会根据 Map 阶段的key
执行 Partition、Sort 以及 Spill 生成 Reduce 阶段的输入,这里有几个 Partition 就会有几个 Reduce。具体的细节,小伙伴们可以在数据科学之路的另一篇文章中找到:https://www.jianshu.com/p/4ff6744751c9,这里就不再赘述了。 -
Reduce 阶段
对于 Shuffle 的结果数据,小伙伴们可以看到,相同的key
下,value
中既会存在User
表的数据,也会存在Order
表的数据,由于我们是在做JOIN
操作,所以我们需要在 Reduce 阶段将同一个uid
下的name
和order
字段连接起来,形成一张新的表。至此,JOIN
操作才算完成!
案例二:Group by 可以将不同维度下的数据进行聚合统计,那么Hive 是如何将 Group by 操作转换成 MapReduce 呢?
我们这里有一张 employee
表,记录了员工的国籍和城市,我们想知道员工在各个城市的分布人数,就需要 group by
进行聚合统计:SELECT country, city, count(*) AS nums FROM employee GROUP BY country, city;
。
-
Map 阶段 首先将字段
country
和city
进行组合,形成 Map 阶段的key
,上面的例子中,我们有两种组合方式:<中国, 北京> 和 <美国, 纽约>,然后遍历得到每个key
对应的value
,即是对每个组合key
进行累计加和。 -
Shuffle 阶段 Shuffle 阶段会根据 Map 阶段的
key
执行 Partition、Sort 以及 Spill 生成 Reduce 阶段的输入,这里有几个 Partition 就会有几个 Reduce。具体的细节,小伙伴们可以在数据科学之路的另一篇文章中找到:https://www.jianshu.com/p/4ff6744751c9,这里就不再赘述了。 -
Reduce 阶段 将相同的
key
对应的value
进行累加,至此,Group by
操作完成!
Hive 的数据类型
Hive 的数据类型跟我们平常使用的关系型数据库 MySQL 很类似:
类型 | 描述 | 示例 |
---|---|---|
tinyint | 1个字节,有符号整数 | 1 |
smallint | 2个字节,有符号整数 | 1 |
int | 4个字节,有符号整数 | 1 |
bigint | 8个字节,有符号整数 | 1 |
float | 4个字节,单精度浮点数 | 1.0 |
double | 8个字节,双精度浮点数 | 1.0 |
boolean | 布尔类型,true/false | true |
string | 字符串,类似于SQL中的varchar类型,最大支持2G | "hi" |
timestamp | 时间戳,精确到秒 | 1579430829 |
binary | 字节数组 | [0,1,0,1] |
Hive HA
Hive 在实际的企业应用中,很多时候会表现出不稳定(比如,端口调用无响应,进程丢失等)。对于 Hive 不稳定的问题,业界给出了高可用性 Hive 解决方案(Hive High Availability)。
在上图中,小伙伴们会发现,Hive HA 即是设置多个 Hive 实例,放到资源管理池中,并设置了统一的对外访问接口 HAProxy。也就是说,外部的访问并不会直接到达 Hive 实例,而是通过 HAProxy 到达 Hive 实例,所以,Hive HA 在外部使用看来,并没有发生任何改变,外部用户可以简单将 HAProxy 视为一个很强大的 Hive 实例。
对于外部的访问请求,首先会访问 HA Proxy,HA Proxy 会依次对底层的各个实例进行询问(逻辑可用性测试),如果该 Hive 实例可以使用,那么 HA Proxy 将会将该请求转发到该实例。如果该 Hive 实例不可用,就把该 Hive 实例加入黑名单,继续访问下一个 Hive 实例是否可用,直到找到可用的 Hive 实例去执行外部的访问请求。每隔一定的时间,HA Proxy 会重新对黑名单上的 Hive 实例进行重启,如果重启之后,该实例可用了,则重新放回资源池中。
Hive 的简单使用
首先,我们要建立一个数据库:
hive> create database if not exists corpus;
然后,在 corpus 库中 新建表:
hive> use corpus;
hive> CREATE EXTERNAL TABLE mdm_corpus_yx_labeled (
`uid` string,
`serial` string,
`query` string COMMENT '问题',
`domain` string COMMENT '领域',
`intent` string COMMENT '意图',
`slots` string COMMENT '分槽,实体词',
`marked_domain` string COMMENT '标记的领域',
`marked_intent` string COMMENT '标记的意图',
`marked_slots` string COMMENT '标记的实体词',
`sid` string COMMENT '机器人sn号',
`b_answer` string COMMENT '回答')
PARTITIONED BY (`dates` STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
COLLECTION ITEMS TERMINATED BY ','
MAP KEYS TERMINATED BY ':'
STORED AS PARQUET
LOCATION 'hdfs://xxxx/usr/corpus/unit_yx/mdm/mdm_corpus_yx_labeled';
限于篇幅原因,本篇到这里就结束了,涉及到 Hive 建表语句中的各种细节以及Hive的更多使用和操作,我们将在下一篇详细讲解,想了解更多的数据科学知识的小伙伴们,可以关注我哦!