Hive是一个基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。在本质上,Hive与Pig一样,都是将相应的查询语句转换为MapReduce程序,简化数据处理过程。不过相比Pig来说,Hive使用的HiveQL语言很接近SQL语言,因此对于开发人员来说更容易上手,使用频率也更高。

一、Hive基础知识

1、基本概念

例如语句:select * from t1,可直接查询HDFS,不会生成MR任务),几乎其他所有的HiveQL查询操作会被转化为优化后的MapReduce程序执行。

这套映射工具称之为metastore,一般存放在derby、mysql中。

数据存储不同。Hive存储基于Hadoop的HDFS,而关系数据库则基于本地操作系统的文件系统;②计算模型不同。Hive基于Hadoop的MapReduce,而关系数据库则基于索引的内存计算模型;③应用场景不同。Hive适用于海量数据查询,实时性很差,而关系数据库使用普通数据查询,实时性较高;④扩展性不同。Hive基于Hadoop之上,很容易通过分布式的节点增加来扩大存储能力和计算能力,而关系数据库水平扩展很难,只能不断增加单机的性能。

2、数据模型

表(Table)、分区(partition)、桶(Bucket)和外部表(External Table)。

表(Table):与关系数据库中的表在概念上是类似的,包含行和列。每一个Hive表都有一个HDFS对应的文件与之对应,表数据就存储在该文件中。

分区(partition):数据表的分区,一个分区对应一个HDFS目录文件。可根据指定列进行分区,例如按天(day)和小时(hour)对大数据量的销售表进行分区,那么不同分区对应的HDFS文件路径分别为.../day={day}/hour={hour}。分区表在查询时可指定查询,而不必全表扫描,大大提高了效率。

桶(Bucket):对指定列计算hash,根据不同hash值切分数据,每个bucket对应一个文件,可以实现并行操作。例如将一个表分为16个bucket,那么存储位置分布范围从.../TableName/part-00000到.../TableName/part-00015。

外部表(External Table):与表结构和用法是一致的,区别在于对应的文件存储位置不同以及表删除操作时对应文件是否会被删除。除了外部表,其余三种模型的表数据都是存储于HDFS的指定Hive存储目录下,该目录由配置文件hive-site.xml中的${hive.metastore.warehouse.dir}指定,默认值/user/hive/warehouse。在其他三种模型的数据加载过程中,对应的数据会被同时移动到上述Hive存储目录中,同时在删除记录或销毁表时,对应的数据也会被删除。但是外部表对应的HDFS文件可以不受Hive的控制,也即是外部表可以直接加载已经存在的HDFS文件,而不用移动到指定存储目录中,同时在删除外部表后,对应的数据也不会被删除。

外部分区表,由于外部分区表元数据不存在分区信息,因此创建表后需要手动添加分区信息,才可以查询到数据。

因此直接操作hdfs文件内容,等同于通过HiveQL语句操作记录;

3、元数据表

   配置mysql保存元数据表后,可以在hive库中看到许多元数据表,如DBS、TBS、SDS等。重要元数据表说明如下:

   ▶ DBS:记录了hive表存储于HDFS中的位置;

   ▶ TBS:记录了表的名称、表所属的数据库id,以及表的类型(外部表EXTERNAL_TABLE,其他MANAGED_TABLE);

   ▶ SDS:记录了表对应于HDFS的路径,可根据TBS中SD_ID字段关联;

   ▶ COLUMNS_V2:记录表字段信息,与SDS表中的CD_ID字段关联;

当创建一个外部分区表后,需要手动添加分区,否则查询不到数据;

二、Hive的安装、MySQL集成配置与启动

1、安装步骤

安装时不必考虑集群模式或伪分布模式。本实例安装版本:apache-hive-1.2.0-bin.tar.gz

/usr/local/hive/bin/hive

2、MySQL集成元数据配置

Hive中的元数据包括表的名字、表的列、表的分区、表分区的属性、表的属性(是否为外部表等)、表的数据所在目录等。

