Hadoop学习笔记(5)-Hive

概述

  Hive是建立在Hadoop上的数据仓库基础架构。它提供了一系列的工具,用来进行数据提取、转换、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据机制。可以把Hadoop下结构化数据文件映射为一张成Hive中的表,并提供类sql查询功能,除了不支持更新、索引和事务,sql其它功能都支持。可以将sql语句转换为MapReduce任务进行运行,作为sql到MapReduce的映射器。提供shell、JDBC/ODBC、Thrift、Web等接口。

  Hive 并不适合那些需要低延迟的应用,例如,联机事务处理(OLTP)。Hive 查询操作过程严格遵守Hadoop MapReduce 的作业执行模型,Hive 将用户的HiveQL 语句通过解释器转换为MapReduce 作业提交到Hadoop 集群上,Hadoop 监控作业执行过程,然后返回作业执行结果给用户。Hive 并非为联机事务处理而设计,Hive 并不提供实时的查询和基于行级的数据更新操作。Hive 的最佳使用场合是大数据集的批处理作业,例如,网络日志分析。

元数据存储

  Hive将元数据存储在RDBMS中,有三种方式可以连接到数据库。

  1. 内嵌模式:元数据保持在内嵌数据库的Derby,一般用于单元测试,只允许一个会话连接。
  2. 多用户模式:在本地安装Mysql,把元数据放到Mysql内。
  3. 远程模式:元数据放置在远程的Mysql数据库。

数据存储

  Hive没有专门的数据存储格式,也没有为数据建立索引,用于可以非常自由的组织Hive中的表,只需要在创建表的时候告诉Hive数据中的列分隔符和行分隔符。

  Hive中所有的数据都存储在HDFS中,Hive中包含4中数据模型:Tabel、ExternalTable、Partition、Bucket。

Table

  类似与传统数据库中的Table,每一个Table在Hive中都有一个相应的目录来存储数据。例如:一个表zz,它在HDFS中的路径为:/wh/zz,其中wh是在hive-site.xml中由${hive.metastore.warehouse.dir}指定的数据仓库的目录,所有的Table数据(不含External Table)都保存在这个目录中。

Partition

  类似于传统数据库中划分列的索引。在Hive中,表中的一个Partition对应于表下的一个目录,所有的Partition数据都存储在对应的目录中。例如:zz表中包含ds和city两个Partition,则对应于ds=20140214,city=beijing的HDFS子目录为:/wh/zz/ds=20140214/city=Beijing。

ExternalTable

  指向已存在HDFS中的数据,可创建Partition。和Table在元数据组织结构相同,在实际存储上有较大差异。Table创建和数据加载过程,可以用统一语句实现,实际数据被转移到数据仓库目录中,之后对数据的访问将会直接在数据仓库的目录中完成。删除表时,表中的数据和元数据都会删除。ExternalTable只有一个过程,因为加载数据和创建表是同时完成。时间数据是存储在Location后面指定的HDFS路径中的,并不会移动到数据仓库中。

Bcuket

  对指定列计算的hash,根据hash值切分数据,目的是为了便于并行,每一个Buckets对应一个文件。将user列分数至32个Bucket上,首先对user列的值计算hash,比如,对应hash=0的HDFS目录为:/wh/zz/ds=20140214/city=Beijing/part-00000;对应hash=20的,目录为:/wh/zz/ds=20140214/city=Beijing/part-00020。

Hive常用优化方法

  1. join连接时的优化:当三个或多个以上的表进行join操作时,如果每个on使用相同的字段连接时只会产生一个mapreduce。
  2. join连接时的优化:当多个表进行查询时,从左到右表的大小顺序应该是从小到大。原因:hive在对每行记录操作时会把其他表先缓存起来,直到扫描最后的表进行计算。
  3. 在where字句中增加分区过滤器。
  4. 当可以使用left semi join 语法时不要使用inner join,前者效率更高。原因:对于左表中指定的一条记录,一旦在右表中找到立即停止扫描。
  5. 如果所有表中有一张表足够小,则可置于内存中,这样在和其他表进行连接的时候就能完成匹配,省略掉reduce过程。设置属性即可实现,set hive.auto.covert.join=true; 用户可以配置希望被优化的小表的大小 set hive.mapjoin.smalltable.size=2500000; 如果需要使用这两个配置可置入$HOME/.hiverc文件中。
  6. 同一种数据的多种处理:从一个数据源产生的多个数据聚合,无需每次聚合都需要重新扫描一次。
    例如:insert overwrite table student select  from employee; insert overwrite table person select from employee;
    可以优化成 from employee insert overwrite table student select insert overwrite table person select
  7. limit调优:limit语句通常是执行整个语句后返回部分结果。set hive.limit.optimize.enable=true;
  8. 开启并发执行。某个job任务中可能包含众多的阶段,其中某些阶段没有依赖关系可以并发执行,开启并发执行后job任务可以更快的完成。设置属性:set hive.exec.parallel=true;
  9. hive提供的严格模式,禁止3种情况下的查询模式。
    • 当表为分区表时,where字句后没有分区字段和限制时,不允许执行。
    • 当使用order by语句时,必须使用limit字段,因为order by 只会产生一个reduce任务。
    • 限制笛卡尔积的查询。
  10. 合理的设置map和reduce数量。
  11. jvm重用。可在hadoop的mapred-site.xml中设置jvm被重用的次数。

