【Hive】Hive的企业级优化及实战案例

1、优化一:任务的执行模式

为什么有的sql执行mapreduce,而有的却不?


	hive.fetch.task.conversion
	minimal
	
		Some select queries can be converted to single
		FETCH task minimizing latency.
		Currently the query should be single sourced
		no having any subquery and should not have any
		aggregations or distincts (which incurs RS),
		lateral views and joins.
		1. minimal: SELECT STAR, FILTER on partition columns, LIMIT only
		2. more: SELECT, FILTER, LIMIT only (TABLESAMPLE, virtual columns)
	

2、优化二:解释执行计划

语法格式:EXPLAIN [EXTENDED|DEPENDENCY] query
语句示例:explain select deptno, avg(sal)avg_sal from emp group by deptno ;

EXPLAIN select * from emp ;
EXPLAIN select deptno,avg(sal) avg_sal from emp group by deptno ;
EXPLAIN EXTENDED select deptno,avg(sal) avg_sal from emp group by deptno ;

3、优化三:大表拆分成子表

可以将外部表、分区表、临时表结合使用,以及设置多级分区,分区表可以使得检索更快速,外部表保证源数据的安全性,临时表和拆分子表可以简化复杂的SQL编写以及问题需求。SQL语句的优化可以从join和过滤两方面进行深入优化。数据的存储格式设置:textfile、orcfile、parquet。数据压缩设置:snappy。

CREATE [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name
[AS select_statement]; 


CREATE EXTERNAL TABLE [IF NOT EXISTS] [db_name.]table_name    
[(col_name data_type [COMMENT col_comment], ...)]
[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
[ROW FORMAT row_format] 

set parquet.compression=SNAPPY ;
create table page_views_par_snappy
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS parquet
AS select * from page_views ;

dfs -du -h /user/hive/warehouse/page_views_par_snappy/ ;

4、优化四:属性设置

属性名称 属性 属性值
并行执行的线程个数 hive.exec.parallel.thread.number 15
是否并行执行 hive.exec.parallel true
JVM重用 mapreduce.job.jvm.numtasks 2
Reduce数目 mapreduce.job.reduces 3
推测执行 mapreduce.map.speculative false
  hive.mapred.reduce.tasks.speculative.execution false
  mapreduce.reduce.speculative false
Map数目 hive.merge.size.per.task 256000000

(1)map和reduce的个数优化:
在Hadoop中,一个分片就是一个块,一个块对应一个maptask。Hadoop源码中有这样一个计算公式:min(max_split_size,max(min_split_size,block_size))
其中,min_split_size(最小分片大小)默认值为0,所以max函数取的是block_size,block_size默认是128。max_split_size(最大分片大小)默认值为256。
这个公式决定了map的个数。我们不能直接去修改HDFS的block_size,因为一般在实际的生产环境中,HDFS一旦format格式化之后,block_size的大小就不会去修改的,那么我们就可以通过修改max_split_size和min_split_size来影响map的个数。
(2)推测执行
当某个任务出现迟迟不结束的情况,那么会考虑开启推测执行,开启一个一模一样的任务,两个任务谁先完成,就会关闭另一个。
推测执行分为map端的推测(mapreduce.map.speculative属性)和reduce端的推测(mapreduce.reduce.speculative属性)。
推测执行不好的地方:额外消耗节点资源,并且可能会出现重复写入的情况,产生异常。

5、优化五:动态分区调整

动态分区属性:设置为true,表示开启动态分区功能(默认为false)。
hive.exec.dynamic.partition=true;
分区模式:设置为nonstrict,表示允许所有分区都是动态的(默认为strict)。设置为strict,表示必须保证至少有一个分区是静态的。
hive.exec.dynamic.partition.mode=strict;
每个mapper或reducer可以创建的最大动态分区个数。
hive.exec.max.dynamic.partitions.pernode=100;
一个动态分区创建语句可以创建的最大动态分区个数。
hive.exec.max.dynamic.partitions=1000;
全局可以创建的最大文件个数。
hive.exec.max.created.files=100000;

6、优化六:Strict Mode 严格模式

对分区表进行查询,在where子句中没有加分区过滤的话,将禁止提交任务(默认nonstrict)。
set hive.mapred.mode=strict;
注意:使用严格模式可以禁止3种类型的查询:
(1)对于分区表,不加分区字段过滤条件,不能执行;
(2)对于order by语句,必须使用limit语句;
(3)限制笛卡儿积的查询(join的时候不使用on,而使用where的)。

7、优化七:数据倾斜

Hive出现数据倾斜的原因:MapReduce由于某个key值分布过多,导致某个reduce的运行速度严重影响了整个job的运行。
Hive数据倾斜的解决办法:
(1)分区阶段自定义分区规则,继承partitioner类。
(2)分区阶段将key加入随机数,加入了随机数的key分配到不同的reduce。
(3)Hive Join

select e.a, e.b ,d.h,d.f from 
(select .... from emp where emp.filter) e 
join 
(select .... from dept where emdeptp.filter)  d 
on(e.deptno = d.deptno);

1)Map Join:
连接发生的阶段:Map阶段,小表对大表,大表的数据放从文件中读取,小表的数据内存中,使用DistributedCache。

set hive.auto.convert.join=true;

select count(*) from store_sales join time_dim on (ss_sold_time_sk=t_time_sk);

2)Common/Shuffle/Reduce Join:
连接发生的阶段:Reduce阶段,大表对大表,每个表的数据都是从文件中读取的。