独占式的,也就是说同时只能允许一个会话连接,多个会话无法同时共享数据。而且,Derby数据文件产生的位置由当前运行Hive命令所在的目录决定,因此使用默认的Derby保存元数据很不方便,只适合简单的测试。

默认的Derby数据库存储位于为当前运行hive命令所在的目录(pwd),并且每次切换目录后运行hive都会在当前目录下生成元数据目录。如在/root中通过命令:/usr/local/hive/bin/hive运行hive客户端,则会在/root下生成metastore_db元数据存储文件目录,然后切换目录到/usr下,仍然通过命令:/usr/local/hive/bin/hive运行hive客户端,则还会在/usr下生成一个metastore_db目录。并且对于每一个metastore_db目录,都只能同时允许一个客户端使用,多个ssh客户端切换到同一目录,然后运行hive,则第二个客户端就无法启动。对于这种设计,也是醉了~

Hive的元数据存储数据库metastore目前只支持mysql和derby。Hive中的元数据包括表的名字、表的列和分区及其属性、表的属性(是否为外部表等)、表的数据所在目录等。

通常在安装Hive后,为支持多个会话连接,都会将元数据配置保存于MySQL中,需要做如下配置:

   1) 在Linux上正确安装MySQL(官方下载:http://ftp.kaist.ac.kr/mysql/Downloads/),本实验安装版本为:MySQL-server-5.1.73-1.glibc23.x86_64.rpmMySQL-client-5.1.73-1.glibc23.x86_64.rpm

/usr/bin/mysql_secure_installation命令对mysql进行初始化,设置删除匿名用户、允许远程连接等;

   3) 将MySQL连接驱动文件mysql-connector-java-xxx.jar(本实验:mysql-connector-java-5.1.34-bin.jar)拷贝至Hive的lib目录下;

   4) 修改Hive的hive-site.xml配置文件(如果hive-site.xml不存在,将hive-default.xml.template重命名),修改内容如下所示:

Hadoop和Hive查询 hadoop内的hive_Hive

Hadoop和Hive查询 hadoop内的hive_数据_02

<property>
  <name>javax.jdo.option.ConnectionURL</name>
  <value>jdbc:mysql://localhost:3306/hive?createDatabaseIfNotExist=true</value>
  <description>JDBC connect string for a JDBC metastore</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionDriverName</name>
  <value>com.mysql.jdbc.Driver</value>
  <description>Driver class name for a JDBC metastore</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionUserName</name>
  <value>root</value>
  <description>username to use against metastore database</description>
</property>
<property>
  <name>javax.jdo.option.ConnectionPassword</name>
  <value>123456</value>
  <description>password to use against metastore database</description>
</property>

View Code

hive --service metastore &,否则会抛出异常:Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient;

   配置完成后,即可通过可执行文件/usr/local/hive/bin/hive启动hive客户端。

补充:

grant all on *.* to 'root'@'%' identified by '123456'; ② flush privileges;

mysqladmin -u USER -p old_pwd PASSWORD new_pwd;

3、启动Hive

$Hive_Home/bin/hive 或 $Hive_Home/bin/hive --service cli;

hive --service hwi &,然后可以通过浏览器访问hive,地址:http://localhost:9999/hwi/;

hive --service hiveserver >/dev/null  2>/dev/null &;

4、启动hive客户端常见问题