安装Hive

  1. 解压Hive。
  2. 将mysql的驱动jar包copy到${HIVE_HOME}/lib目录下。
  3. cp hive-default.xml.template hive-size.xml。
  4. 配置hive-size.xml。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <configuration>
    <!-- 指定数据库URL -->
    <property>
    <name>javax.jdo.option.ConnectionURL</name>
    <value>jdbc:mysql://192.168.145.148:3306/hive?createDatabaseIfNotExist=true</value>
    </property>
    <property>
    <name>javax.jdo.option.ConnectionDriverName</name>
    <value>com.mysql.jdbc.Driver</value>
    </property>
    <property>
    <name>javax.jdo.option.ConnectionUserName</name>
    <value>root</value>
    </property>
    <property>
    <name>javax.jdo.option.ConnectionPassword</name>
    <value>root</value>
    </property>
    </configuration>

Hive QL

文件格式

  Hive创建表可以指定四种文件格式。

  1. 文本格式的数据是Hadoop中经常碰到的。如TextFile 、XML和JSON。 文本格式除了会占用更多磁盘资源外,对它的解析开销一般会比二进制格式高几十倍以上,尤其是XML 和JSON,它们的解析开销比Textfile 还要大,因此强烈不建议在生产系统中使用这些格式进行储存。如果需要输出这些格式,请在客户端做相应的转换操作。 文本格式经常会用于日志收集,数据库导入,Hive默认配置也是使用文本格式,而且常常容易忘了压缩,所以请确保使用了正确的格式。另外文本格式的一个缺点是它不具备类型和模式,比如销售金额、利润这类数值数据或者日期时间类型的数据,如果使用文本格式保存,由于它们本身的字符串类型的长短不一,或者含有负数,导致MR没有办法排序,所以往往需要将它们预处理成含有模式的二进制格式,这又导致了不必要的预处理步骤的开销和储存资源的浪费。
  2. SequenceFile是Hadoop API 提供的一种二进制文件,它将数据以的形式序列化到文件中。这种二进制文件内部使用Hadoop 的标准的Writable 接口实现序列化和反序列化。它与Hadoop API中的MapFile 是互相兼容的。Hive 中的SequenceFile 继承自Hadoop API 的SequenceFile,不过它的key为空,使用value 存放实际的值, 这样是为了避免MR 在运行map 阶段的排序过程。如果你用Java API 编写SequenceFile,并让Hive 读取的话,请确保使用value字段存放数据,否则你需要自定义读取这种SequenceFile 的InputFormat class 和OutputFormat class。
  3. RCFile是Hive推出的一种专门面向列的数据格式。 它遵循“先按列划分,再垂直划分”的设计理念。当查询过程中,针对它并不关心的列时,它会在IO上跳过这些列。需要说明的是,RCFile在map阶段从远端拷贝仍然是拷贝整个数据块,并且拷贝到本地目录后RCFile并不是真正直接跳过不需要的列,并跳到需要读取的列, 而是通过扫描每一个row group的头部定义来实现的,但是在整个HDFS Block 级别的头部并没有定义每个列从哪个row group起始到哪个row group结束。所以在读取所有列的情况下,RCFile的性能反而没有SequenceFile高。
  4. Avro是一种用于支持数据密集型的二进制文件格式。它的文件格式更为紧凑,若要读取大量数据时,Avro能够提供更好的序列化和反序列化性能。并且Avro数据文件天生是带Schema定义的,所以它不需要开发者在API 级别实现自己的Writable对象。
  5. 其他格式:Hadoop实际上支持任意文件格式,只要能够实现对应的RecordWriter和RecordReader即可。其中数据库格式也是会经常储存在Hadoop中,比如Hbase,Mysql,Cassandra,MongoDB。 这些格式一般是为了避免大量的数据移动和快速装载的需求而用的。他们的序列化和反序列化都是由这些数据库格式的客户端完成,并且文件的储存位置和数据布局(Data Layout)不由Hadoop控制,他们的文件切分也不是按HDFS的块大小(blocksize)进行切割。