3)SMB Join:Sort-Merge-Bucket Join。
场景:适合数据抽样统计。

set hive.auto.convert.sortmerge.join = true;
set hive.optimize.bucketmapjoin = true;
set hive.optimize.bucketmapjoin.sortedmerge = true;

SMB Join只有在建好的桶表才可以进行,在建表的时候需要先分桶。SMB Join类似分区,在分区与分区之间进行join。两表之间的桶的个数一定要是相同的,或者B表是A表的桶的个数的倍数。
SMB Join示例:

A表:1000万数据,分3个桶
0000-0300	1桶
0301-0600	2桶
0601-1000	3桶
			
B表:1000万数据,分3个桶
0000-0300	1桶
0301-0600	2桶
0601-1000	3桶			

A表:1000万数据,分3个桶
0000-0300	1桶
0301-0600	2桶
0601-1000	3桶
			
B表:2000万数据,分6个桶
0000-0300	1桶
0301-0600	2桶
0601-1000	3桶				
1001-1300	4桶	
1301-1600	5桶
1601-2000	6桶

8、优化八:Hive本地模式

本地模式描述:当在一个完全分布式的Hadoop集群中的一台机器上运行Hive服务端时,Hive客户端提交的hive命令及其生成的MapReduce任务只会提交给该Hive服务端,而不会分配给其他Hadoop集群中的节点。
业务场景:处理小数据集的时候,速度会更快一些,因为不存在集群中的节点互相通信配合的开销。
配置属性:hive.exec.mode.local.auto=true。
本地模式的限制:
1)数据的输入大小不能超过128MB。
2)map的个数不能超过4个。
3)reduce的个数不能超过1个。

9、实战案例

思路:
(1)创建原表;
(2)针对不同的业务创建不同的子表;
(3)数据存储格式:orcfile/parquet;
(4)数据压缩:snappy;
(5)map output 使用snappy进行数据压缩;
(6)创建外部表和分区表。

(1)创建原表

【问题】字段中如果包含了分隔符该如何处理?
【原因】字段加载不全的情况出现,因为hive指定分隔符的实现,每读到一个分隔符就将前面的部分信息作为一个字段
【解决】使用正则表达式的方式去进行匹配

drop table if exists default.bf_log_src ;
create table IF NOT EXISTS default.bf_log_src (
remote_addr string,
remote_user string,
time_local string,
request string,
status string,
body_bytes_sent string,
request_body string,
http_referer string,
http_user_agent string,
http_x_forwarded_for string,
host string
)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
  "input.regex" = "(\"[^ ]*\") (\"-|[^ ]*\") (\"[^\]]*\") (\"[^\"]*\") (\"[0-9]*\") (\"[0-9]*\") (-|[^ ]*) (\"[^ ]*\") (\"[^\"]*\") (-|[^ ]*) (\"[^ ]*\")"
)
STORED AS TEXTFILE;

load data local inpath '/opt/datas/moodle.ibeifeng.access.log' into table default.bf_log_src ;

(2)创建子表

drop table if exists default.bf_log_comm ;
create table IF NOT EXISTS default.bf_log_comm (
remote_addr string,
time_local string,
request string,
http_referer string
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS orc tblproperties ("orc.compress"="SNAPPY");

insert into table default.bf_log_comm select remote_addr, time_local, request,http_referer from  default.bf_log_src ;

select * from bf_log_comm limit 5 ;

(3)定义UDF,对原表数据进行清洗。第一个udf:去除引号。
1)UDF内容