Relative path in absolute URI: ${system:java.io.tmpdir%7D/$%7Bsystem:%7D

       解决:在hive目录下新建io-tmpdir目录,然后将hive/conf/hive-site.xml中含有system:java.io.tmpdir配置项的value值替换成/usr/local/hive/io-tmpdir

java.lang.IncompatibleClassChangeError: Found class jline.Terminal, but interface was expected

       解决:因为在hadoop目录下存在老版本jline,需要将hive的新版本jline拷贝至hadoop中,执行命令:cp /usr/local/hive/lib/jline-2.12.jar /usr/local/hadoop/share/hadoop/yarn/lib/,最好将hadoop目录下的老版本jline删除

Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient

hive --service metastore &

   其他问题参考:

三、HiveQL语言介绍

   HiveQL是一种很类似SQL的语言,与大部分的SQL语法兼容,但并不完全支持SQL标准。由于底层依赖于Hadoop,HiveQL不支持更新操作,也不支持索引和事务,子查询和join操作也有一定的局限性。

为了便于操作,可以在Hive客户端中运行hdfs命令,运行如命令:dfs -ls /;

1、数据类型

tinyint、smallint、int、bigint、float、double、boolean和string,复杂类型有:array、map和strut。

   其中tinyint、smallint、int和bigint长度字节数分别为1、2、4和8,float长度为4字节,double长度为8字节,array为有序字段,map为无序字段,struct为一组命名的字段。

2、常用HiveQL操作语句

   1) 创建内部表

create table t1(id int, name string) row format delimited fields terminated by '\t'

create table t1 as   --根据已创建的表t2创建表t1,同时复制表结构和数据

create table t1 like   --同上,但只复制表结构,不复制表数据

   2) 创建分区表

create table t1(id int, name string) partitioned by(day string) row format delimited fields terminated by '\t'

load data local inpath '/root/Downloads/t1.txt' into table t1 partition

show partitions t1

select * from t1 where day='20141221'

alter table t1 drop partition (day='20141221')

   3) 创建桶表(使用场景不多)

create table t1(id int, name string) clustered by(id) into 5 buckets row format delimited fields terminate by '\t'

insert into table t1 select id, name from t2   --给桶表插入数据,注意,桶表不能通过load data方式插入数据

   4) 创建外部表

create externalrow format delimited fields terminated by '\t' location '/data/t1'  --创建外部表t1,并与已存在的HDFS目录/data/t1(t1是一个目录,不是文件)做关联,分隔符为\t,注意此语句location必须放在最后

create external table t1(id int, name string) partitioned by(day string) row format delimited fields terminated by '\t' location '/data/t1'   --创建外部分区表,由于表创建后不存在分区信息,需要手动添加分区信息,否则查询不到数据

alter table t1 add partition(day='20150521')

insert overwrite table t1 select id, name from t2

   5) 导入数据

load data local inpath '/root/Downloads/t1.txt' [overwrite] into table t1   --从linux本地系统文件中导入数据到表t1中,overwrite表示覆盖,into表示插入

hadoop fs -put /root/Downloads/t1.txt '/user/hive/warehouse/t1'   --也可以通过HDFS命令直接将文件导入到对应的Hive文件中

   6) 修改表

alter table t1 add columns (age int)

alter table t1 rename to t2

   7) 连接查询

select t1.*, t2.* from t1 join t2(=)   --内连接(等值连接)查询,=为连接查询条件

select t1.*, t2.* from t1 left outer join t2(=)   --左连接查询,效果与SQL的左连接基本一致,此处以t1表作为依据,返回所有行,无法匹配到t2的行的时候,t2行对应的数据为空值

select t1.*, t2.* from t1 right outer join t2(=)   --右连接查询,与左连接的区别为,此处已t2表作为依据

select t1.*, t2.* from t1 full outer join t2(=)   --全连接查询,返回左表和右表中的所有行。当某行在另一表中没有匹配行时,则另一个表的选择列表包含空值。如果表之间有匹配行,则整个结果集包含基表的数据值。

select t1.* from t1 left semi join t2(=)   --半连接查询,是Hive所特有的查询语句,Hive 不支持 IN 操作,但是拥有替代的方案; left semi join, 称为半连接, 需要注意的是连接的表不能在查询的列中,只能出现在 on 子句中。 

   8) 子查询(Hive对于子查询的支持很有限,只能在from引导的字句中出现子查询)  

select tearher, MAX(class_num) from (select teacher, count(classname) as class_num from classinfo group by teacher) sub_t group by teacher

   9) 视图操作(Hive版本>=0.6才支持视图操作) 