create table

1
2
3
4
5
6
7
8
create table test_user(id int,name string)
// 注释
comment 'This is the test table'
row format delimited
// 指定切分格式规则
fields terminated by ','
// 指定文件格式
stored as textfile;

insert select

1
2
//使用select语句来批量插入数据
insert overwrite table test_user select * from tab_user;

load data

1
2
3
4
5
//从本地导入数据到hive的表中(实质就是将文件上传到hdfs中hive管理目录下)
load data local inpath '/home/hadoop/test.txt' into table test_user;
//从hdfs上导入数据到hive表中(实质就是将文件从原始目录移动到hive管理的目录下)
load data inpath 'hdfs://ns1/data.log' into table test_user;

external table

1
2
3
4
5
6
7
//LOCATION指定的是hdfs路径
//如果LOCATION路径有数据,则可以直接映射数据建表
CREATE EXTERNAL TABLE test_user_external(id int, name string)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
STORED AS TEXTFILE
LOCATION '/external/user';

CTAS

1
2
3
4
5
6
7
//CTAS是通过查询,然后根据查询的结果来建立表格的一种方式。
//CTAS会根据SELECT语句创建表结构,并把数据一并复制过来。
CREATE TABLE test_user_ctas
AS
SELECT id new_id, name new_name
FROM test_user
SORT BY new_id;

Partition

1
2
3
4
5
6
7
8
9
10
11
12
//创建一个分区表,以year年份作为分区字段
create table test_user_part(id int,name string)
partitioned by (year string)
row format delimited fields terminated by ',';
//将data.log导入到test_user_part表中,并设置分区为1990
load data local inpath '/home/hadoop/data.log' overwrite into table test_user_part
partition(year='1990');
load data local inpath '/home/hadoop/data2.log' overwrite into table test_user_part
partition(year='2000');

Array&&Map

  hive中的列支持使用struct、map和array集合数据类型。大多数关系型数据库中不支持这些集合数据类型,因为它们会破坏标准格式。关系型数据库中为实现集合数据类型是由多个表之间建立合适的外键关联来实现。在大数据系统中,使用集合类型的数据的好处在于提高数据的吞吐量,减少寻址次数来提高查询速度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//array
create table tab_array(a array<int>,b array<string>)
row format delimited
fields terminated by '\t'
collection items terminated by ',';
select a[0] from tab_array;
select * from tab_array where array_contains(b,'word');
insert into table tab_array select array(0),array(name,ip) from tab_ext t;
//map
create table tab_map(name string,info map<string,string>)
row format delimited
fields terminated by '\t'
collection items terminated by ','
map keys terminated by ':';
load data local inpath '/home/hadoop/hivetemp/tab_map.txt' overwrite into table tab_map;
insert into table tab_map select name,map('name',name,'ip',ip) from tab_ext;

UDF

  UDF即用户自定义函数(User Defined Function),Hive支持UDF进行自定义函数的编写。

  需要先使用Java代码开发UDF,然后再把jar包导入到Hive中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class FindRegionByPhone extends UDF {
//使用map模拟数据库
private static HashMap<String,String> dataDictionary = new HashMap<String,String>();
static{
dataDictionary.put("136","beijing");
dataDictionary.put("137","guangzhou");
dataDictionary.put("138","shenzhen");
dataDictionary.put("139","shanghai");
}
public String evaluate(String phone) {
// 如果没有匹配到对应的区域则返回"other"
return areaMap.get(phone.substring(0, 3)) == null ? "other" : areaMap
.get(phone.substring(0, 3));
}
}

添加jar包到Hive

1
2
3
4
5
方式1:添加到hive
hive> add jar /root/MyUDF.jar;
方式2:添加到hdfs,调用时需要指定jar包地址
hdfs -dfs -put MyUDF.jar 'hdfs:///user/hadoop/hiveUDF'

创建临时函数

  临时函数只在当前session中有效,临时函数不能指定库。

1
create temporary function testUDF as 'cn.sylvanas.hive.udf.FindRegionByPhone' using jar 'hdfs:///user/hadoop/hiveUDF/MyUDF.jar'

创建永久函数

格式

CREATE FUNCTION [db_name.]function_name AS class_name[USING JAR|FILE|ARCHIVE ‘file_uri’ [, JAR|FILE|ARCHIVE ‘file_uri’] ];

例如

1
2
3
create function test.testUDF as 'cn.sylvanas.hive.udf.FindRegionByPhone' using jar 'hdfs:///user/hadoop/hiveUDF/MyUDF.jar'
函数需要属于某个库,如这里是’test’,当其他库调用时,需要加上库名,如’test.testUDF’.
分享