package com.beifeng.senior.hive.udf;

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

/**
 * 1. Implement one or more methods named
 * "evaluate" which will be called by Hive.
 * 
 * 2. "evaluate" should never be a void method. 
 * However it can return "null" if needed.
 * @author XuanYu
 *
 */
public class RemoveQuotesUDF extends UDF {
	
	public Text evaluate(Text str){
		// validate 
		if(null == str){
			return null ;
		}
		
		if(null == str.toString()){
			return null ;
		}
		
		// remove
		return new Text (str.toString().replaceAll("\"", ""))  ;
	}
	
	public static void main(String[] args) {
		System.out.println(new RemoveQuotesUDF().evaluate(new Text("\"31/Aug/2015:00:04:37 +0800\"")));
	}

}

2)UDF使用

add jar /opt/datas/hiveudf2.jar ;
create temporary function my_removequotes as "com.beifeng.senior.hive.udf.RemoveQuotesUDF" ;

insert overwrite table default.bf_log_comm select my_removequotes(remote_addr), my_removequotes(time_local), my_removequotes(request), my_removequotes(http_referer) from  default.bf_log_src ;

select * from bf_log_comm limit 5 ;

(4)第二个 UDF:处理日期时间字段。
31/Aug/2015:00:04:37 +0800格式变为20150831000437格式。
1)UDF内容

package com.beifeng.senior.hive.udf;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;

/**
 * 1. Implement one or more methods named "evaluate" which will be called by
 * Hive.
 * 
 * 2. "evaluate" should never be a void method. However it can return "null" if
 * needed.
 * 
 * @author XuanYu
 *
 */
public class DateTransformUDF extends UDF {

	private final SimpleDateFormat inputFormat = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.ENGLISH);
	private final SimpleDateFormat outputFormat = new SimpleDateFormat("yyyyMMddHHmmss");
	/**
	 * 31/Aug/2015:00:04:37 +0800
	 * 
	 * 20150831000437
	 * 
	 * @param str
	 * @return
	 */
	public Text evaluate(Text input) {
		Text output = new Text();
		
		// validate
		if (null == input) {
			return null;
		}
		
		String inputDate = input.toString().trim();
		if(null == inputDate){
			return null ;
		}
		
		try{
			
			// parse
			Date parseDate = inputFormat.parse(inputDate);
			
			//transform
			String outputDate = outputFormat.format(parseDate);
			
			// set
			output.set(outputDate);
			
		}catch(Exception e){
			e.printStackTrace();
			return output ;
		}
		
		return output;
	}

	public static void main(String[] args) {
		System.out.println(new DateTransformUDF().evaluate(new Text("31/Aug/2015:00:04:37 +0800")));
	}

}

2)UDF使用

add jar /opt/datas/hiveudf3.jar ;
create temporary function my_datetransform as "com.beifeng.senior.hive.udf.DateTransformUDF" ;

insert overwrite table default.bf_log_comm select my_removequotes(remote_addr), my_datetransform(my_removequotes(time_local)), my_removequotes(request), my_removequotes(http_referer) from  default.bf_log_src ;

select * from bf_log_comm limit 5 ;

(5)可以将日期处理成其他格式
原始格式:31/Aug/2015:00:04:37 +0800
期望格式:2015-08-31 00:04:37
1)UDF内容

package com.bigdata.mapreduce;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;

public class TestDateUdf extends UDF {

	/**
	 * 日期格式转换 当前日期格式:31/Aug/2015:00:04:37 +0800 目标日期格式:2015-08-31 00:04:37
	 * 
	 * @param time
	 * @return
	 */
	// 注意建议使用public,因为要将程序打jar包进行调用的
	public Text evaluate(Text time) {

		String output = null;

		// 定义输入的日期格式,第一个参数表示传进来的日期格式是什么类型的
		SimpleDateFormat inputDate = new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss", Locale.ENGLISH);
		// 定义输出的日期:2015-08-31 00:04:37
		SimpleDateFormat outputDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

		// 首先判断列中是不是有null,如果有直接返回null
		if (time == null) {
			return null;
		}

		// 判断字符串中有没有值
		if (StringUtils.isBlank(time.toString())) {
			return null;
		}

		// 去除字段中的双引号,否则无法解析,双引号需要进行转义
		String parser = time.toString().replaceAll("\"", "");

		// 将传递过来的time进行识别解析,将读入进来的信息进行保存,保存到一个变量中,返回类型是date类型
		try {
			Date parseDate = inputDate.parse(parser);
			// 使用outputDate来将date类型进行转换
			output = outputDate.format(parseDate);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new Text(output);
	}

	public static void main(String[] args) {
		System.out.println(new TestDateUdf().evaluate(new Text("31/Aug/2015:00:04:37 +0800")));
	}
}

2)UDF使用

