一、数据分析引擎
大数据的终极目标,是使用 SQL 语句来处理大数据,这样就能方便不知道怎么编程的数据分析人员进行数据分析。要实现这个目标,就需要大数据分析引擎,常见的有:
- Hadoop 体系的 Hive 和 Pig
- Spark SQL
- Flink Table & SQL
- Presto:支持 SQL,基于内存,通常集成 Presto 和 Hive
- Impala
二、Hive 的作用
hive 是基于 Hadoop 的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种可以存储、查询和分析存储在 Hadoop 中的大规模数据的机制。hive 数据仓库工具能将结构化的数据文件映射为一张数据库表,并提供 SQL 查询功能,能将 SQL 语句转变成 MapReduce 任务来执行。
Hive 的特点:
1)定义了一种类 SQL 语言 HiveQL
2)可以看成是 SQL 到 MapReduce 的映射器
-
3)提供 Hive shell、JDBC/ODBC、Thrift 客户端等接口
在 HDFS 中,hive 的表、分区是一个目录,数据、桶是一个文件。所有的数据都保存在
/user/hive/warehouse
目录下。
三、体系架构
首先,Hive 是基于 HDFS 之上的数据仓库,可以使用 HQL 来很方便地查询大数据,Hive Driver 会将 HQL 翻译转化为 MapReduce 程序(但并不意味着所有的 HQL 都会被转化为 MapReduce)。
Hive 自身有三种方式跟 Hive Driver 交互:
- Hive shell:直接执行 HQL
- Java 程序:通过 JDBC 的通讯标准像执行 DB 操作一样执行 HQL
- HWI:Hive 自带的 Web Console,在 2.2 版本之前自带,但是不太好用,现在推荐使用 HUE
Hive 会将元信息保存在 MySQL 中,元信息包括:表名、列名、分区等。
四、环境搭建
1、安装配置
解压到安装目录
tar -zxvf apache-hive-2.3.0-bin.tar.gz -C ~/training/
配置环境变量(~/.bash_profile
)
HIVE_HOME=/root/training/apache-hive-2.3.0-bin
export HIVE_HOME
PATH=$HIVE_HOME/bin:$PATH
export PATH
重载环境配置
source ~/.bash_profile
2、嵌入模式
嵌入模式,就是数据存储在本地,元信息存储依赖于本地的一个 derby 的微型数据库,适合用来做开发和测试环境。
(1)配置
Hive 的配置文件位于 $HIVE_HOME/conf/hive-site.xml
,嵌入模式需要做以下配置:
javax.jdo.option.ConnectionURL
jdbc:derby:;databaseName=metastore_db;create=true
javax.jdo.option.ConnectionDriverName
org.apache.derby.jdbc.EmbeddedDriver
hive.metastore.local
true
hive.metastore.warehouse.dir
file:///root/training/apache-hive-2.3.0-bin/warehouse
正式使用环境之前,需要对本地 derby 数据库进行初始化:
schematool -dbType derby -initSchema
然后 Hive 会从 $HIVE_HOME/scripts
目录下找到 derby 的初始化脚本执行,并启动本地数据库
除了 derby,还支持 mssql、mysql、oracle、postgres 等数据库存储元信息。
使用了本地文件系统,作为元信息存储仓库目录
(2)测试
完成上述步骤后,就可以使用 hive shell 执行 HQL,其语法跟 SQL 非常类似。
[root@bigdata111 ~]# hive
# 创建一个 student 表
hive> create table student(sid int, sname string);
OK
Time taken: 2.54 seconds
# 查询表结构
hive> desc student;
OK
sid int
sname string
Time taken: 0.098 seconds, Fetched: 2 row(s)
hive> show tables;
OK
student
Time taken: 0.018 seconds, Fetched: 1 row(s)
3、本地模式/远程模式
一般正式生产环境中,会把 hive 的元信息保存在 mysql、oracle 等关系型数据库中,如果数据库跟 hive 在同一台服务器上,称为本地模式,否则称为远程模式,实质并没有差别。
(1)安装 MySQL
这里我们使用 MySQL 来存储元信息,首先安装 MySQL。
yum remove mysql-libs
rpm -ivh mysql-community-common-5.7.19-1.el7.x86_64.rpm
rpm -ivh mysql-community-libs-5.7.19-1.el7.x86_64.rpm
rpm -ivh mysql-community-client-5.7.19-1.el7.x86_64.rpm
rpm -ivh mysql-community-server-5.7.19-1.el7.x86_64.rpm
rpm -ivh mysql-community-devel-5.7.19-1.el7.x86_64.rpm
启动 MySQL
systemctl start mysqld.service
查看数据库用户 root 密码
cat /var/log/mysqld.log | grep password
以 root 用户登录修改密码
alter user 'root'@'localhost' identified by 'Welcome_1';
创建一个 hive 数据库,还有新的用户 hiveowner,专门用来存储和操作 hive 元信息
create database hive;
create user 'hiveowner'@'%' identified by 'Welcome_1';
给新建的用户授权
grant all on hive.* TO 'hiveowner'@'%';
grant all on hive.* TO 'hiveowner'@'localhost' identified by 'Welcome_1';
(2)配置
javax.jdo.option.ConnectionURL
jdbc:mysql://localhost:3306/hive?useSSL=false
javax.jdo.option.ConnectionDriverName
com.mysql.jdbc.Driver
javax.jdo.option.ConnectionUserName
hiveowner
javax.jdo.option.ConnectionPassword
Welcome_1
五、数据模型
1、内部表
与数据库中的 Table 在概念上是类似的;每一个 Table 在 Hive 中都有一个相应的目录存储数据;所有的 Table 数据(不包括外部表)都保存在这个目录中;删除表时,元数据和数据都会删除。
现在有一个员工表的 csv,表中每行数据如下:
员工号、员工姓名、职位、员工上级、出生日期、工资、奖金、所属部门。
empno | ename | job | mgr | hiredate | sal | comm | deptno |
---|---|---|---|---|---|---|---|
7369 | SMITH | CLERK | 7902 | 1980/12/17 | 800 | 0 | 20 |
7499 | ALLEN | SALESMAN | 7698 | 1981/2/20 | 1600 | 300 | 30 |
7521 | WARD | SALESMAN | 7698 | 1981/2/22 | 1250 | 500 | 30 |
7566 | JONES | MANAGER | 7839 | 1981/4/2 | 2975 | 0 | 20 |
7654 | MARTIN | SALESMAN | 7698 | 1981/9/28 | 1250 | 1400 | 30 |
7698 | BLAKE | MANAGER | 7839 | 1981/5/1 | 2850 | 0 | 30 |
7782 | CLARK | MANAGER | 7839 | 1981/6/9 | 2450 | 0 | 10 |
7788 | SCOTT | ANALYST | 7566 | 1987/4/19 | 3000 | 0 | 20 |
7839 | KING | PRESIDENT | -1 | 1981/11/17 | 5000 | 0 | 10 |
7844 | TURNER | SALESMAN | 7698 | 1981/9/8 | 1500 | 0 | 30 |
7876 | ADAMS | CLERK | 7788 | 1987/5/23 | 1100 | 0 | 20 |
7900 | JAMES | CLERK | 7698 | 1981/12/3 | 950 | 0 | 30 |
7902 | FORD | ANALYST | 7566 | 1981/12/3 | 3000 | 0 | 20 |
7934 | MILLER | CLERK | 7782 | 1982/1/23 | 1300 | 0 | 10 |
创建一个 emp 表
hive>create table emp
(empno int,
ename string,
job string,
mgr int,
hiredate string,
sal int,
comm int,
deptno int);
从本地文件系统,把数据加载到 emp 表中
load data local inpath '/root/temp/emp.csv' into table emp;
但是查询数据却显示 NULL
这是因为 hive 加载数据文件时,默认的分隔符是 tab,而 emp.csv 是逗号,因此建表的时候需要指定分隔符为逗号。
create table emp1
(empno int,
ename string,
job string,
mgr int,
hiredate string,
sal int,
comm int,
deptno int)
row format delimited fields terminated by ',';
重载加载查询数据,回显正常
除了可以加载本地文件,还可以直接加载 HDFS 上的文件,但是加载后 HDFS 上的文件会被删除。
load data inpath '/scott/emp.csv' into table emp1;
查看 hdfs,发现 emp 中的数据在 HDFS 上保存是一个 emp.csv 文件,表是目录
2、分区表
分区的核心思想,就是将数据存储文件分成几份,从而当要查询的数据只在某个分区内时,就只读取对应的分区文件,从而提高性能。
创建分区表,根据部门号建立分区
create table emp_part
(empno int,
ename string,
job string,
mgr int,
hiredate string,
sal int,
comm int)
partitioned by (deptno int)
row format delimited fields terminated by ',';
插入数据
insert into table emp_part partition(deptno=10) select empno,ename,job,mgr,hiredate,sal,comm from emp1 where deptno=10;
insert into table emp_part partition(deptno=20) select empno,ename,job,mgr,hiredate,sal,comm from emp1 where deptno=20;
insert into table emp_part partition(deptno=30) select empno,ename,job,mgr,hiredate,sal,comm from emp1 where deptno=30;
每条 insert 语句都会触发一个 MapReduce 作业
但是作业都执行失败了
查看日志,发现有以下错误:
原因是 namenode、datanode 之间时间不同步,同步下时间即可。
分区在 HDFS 也是个目录,每个分区目录下会产生一个文件,可以看到被当成分区键的列的值是不会保存在数据文件中的,原因很简单,同个分区内该列的值相等,避免数据冗余。
可以通过 explain 执行计划,查看分区表对比内部表 emp1 在性能上的提升
不管是数据量,耗时还是扫描数据行,分区表都明显优于不分区的内部表。
3、外部表
在实际生产中,分区表和外部表更常用。通常会使用 sqoop、flume 采集数据到 HDFS 中,如果想建立一个表,直接指向这些数据文件,则可以使用外部表,这里的外部是相对于数据仓库目录而言的,除了元信息之外,外部表不会在数据仓库目录 /user/hive/warehouse 下建立数据文件。
定义一个外部表,首先要指明是一个外部表,还有指向的外部文件的 HDFS 的目录,下面定义一个学生表,指向 HDFS 目录 /students
create external table student_ex
(sid int,sname string,age int)
row format delimited fields terminated by ','
location '/students';
因为 /students 目录下的两个文件一共有3行,所以查询外部表可以查到有3条记录。
如果外部目录中的数据有变化,查询外部表时,也能实时看到这种变化
4、桶表
桶表可以看成是一类特殊的分区,即 hash 分区表。
创建桶表时需要指定根据哪个列分桶,一共有多少个桶,如下创建了一个根据 job 列分桶并具有4个桶的桶表。
create table emp_bucket
(empno int,
ename string,
job string,
mgr int,
hiredate string,
sal int,
comm int,
deptno int)
clustered by (job) into 4 buckets
row format delimited fields terminated by ',';
往桶表中插入数据,转化成 MapReduce 作业。
insert into table emp_bucket select * from emp1;
在数据仓库对应目录下,生成了4个文件,表示4个桶。
5、视图
视图的概念跟关系型数据库中一样,视图是一个虚表,是一个逻辑概念,可以跨越多张表,从而简化一些复杂的查询。视图建立在已有表的基础上,这些表被称为基表。
下面建立一个根据部门表和员工表计算对应关系的视图。
create view view1
as
select dept.dname,emp1.ename
from dept,emp1
where dept.deptno=emp1.deptno;
首先建立一张内部表 dept,从 dept.csv 中加载数据
10 | ACCOUNTING | NEW YORK |
---|---|---|
20 | RESEARCH | DALLAS |
30 | SALES | CHICAGO |
40 | OPERATIONS | BOSTON |
hive> create table dept
> (deptno int,
> dname string,
> city string)
> row format delimited fields terminated by ',';
OK
Time taken: 0.035 seconds
hive> load data inpath '/scott/dept.csv' into table dept;
Loading data to table default.dept
OK
Time taken: 0.298 seconds
hive> select * from dept;
OK
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
Time taken: 0.066 seconds, Fetched: 4 row(s)
再真正建立视图,对视图的查询会触发一个 MapReduce 作业,结果如下:
六、元信息
连接到 hive-site.xml 中 javax.jdo.option.ConnectionURL
参数配置的数据库,可以看到 hive 元信息保存的表。
表非常多,其中有几个比较重要的。
(1)TBLS
这个表中的每一行表示存储在 hive 中创建的一张表,还可以看到表的基本类型。
(2)PARTITIONS
这张表记录了所有分区表的分区,每一行表示一个分区,记录了分区条件,以及关联的分区表等信息。
(3)BUCKETING_COLS
记录桶表的分桶条件。
(4)COLUMNS_V2
记录 hive 表的列,每一行数据表示一个列,通过 CD_ID 属性关联 TBLS 可知是哪张表的列。
七、操作 Hive
1、Hive shell
使用的 HQL 遵循 SQL 标准,目前支持的功能是 SQL 的一个子集。
2、JDBC
建立一个工程,引入 hive jdbc 驱动的 jar 包,hive-jdbc-2.3.0-standalone.jar,位于 $HIVE_HOME/jdbc 目录下,也可以通过 maven 引入。
org.apache.hive
hive-jdbc
2.3.0
先编写一个工具类 JDBCUtils,设置 hive 驱动和连接,代码如下:
/*
* 工具类:
* 1、获取Hive的数据块连接
* 2、释放数据库资源:Connection、Statement、ResultSet
*/
public class JDBCUtils {
// Hive的驱动
private static String driver = "org.apache.hive.jdbc.HiveDriver";
//Hive的URL
private static String url = "jdbc:hive2://192.168.190.111:10000/default";
// 注册驱动
static {
try {
Class.forName(driver);
}catch(Exception ex) {
ex.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() {
try {
return DriverManager.getConnection(url);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
// 释放资源
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
rs = null;
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
st = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
conn = null;
}
}
}
}
写一个 JDBC 查询标准程序:
// 测试使用Java客户端操作Hive进行查询
public class TestJDBC {
public static void main(String[] args) {
String sql = "select * from emp1";
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 获取连接
conn = JDBCUtils.getConnection();
// 创建SQL的执行环境
st = conn.createStatement();
// 执行SQL
rs = st.executeQuery(sql);
while(rs.next()) {
//姓名 薪水
String name = rs.getString("ename");
double sal = rs.getDouble("sal");
System.out.println(name+"\t"+sal);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
// 释放资源
JDBCUtils.release(conn, st, rs);
}
}
}
先启动 hiveserver2,否则执行 JDBC 会报错:
java.sql.SQLException: Could not open client transport with JDBC Uri: jdbc:hive2://192.168.190.111:10000/default: java.net.ConnectException: Connection refused: connect
启动之后,可以看到一个 RunJar 的进程
再次执行,还是报错:
Caused by: java.lang.RuntimeException: java.lang.RuntimeException: org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.authorize.AuthorizationException): User: root is not allowed to impersonate anonymous
需要修改两个 hadoop 配置(core-site.xml)
hadoop.proxyuser.root.hosts
*
hadoop.proxyuser.root.groups
*
重启 hadoop,执行程序,得到正常查询结果
八、自定义函数
引入依赖:
org.apache.hive
hive-common
${hive.version}
org.apache.hive
hive-jdbc
${hive.version}
hive 内置了一些函数比如求最大值 max()、最小值 min()、平均值 avg() 等,也可以编写程序自定义函数,需要继承 org.apache.hadoop.hive.ql.exec.UDF
。
现在编写一个自定义函数实现字符串的拼接,代码如下:
package demo.udf;
import org.apache.hadoop.hive.ql.exec.UDF;
public class MyConcatString extends UDF {
// 执行 select MyConcatString(a,b) from emp1;
// 重写的函数必须叫 evaluate
public String evaluate(String a, String b) {
return a + "********" + b;
}
}
编写完打包,上传到 Hive 服务器上,在 Hive shell 中使用以下命令添加到 Hive 的 classpath 中
add jar myudf.jar;
为自定义函数创建别名
create temporary function myconact as 'demo.udf.MyConcatString';
测试
编写另外一个 UDF,根据员工薪水判断级别
package demo.udf;
import org.apache.hadoop.hive.ql.exec.UDF;
public class CheckSalaryGrade extends UDF {
public String evaluate(String salary) {
int sal = Integer.parseInt(salary);
if(sal < 1000) return "Grade A";
else if(sal >= 1000 && sal < 3000) return "Grade B";
else return "Grade C";
}
}
同样打包加载,创建别名
create temporary function checksal as 'demo.udf.CheckSalaryGrade';
测试结果