一、fetch抓取
Fetch抓取是指,Hive中对某些情况的查询可以不必使用MapReduce计算。例如:SELECT * FROM employees;在这种情况下,Hive可以简单地读取employee对应的存储目录下的文件,然后输出查询结果到控制台。
在hive-default.xml.template文件中hive.fetch.task.conversion默认是more,老版本hive默认是minimal,该属性修改为more以后,在全局查找、字段查找、limit查找等都不走mapreduce。
hive (default)> set hive.fetch.task.conversion=more;
二、本地模式
有时Hive的输入数据量是非常小的。在这种情况下,为查询触发执行任务消耗的时间可能会比实际job的执行时间要多的多。对于大多数这种情况,Hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间可以明显被缩短。即只启动一个map和reduce任务,且在单台主机上执行。相关参数设置如下:
//开启本地mr,自动根据下面的配置决定是否使用本地模式
set hive.exec.mode.local.auto=true;
//设置local mr的最大输入数据量,当输入数据量小于这个值时采用local mr的方式,默认为134217728bytes,即128M
set hive.exec.mode.local.auto.inputbytes.max=50000000;
//设置local mr的最大输入文件个数,当输入文件个数小于这个值时采用local mr的方式,默认为4
set hive.exec.mode.local.auto.input.files.max=10;
三、表的优化
3.1 小表join大表
将key相对分散,并且数据量小的表放在join的左边,这样可以有效减少内存溢出错误发生的几率,因为是将左边的表先读取的;再进一步,可以使用Group变小的维度表(1000条以下的记录条数)先进内存。在map端完成reduce。
实际测试发现:新版的hive已经对小表JOIN大表和大表JOIN小表进行了优化。小表放在左边和右边已经没有明显区别。
3.2 大表join大表
这个实验过程中可以打开hadoop的jobhistory server来查看job的执行情况,包括执行的时间等。
配置 mapred-site.xml
mapreduce.jobhistory.address
bigdata111:10020
mapreduce.jobhistory.webapp.address
bigdata111:19888
启动历史服务器:
mr-jobhistory-daemon.sh start historyserver
进入historyserver 的web页面:
http://192.168.1.102:19888
二、hive调优
图 3.1 hive大表join结果图
可以看到job结果有很多执行结果状态参数,比如执行时间等。
3.2.1 空key过滤
有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,我们需要在SQL语句中进行过滤。比如说key是null,如果是异常数据的话,就应该过滤掉。例如:
insert overwrite table jointable
select n.* from (select * from nullidtable where id is not null ) n left join ori o on n.id = o.id;
这里就事先对 nullidtable 表中 id 为null 的行过滤掉。
但是要注意,确定key是null的数据是无效数据时才过滤,如果是有效数据就不能采用这种方式了
3.2.2空key转换
有时虽然某个key为空对应的数据很多,但是相应的数据不是异常数据,必须要包含在join的结果中,此时我们可以表a中key为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的reducer上。例如:
insert overwrite table jointable
select n.* from nullidtable n full join ori o on
case when n.id is null then concat(‘hive’, rand()) else n.id end = o.id;
使用 case when xxx then value1 else id end 语句判断id是否为空,为空则用随机数替代,否则直接
3.3 开启自动map join
如果不指定MapJoin或者不符合MapJoin的条件,那么Hive解析器会将Join操作转换成Common Join,即:在Reduce阶段完成join。容易发生数据倾斜。可以用MapJoin把小表全部加载到内存在map端进行join,避免reducer处理。
我们可以指定当小表超过多少时采用reduce join,小于就采用map join
(1)设置自动选择Mapjoin
set hive.auto.convert.join = true; 默认为true
(2)大表小表的阈值设置(默认25M一下认为是小表):
set hive.mapjoin.smalltable.filesize=25000000;
3.4 group by自动负载均衡
采取reduce聚合默认情况下,Map阶段同一Key数据分发给一个reduce,当一个key数据过大时就倾斜了。并不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端进行部分聚合,最后在Reduce端得出最终结果。
(1)是否在Map端进行聚合,默认为True
hive.map.aggr = true
(2)在Map端进行聚合操作的条目数目
hive.groupby.mapaggr.checkinterval = 100000
(3)有数据倾斜的时候进行负载均衡(默认是false)
hive.groupby.skewindata = true
当这一项设置为true时,生成的查询计划会有两个MR Job。第一个MR Job中,Map的输出结果会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的Group By Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;第二个MR Job再根据预处理的数据结果按照Group By Key分布到Reduce中(这个过程可以保证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。
3.5 去重统计先group by再count
普通情况选,我们统计去重后的数据行数时,是这样的统计的:
select count(distinct id) from bigtable;
这种方式有一个巨大的缺陷,因为是整体去重的,所以MapReduce时,无法使用多个reducer任务,如果使用了,就变成局部去重,但整体不能保证去重。这样的话一个reducer 的负载其实是很大的,可以采用下面的方式优化:
select count(id) from (select id from bigtable group by id) a;
先启动MapReduce根据id进行group by,这个过程中其实已经去重了,而且group by中是可以用多个reducer任务,这样的就可以减轻单个reducer 的压力。接着再启动另外一个MapReduce,用于count统计group by之后的数据的行数。所以这里是变成两个MapReduce job执行的任务,所以要注意仅当数据量大时采用这种方式,否则多任务的调度反而占用更多资源,并且效率也不好。
3.6 join之前进行行列过滤
列过滤:尽量不使用select * 而是指定要查询的字段
行过滤:在我们进行外部join时,如果某个表要过滤到某些行。要先在join之前进行过滤,不要两表join之后再过滤,因为join之后数据量比原来增大了,过滤要更久。
join之后过滤:
select o.id from bigtable b
join ori o on o.id = b.id
where o.id <= 10;
所以where语句不要放在join之后,这是不好的,大数据量的时候耗时很长
join之前过滤:
select b.id from bigtable b
join (select id from ori where id <= 10 ) o on b.id = o.id;
这里就是先对ori表进行id列的过滤,过滤后的数据再和bigtable表join
3.7 开启动态分区调整
如果hive表是一张分区表,一般情况下,我们进行insert插入时间时,需要明显指定插入到哪个分区中。而如果开启了动态分区,那么就会根据导入数据的分区字段,自动导入到指定分区,如果分区不存在,就自动创建。
(1)开启动态分区功能(默认true,开启)
hive.exec.dynamic.partition=true
(2)设置为非严格模式(动态分区的模式,默认strict,表示必须指定至少一个分区为静态分区,nonstrict模式表示允许所有的分区字段都可以使用动态分区。)
hive.exec.dynamic.partition.mode=nonstrict
(3)在所有执行MR的节点上,最大一共可以创建多少个动态分区。
hive.exec.max.dynamic.partitions=1000
(4)在每个执行MR的节点上,最大可以创建多少个动态分区。该参数需要根据实际的数据来设定。比如:源数据中包含了一年的数据,即day字段有365个值,那么该参数就需要设置成大于365,如果使用默认值100,则会报错。
hive.exec.max.dynamic.partitions.pernode=100
(5)整个MR Job中,最大可以创建多少个HDFS文件。
hive.exec.max.created.files=100000
(6)当有空分区生成时,是否抛出异常。一般不需要设置。
hive.error.on.empty.partition=false
例子:
需求:将ori中的数据按照时间(如:20111230000008),插入到目标表ori_partitioned_target的相应分区中
(1)创建分区表
create table ori_partitioned(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string)
partitioned by (p_time bigint)
row format delimited fields terminated by ‘\t’;
(2)加载数据到分区表中
hive (default)> load data local inpath ‘/opt/module/datas/ds1’ into table ori_partitioned partition(p_time=‘20111230000010’) ;
hive (default)> load data local inpath ‘/opt/module/datas/ds2’ into table ori_partitioned partition(p_time=‘20111230000011’) ;
(3)创建目标分区表
create table ori_partitioned_target(id bigint, time bigint, uid string, keyword string, url_rank int, click_num int, click_url string) PARTITIONED BY (p_time STRING) row format delimited fields terminated by ‘\t’;
(4)设置动态分区
set hive.exec.dynamic.partition = true;
set hive.exec.dynamic.partition.mode = nonstrict;
set hive.exec.max.dynamic.partitions = 1000;
set hive.exec.max.dynamic.partitions.pernode = 100;
set hive.exec.max.created.files = 100000;
set hive.error.on.empty.partition = false;
hive (default)> insert overwrite table ori_partitioned_target partition (p_time)
select id, time, uid, keyword, url_rank, click_num, click_url, p_time from ori_partitioned;
四、数据倾斜
4.1 合理设置map数
4.1.1 大量小文件导致大量map
这个问题在MapReduce中说过了,默认是按每个文件一个整体去切片的,一个文件至少是一个切片,大量小文件时,势必产生很多map任务。这个问题在hive中也是一样的。
解决方案:
在map执行前合并小文件,减少map数:CombineHiveInputFormat具有对小文件进行合并的功能(系统默认的格式)。HiveInputFormat没有对小文件合并功能。
set hive.input.format= org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
4.1.2 单个map工作量过大
当每个map执行都非常缓慢时,可能是因为处理逻辑复杂,这时候可以考虑将切片大小设置的小点,增加map数目,减轻每个map工作量。
增加map的方法为:根据computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M公式,调整maxSize最大值。让maxSize最大值低于blocksize就可以增加map的个数。
设置最大切片值为100个字节
hive (default)> set mapreduce.input.fileinputformat.split.maxsize=100;
这里只是例子,具体设置为多大,根据具体情况决定
4.2 合理设置reduce数
调整方式:
(1)每个Reduce处理的数据量默认是256MB
hive.exec.reducers.bytes.per.reducer=256000000
(2)每个任务最大的reduce数,默认为1009
hive.exec.reducers.max=1009
(3)计算reducer数的公式
N=min(参数2,总输入数据量/参数1)
要注意:
1)过多的启动和初始化reduce也会消耗时间和资源;
2)另外,有多少个reduce,就会有多少个输出文件,如果生成了很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;
在设置reduce个数的时候也需要考虑这两个原则:处理大数据量利用合适的reduce数;使单个reduce任务处理数据量大小要合适;
五、开启并发执行
Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段。或者Hive执行过程中可能需要的其他阶段。默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得整个job的执行时间缩短。不过,如果有更多的阶段可以并行执行,那么job可能就越快完成。
通过设置参数hive.exec.parallel值为true,就可以开启并发执行。不过,在共享集群中,需要注意下,如果job中并行阶段增多,那么集群利用率就会增加。
set hive.exec.parallel=true; //打开任务并行执行
set hive.exec.parallel.thread.number=16; //同一个sql允许最大并行度,默认为8。
六、开启严格模式
Hive提供了一个严格模式,可以防止用户执行那些可能意向不到的不好的影响的查询。通过设置属性hive.mapred.mode值为默认是非严格模式nonstrict 。开启严格模式需要修改hive.mapred.mode值为strict,开启严格模式可以禁止3种类型的查询。
七、开启JVM重用
JVM重用是Hadoop调优参数的内容,其对Hive的性能具有非常大的影响,特别是对于很难避免小文件的场景或task特别多的场景,这类场景大多数执行时间都很短。
Hadoop的默认配置通常是使用派生JVM来执行map和Reduce任务的。这时JVM的启动过程可能会造成相当大的开销,尤其是执行的job包含有成百上千task任务的情况。JVM重用可以使得JVM实例在同一个job中重新使用N次。N的值可以在Hadoop的mapred-site.xml文件中进行配置。通常在10-20之间,具体多少需要根据具体业务场景测试得出。
八、推测执行
关于MapReduce的推测执行见MapReduce部分的推测执行相关的内容,这里不重复。
而hive自己也有提供了配置项来控制reduce-side的推测执行:
九、启用压缩
这个可以看“hive–基本原理”中压缩相关内容。主要就是从减少map和reduce传递的数据量,以及减少reduce输出文件的大小进行优化。
十、查看执行计划
执行sql任务时,可以使用 explain查看执行的预计过程,看看有没有可优化的点。
(1)查看下面这条语句的执行计划
hive (default)> explain select * from emp;
(2)查看详细执行计划
hive (default)> explain extended select * from emp;