add jar /opt/datas/bigdatadateudf.jar;
create temporary function pdate as 'com.bigdata.mapreduce.TestDateUdf';

select pdate(time_local) from default.bf_log_src limit 10;

hive中另一种关联jar包的方式,如果jar包是在HDFS上的情况:
create temporary function pdate as ‘com.bigdata.mapreduce.TestDateUdf’ using jar
‘hdfs://bigdata-senior01.ibeifeng.com:8020/bigdatadateudf.jar’
hive中生成永久生效function的方式:
(1)将hive的jar添加到 hive环境变量hive-env.sh中
(2)编译hive源码
可以使用HDFS的jar方式来当做永久的方式使用

(6)从日期格式中截取小时字段。
desc function extended substring ;
substring(‘Facebook’, 5, 1),得到’b’,即substring下标从1开始计数。

select substring('20150831230437',9,2) hour from bf_log_comm limit 1 ;

select t.hour, count(*) cnt from
(select substring(time_local,9,2) hour from bf_log_comm ) t
group by t.hour order by  cnt desc ;

(7)从IP地址中截取前缀。

select t.prex_ip, count(*) cnt from
(select substring(remote_addr,1,7) prex_ip from bf_log_comm) t
group by t.prex_ip order by  cnt desc limit 20 ;

(8)使用python脚本统计数据星期几的记录最多。
Python脚本:

import sys
import datetime

for line in sys.stdin:
  line = line.strip()
  userid, movieid, rating, unixtime = line.split('\t')
  weekday = datetime.datetime.fromtimestamp(float(unixtime)).isoweekday()
  print '\t'.join([userid, movieid, rating, str(weekday)])

注:.strip()移除左右空格,默认就是空格;.split(’\t’)对于字符串进行分割、解包;.isoweekday()最终返回一个星期几的值,如果是周一,那么返回1,以此类推。

创建原表:

CREATE TABLE u_data (
userid INT,
movieid INT,
rating INT,
unixtime STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;

wget命令:从指定的URL地址去下载文件(比较稳定,对于网络环境要求不是很高)
在Linux上下载数据包:$ wget http://files.grouplens.org/datasets/movielens/ml-100k.zip
使用unzip解压数据包:$ unzip ml-100k.zip
加载数据到u_data表中:

LOAD DATA LOCAL INPATH '/opt/datas/ml-100k/u.data' OVERWRITE INTO TABLE u_data;

查看u_data表中的数据:SELECT COUNT(*) FROM u_data;或者count(1)
创建结果集表:

CREATE TABLE u_data_new (
  userid INT,
  movieid INT,
  rating INT,
  weekday INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';

加载python脚本:

add FILE /opt/datas/ml-100k/weekday_mapper.py;

将weekday_mapper.py脚本文件与hive进行关联,add FILE和add jar功能类似,都是将文件或者jar包添加到hive中。
将原表中数据经过python脚本处理后,插入新表:

INSERT OVERWRITE TABLE u_data_new
SELECT
  TRANSFORM (userid, movieid, rating, unixtime)  -- input from source table
  USING 'python weekday_mapper.py'  -- script 
  AS (userid, movieid, rating, weekday) --output from python
FROM u_data;

其中,TRANSFORM (userid, movieid, rating, unixtime)将字段(userid, movieid, rating, unixtime)传递给USING ‘python weekday_mapper.py’。

查询新表中的数据:

SELECT weekday, COUNT(*) FROM u_data_new GROUP BY weekday;
SELECT weekday, COUNT(1) cnt FROM u_data_new GROUP BY weekday order by cnt desc;

我们还可以:
1)画出热点图:根据用户点击行为,在页面上某一部分点击的次数很多,在这一块的颜色区域颜色就很深,实现一个热点分布,而最终的目的就是为了提升网站用户体验度。
2)链入地址和链出地址:可以对这些信息进行流量的走向分析。
3)对于客户端进行过滤,得到访问网站的浏览器的名称及版本。

你可能感兴趣的:(BigData,云计算大数据学习分享与沉淀)