create view view_teacher_classnum as select teacher, count(classname) from classinfo group by teacher

   10) 其他常用命令

show create table users

hive -e 'show tables;'

show tables

show functions

source file <filepath>

3、使用Hive处理手机流量数据统计分析实例(实例见文章《Hadoop学习(4)-- MapReduce》)

hive> create table wlan(reportTime string,msisdn string,host string,upPackNum double,downPackNum double,upPayLoad double,downPayLoad double) row format delimited fields terminated by '\t';;
hive> load data local inpath '/root/Downloads/upload.txt' into table wlan;    --上传upload.txt数据到hive表wlan,upload.txt内数据用\t间隔
hive> select msisdn,sum(upPackNum),sum(downPackNum),sum(upPayLoad),sum(downPayLoad) from wlan group by msisdn;   --通过此HiveQL语句统计每个手机的上传下载等数据包量

   使用Hive三个语句就轻松解决了此实例,足见Hive应用是很方便的。

四、Hive自定义函数UDF、UDAF和UDTF

  Hive提供了许多内置函数,但有些情况下可能不能够满足特殊的业务需求,因此,Hive支持几种类型的自定义函数的实现。

1、UDF(User Defined Function)

一进一出”,即输入一行数据,输出也是一行数据,用于select查询的格式化处理。

   只需要在自定义类中重构UDF类的evaluate函数即可。Hive UDF允许输入多个参数,但只能有一个返回值(需要返回多个值可以返回对象,重写ToString方法)。实现步骤如下:

   1) 在eclipse项目中导入相关hive的jar包,编写自定义类,继承org.apache.hadoop.hive.ql.exec.UDF并实现evaluate方法,如:

Hadoop和Hive查询 hadoop内的hive_Hive

Hadoop和Hive查询 hadoop内的hive_数据_02

package com.hicoor.hadoop.hive;

import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;

public class UdfDemo extends UDF {
    public Text evaluate(Text name, LongWritable age) {
        try {
            return new Text("name=" + name.toString() + ",age=" + age.get());
        } catch (Exception e) {
            return null;
        }
    }
}

View Code

   2) 将上述自定义类导出jar包,上传到linux磁盘中,在hive命令行中执行命令:

add jar /Downloads/UdfDemo.jar;   --将自定义jar添加到hive的classpath中

create temporary function myfunc as 'com.hicoor.hadoop.hive.UdfDemo';   --创建临时函数

select myfunc(age, name) from users;   --调用自定义函数

drop temporary function myfunc;   --删除自定义函数

   需要注意的是,每次进入hive命令行都需要执行添加jar和临时函数操作。

2、UDAF(User Defined Aggregation Function)

多进一出”,即输入多行数据,输出为一行数据,多为聚合函数,如sum()等。

3、UDTF(User Defined Table-Generating Function)

一进多出”,即输入一行数据,输出多行数据,也用于格式化数据。

   参考:http://url2.biz/bskk

五、Java客户端调用Hive

1、使用Java客户端调用Hive接口前,必须开启Hive服务,在服务器上执行命令:hive --service hiveserver >/dev/null  2>/dev/null &

2、导入Hive和Hadoop的相关jar包,然后可通过jdbc查询数据,参考代码(完整项目):

Hadoop和Hive查询 hadoop内的hive_Hive

Hadoop和Hive查询 hadoop内的hive_数据_02

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class TestHiveConnection {

    public static void main(String[] args) throws Exception {
        Class.forName("org.apache.hadoop.hive.jdbc.HiveDriver");
        //default为连接数据库 后面为用户名与密码 此处为空
        Connection con = DriverManager.getConnection("jdbc:hive://192.168.137.101:10000/default", "", "");
        Statement stmt = con.createStatement();
        String querySQL="SELECT * FROM wlan";
        ResultSet res = stmt.executeQuery(querySQL);  
        while (res.next()) {
            System.out.println(res.getString("msisdn"));
        }
    }

}

View Code