此处省略150字…
在大表Join小表时,解决数据倾斜最好的方式是使用Map Join,避免Shuffle,从而也避免了数据倾斜. map join主要通过下面的参数来调节:
#默认是true
set hive.auto.convert.join=true --开启map join //1.x版本及以后默认是开启的
#设置小表的大小,生产环境可以适当调大
set hive.mapjoin.smalltable.filesize=25000000 --默认小表小于25mb
在map join中,还有如下2个参数经常使用:
set hive.auto.convert.join.noconditionaltask=true;
set hive.auto.convert.join.noconditionaltask.size=60000000;
说明:
上面的Map Join解决了小表关联的问题,假如关联的是2个大表就不太适用了.小表不小,所以不太适合加载进内存,那么Map Join肯定是不适合的;两个表都不小,所以必然带来大量的网络IO和磁盘IO,所以Reduce Join也不适合.这个时候就可以使用SMB Join,并带来性能很大的提升.
使用SMB Join的前提: 1.创建表时必须是创建分桶表;2.创建表时必须指定sort by排序;3.分桶的字段与sort by排序的字段且和两表关联的字段必须是一样的.
set hive.enforce.bucketing=true; --默认为false,开启后,则写入数据时会自动分桶
set hive.enforce.sorting=true; --默认为false,开启后,插数据到表中会进行强制排序
#如果希望SMB Join能够转换为SMB Map Join,还需要设置以下参数
set hive.auto.convert.sortmerge.join=true;
set hive.optimize.bucketmapjoin = true;
set hive.optimize.bucketmapjoin.sortedmerge =true
原理说明:
在map join的时候,两个表是以相同的方式来划分桶的,则处理左边表的某个桶的时候,Mapper是知道表内对应的记录也在右边表的相同的桶内.因此Mapper只需要获取对应的那个桶,然后进行连接就可以.桶中的数据是根据相同的字段进行排序,这样每个桶的join就变成了merge sort,可以进一步提升map join效率.
#默认为fasle,这样就会开始map端聚合combiner.可以减少shuffle的数据量.
set hive.map.aggr=true;
#默认为fasle,应设置为true
set hive.groupby.skewindata=true;
#设置group by后面的键的记录数超过该值就会优化
set hive.groupby.mapaggr.checkinterval=100000;
#预先取100000条数据聚合,如果聚合后的条数/1000000 > 0.5,则不再预聚合
set hive.map.aggr.hash.min.reduction=0.5;
其原理是,在group by时启动2个MR job,第一个job会将map端数据随机分发到各个reducer,每个reducer做部分聚合,相同key的所有数据就会分布在不同的reducer中.第二个job再将前面预处理过程过的数据按key聚合并输出结果,这样就启到了均衡的效果.
当我们需要对某个字段数据进行去重统计时,如果数据量很大,count(distinct)就会非常慢,为了保证全局去重,count(distinct)只会有一个reduce来处理.这时可以用group by来改写.但是这样会启动两个MR job(单纯的distinct只会启动一个).所以如果数据量不是太大的话,使用count(distinct)或许更快.因为这时还使用group by的话,需要启动2个MR,就会耗费更多的时间.
实际场景:
在日志中,常会有信息丢失的问题,比如日志中的user_id,如果取其中的user_id和用户表中的user_id关联,会碰到数据倾斜的问题.因为这些空值在join时会被分配到一个reduce,拖累进度.(空值字段是连接字段的情况)
解决办法:
如果user_id为null的数据不需要,可以直接过滤掉,不参与关联.
select
user_id
from
(
select user_id from log where log.user_id is not null
)t
join users on t.user_id=users.user_id
如果user_id为null的需要保留可以使用下面2种方式解决数据倾斜:
#先单独取出user_id为null的,
select
user_id
from
(
select user_id from log where log.user_id is not null
)t
join users on t.user_id=users.user_id
union all
select * from log where user_id is null
#在进行join时,将null值用随机值替代然后进行join
select *
from log left
join users
on case when log.user_id is null then cancat("hive",rand()) else log.user_id = users.user_id
说明:
1. 在给定随机值的时候注意产生的随机值不要与另一张表有关联上的可能,一定要保证产生的随机值是关联不上的,因为空值本来就是关联不上的,不要改变了关联了结果.变为随机值也不要影响最终结果.
2. 把空值的key变成一个字符串加上随机数,就能把倾斜的数据分到不同的reduce上,解决数据倾斜问题.而这些分散到各个reduce上的数据由于是随机值,不会产生关联,不会影响最后关联的结果.
用户表中user_id字段类型为int , log表中user_id的字段类型为string.当两表进行关联,user_id作为关联字段时,默认的hash操作会按照int类型的user_id来进行分配,这样会导致所有string类型的user_id记录都会分配到同一个Reducer中,造成严重的数据倾斜.
解决办法: 把数字类型转换成字符串类型.
on user.user_id=cast(log.user_id as string)
在MR job中,默认是每执行一个task就启动一个JVM.如果task非常小而碎,那么JVM启动和关闭的耗时就会很长.可以通过调节参数mapred.job.reuse.jvm.num.tasks的值来达到重用的目的(默认值为1).例如将这个参数设成5,那么就代表同一个MR job中顺序执行的5个task可以重复使用一个JVM,减少启动和关闭的开销.但它对不同MR job中的task无效.
如果大量的小文件启动大量的map,可以通过开启JVM重用来解决.特别适合很难避免小文件或task特别多的场景,这类场景大多执行时间都很短.hadoop默认配置是使用派生JVM来执行map和reduce任务的,这时jvm的启动过程可能会造成相当大的开销,尤其是执行的job包含大量的task任务的情况时.
JVM重用可以使得JVM实例在同一个job中重新使用N次,N的值在Hive中可以通过set mapred.job.reuse.jvm.num.tasks参数来设置.
JVM重用的缺点是一直占着资源,以便进行重用.所以如果有几个task迟迟执行不成功,那么被占着的资源迟迟得不到释放,那么被占着的插槽一直空闲确无法被其它job使用,直到所有task都结束才会释放.
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat
设置上面的参数之后就会自动合并小文件,具体合并的大小由下面的参数决定:
#最大的split大小.一个split就对应1个map. 默认为256MB,可以调大
mapred.max.split.size=256000000; --决定每个map处理的最大的文件大小,单位B
#单个节点最小的split的大小.默认为1Byte,需要调大.那么小于这个数的文件就会合并
mapred.min.split.size.per.node=1; --一个节点上split的至少的大小
#单个机架最小的split的大小
mapred.min.split.size.per.rack=1; --一个机架中split的至少的大小
#在只有map阶段时进行合并
set hive.merge.mapfiles=true
#MR引擎适用,在reduce阶段合并
set hive.merge.mapredfiles=true
#tez引擎适用
set hive.merge.tezfiles=true
#合并后期望的文件大小
set hive.merge.size.per.task=256000000(默认256MB)
#当输出文件大小的平均值大小小于该值时,启动一个独立的mapReduce任务进行文件merge
set hive.merge.smallfiles.avgsize=16000000(默认16MB)
当数据比较大的时候,可以考虑压缩数据.Snappy压缩率低,但压缩,解压速度最快.
压缩Job的中间结果数据和输出数据.具体的压缩方式根据场景来定,如果需要考虑分片,可以使用lzo压缩,为压缩文件建立索引之后,就可以进行分片了.如果不考虑分片,可以使用snappy压缩,因为它的压缩速度是最快的.
开启map和reduce中间过程的压缩,减少map和ruduce task之间的数据传输量.
set hive.exec.compress.intermediate=true; --默认为false
--设置解码格式
set mapred.map.output.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;
Hive的最终输出结果压缩:
set Hive.exec.compress.output=true; --默认为false,开启最终压缩
set mapred.output.compression.type=BLOCK; --默认为record
--设置压缩方式,这里为snappy
set mapred.output.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;
set mapreduce.output.fileoutputformat.compress=true;
说明:
通常,为了减轻存储和数据传输,提升效率,我们都会选择压缩,但压缩的不同格式有不同的适合场景,这个每个公司都有差异,需要根据实际情况来选择。
开启并行执行,在同一个应用中,Hive中互相没有依赖关系的job间是可以并行执行的,最典型的就是多个子查询union all,在集群资源相对充足的情况下,可以开启并行执行,即将参数hive.exec.parallel设为true(默认为false).另外hive.exec.parallel.thread.number可以设定并行执行的线程数,默认为8,一般都够用(即指定并行的最大job个数,默认为8个)。
在有union all的场景下,特别适用,多个任务之间没有依赖,独立运行.在资源不是核心瓶颈的前提下,可以直接缩短运行时间.如果集群只有数台机器,资源有限,开并行会导致资源紧张,这种方式就不一定能提到调休的效果了.
Hive也可以不将任务提交到集群进行运算,而是直接在一台节点上处理。因为消除了提交到集群的消耗.所以比较适合数据量很小,且逻辑不复杂的任务。要启用本地模式,需要设置以下参数:
set hive.exec.mode.local.auto=true; --默认为false,设为ture开启本地执行
set hive.exec.mode.local.auto.inputbytes.max=134217728; --任务输入的总数据量必须小于设定的值,默认128MB
set hive.exec.mode.local.auto.tasks.max=4; --且Mapper的数量必须小于设定的值.默认为4
reduce的数量必须0或1个
所谓严格模式,就是强制不允许用户执行3种有风险的HiveSQL语句,一旦执行就会直接失败.设置hive.mapred.mode的值为strict可以开启严格模式.
严格模式开启后,会禁止以下3类查询:
因为在Hive中很多场景下,我们并不需要查询每行数据中的所有字段,更多的时候是查询需要的某几个字段,这时可以考虑使用列式存储格式,比如parquet和orc格式.如果仅仅是使用HiveSQL查询,可以选用ORC格式,性能是最好的.如果考虑到多分析引擎的使用,比如Spark,Impala等,可以考虑Parquet格式.
Map数量是否是越多越好?
如果一个任务有很多小文件(<<128M),每个小文件也会被当做一个数据块,用一个mapTask来完成.每个mapTask启动和初始化时间>>处理时间,会造成资源浪费,而且系统中同时可用的map数量是有限的.所以对于小文件采用的策略是合并.
每个Map处理接近128M的文件,就是最合适的么?
有一个125M的文件,一般情况下用一个MapTask来完成.假设这个文件字段很少,但记录数却非常多,如果map函数处理的逻辑比较复杂,用一个mapTask去做的话,性能也不好.对于这种复杂文件采用的策略是增加Map数量.
可以通过下面的方式调节map的数量:
computeSplitSize(max(minSize,min(maxSize,blocksize)))
minSize : mapred.min.split.size(默认值为1)
maxSize : mapred.max.split.size(默认值256M)
如果都是默认参数那么计算出来就是按128MB作为一个分片.
也可调整maxSize的最大值可以改变map的数量.
1.如果让maxSize最大值小于blocksize就可以增加map的个数.假如maxSize的
值调为64,那么分片大小计算出来就为64
reducer数量的确定方法比mappper简单的多,可以直接使用mapred.reduce.tasks参数来设置.如果未设置该参数,Hive会自动推测Reducer的数量,推测逻辑如下:
所谓列裁剪就是在查询时只读取需要的列.分区裁剪就是只读取需要的分区.创建分区表,避免全表扫描.
简单理解,在进行join操作时,先使用where条件过滤出需要的行数据,再进行join,这样就能减少下游处理的数据量.而不是先全表join,再进行过滤.
启动一次job尽可能的多做事情,一个job能完成的事情,不要两个job来做.可以尽量减少job的数量,即减少MR的数量,让一个job完成更多的功能.提升执行速度.
一般在实际场景中不会使用union,因为union除了将几张表拼接在一起,还会去重,会增加job的数量.union all会单纯的将几张表拼接在一起,不会产生额外的job,如果关联在一起后真的要去重,也不使用union,而是用group by来去重.
#1.最简单的方式.有4个job.但是效率比较低.
select count(1) from table1;
select count(1) from table2;
select count(1) from table3;
select count(1) from table4;
#2.改进后的方式.只有一个job.只是一个job中有多个map和reduce任务
select
type,count(1)
from
(
select 'table1' as type,name from table1
union all
select 'table2' as type,name from table2
union all
select 'table3' as type,name from table3
union all
select 'table4' as type,name from table4
)tmp
group by type;