https://zhuanlan.zhihu.com/p/165343463?utm_source=wechat_session&utm_medium=social&utm_oi=1118145344197935104
分区裁剪
为了尽早的过滤掉数据,减少每个阶段的数据量,对于分区表要加分区
查询涉及分区表时,在where子句或on子句中限制分区范围
select * from table where ds='2020-07-29'
列裁剪
值读取需要的列,忽略其他不关心的列,避免全表扫描
select * from..... -------->select col1,col2 from .......
合理的设置map、reduce数量
增加map、reduce个数,增加并发数,可以提高计算速度,但是。。。
如果map、reduce数量过多会怎样
从单一任务角度看
从客户资源角度看
默认Map数的计算公式为
default_num=total_size/block_size
影响map的个数因素为:
splitSize=Math.max(minSize,Math.min(maxSize,blockSize))
mapred.max.split.size指的是数据最大分割单元大小(默认为256M)
mapred.min.split.size指的是数据最小分割单元大小(默认为1B)
可以通过参数mapred.map.tasks来设置期望的map个数,但是这个个数只有大于default_num的时候才生效
合理设置reduce的个数
reduce个数并不是越多越好
和map一样,启动和初始化reduce也会消耗时间和资源;
另外,有多少个reduce,就会有多少个输出文件,如果小文件过多,而这些小文件是下游的输入,则会造成小文件过多的问题
只有单一的reduce也不是很好,什么情况下会造成只有一个reduce呢?
很多时候你会发现不管任务中的数据量有多大,不管你有没有设置reduce的个数,任务总是只有一个reduce
其实,只有一个reduce的情况,除了数据量小于hive.exec.reducers.bytes.per.reducer的参数情况外,还有以下原因:
在未设置reduce个数的情况下,计算公式如下
reducers=Math.min(maxReducers,totalInputFileSize/bytesPerReduces)
maxReducers有参数hive.exec.reduces.max设置(默认999)
bytesPerReduces由参数hive.exec.reducers.bytes.per.reducer设置(默认是1G)
举个例子:
1、如果一个文件不超过1G,那么只有一个reduce
2、如果一个文件有8.2G,那么有9个reduce
3、如果一个文件有8.2G,调整hive.exec.reducers.bytes.per.reducer参数的值,比如set hive.exec.reducers.bytes.per.reducer=500000000;(500M)那么会有18个reduce产出
4、直接设置reduce个数
set mapred.reduce.tasks=15;就会有15个reduce产生
输入阶段合并(即在map前合并小文件)
1、执行map前进行小文件合并
需要更改Hive的输入文件格式,即参数hive.input.format,默认值是org.apache.hadoop.hive.ql.io.HiveInputFormat,我们改成 set org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
2、每个map最大输入大小,决定合并后的文件数
set mapred.max.split.size=256000000;
3、一个节点上split的至少的大小,决定多个data node 上的文件是否需要合并
set mapred.min.split.size.per.node=100000000;
4、一个交换机下split的至少的大小,决定多个交换机上的文件是否合并
set mapred.min.split.size.per.rack=100000000;
mapred.min.split.size.per.node和mapred.min.split.size.per.rack,含义是单节点和单机架上的最小split大小。如果发现有split大小小于这两个值(默认都是100MB),则会进行合并。具体逻辑可以参看Hive源码中的对应类。
输出阶段合并
直接将hive.merge.mapfiles和hive.merge.mapredfiles都设为true即可,前者表示将map-only任务的输出合并,后者表示将map-reduce任务的输出合并。
另外,hive.merge.size.per.task可以指定每个task输出后合并文件大小的期望值,hive.merge.size.smallfiles.avgsize可以指定所有输出文件大小的均值阈值,默认值都是1GB。如果平均大小不足的话,就会另外启动一个任务来进行合并。
什么事shuffle,shuffle就是map端输出到reduce输入的过程
可能存在的问题
优化思路
优化方法
中间结果压缩(集群默认是开启的)
开启压缩中间结果
设置中间压缩算法
利用map join特性
如果两张表关联,一张表非常大,一张表非常小,可采用mapjoin,小表叫build table,大表叫probe table
小表放左边,开启hive.auto.convert.join=true(默认是开启的)
select /*+mapjoin(a)*/ a.event_type,b.upload_time
from calendar_event_code a
inner join (
select event_type,upload_time from calendar_record_log
where pt_date = 20190225
) b on a.event_type < b.event_type;
上面的语句中加了一条map join hint,以显式启用map join特性。早在Hive 0.8版本之后,就不需要写这条hint了。map join还支持不等值连接,应用更加灵活。
多表join时key相同
这种情况会将多个join合并为一个MR job来处理,例如:
select a.event_type,a.event_code,a.event_desc,b.upload_time
from calendar_event_code a
inner join (
select event_type,upload_time from calendar_record_log
where pt_date = 20190225
) b on a.event_type = b.event_type
inner join (
select event_type,upload_time from calendar_record_log_2
where pt_date = 20190225
) c on a.event_type = c.event_type;
如果上面两个join的条件不相同,比如改成a.event_code = c.event_code,就会拆成两个MR job计算。
负责这个的是相关性优化器CorrelationOptimizer,它的功能除此之外还非常多,逻辑复杂,参考Hive官方的文档可以获得更多细节:https://cwiki.apache.org/confluence/display/Hive/Correlation+Optimizer。
什么是数据倾斜,就是大量相同的key被分到了一个分区中,导致出现"一人累死,其他人闲死"的情况。
数据倾斜的表现,任务进度长时间维持在99%(或100%),查看任务监视页面,发现只有少量(1个或几个)子任务未执行,因为其他处理的数据量和其他reduce差异过大,单一的reduce记录数和平均记录数差异过大,通常可能达到3倍甚至更多。最长时长也远大于平均时长
数据倾斜优化--group by
空值或无意义值
这种情况很常见,比如当事实表是日志类数据时,往往会有一些项没有记录到,我们视情况会将它置为null,或者空字符串、-1等。如果缺失的项很多,在做join时这些空值就会非常集中,拖累进度。
因此,若不需要空值数据,就提前写where语句过滤掉。需要保留的话,将空值key用随机方式打散,例如将用户ID为null的记录随机改为负值:
select a.uid,a.event_type,b.nickname,b.age
from (
select
(case when uid is null then cast(rand()*-10240 as int) else uid end) as uid,
event_type from calendar_record_log
where pt_date >= 20190201
) a left outer join (
select uid,nickname,age from user_info where status = 4
) b on a.uid = b.uid;
不同数据类型
这种情况不太常见,主要出现在相同业务含义的列发生过逻辑上的变化时。
举个例子,假如我们有一旧一新两张日历记录表,旧表的记录类型字段是(event_type int),新表的是(event_type string)。为了兼容旧版记录,新表的event_type也会以字符串形式存储旧版的值,比如'17'。当这两张表join时,经常要耗费很长时间。其原因就是如果不转换类型,计算key的hash值时默认是以int型做的,这就导致所有“真正的”string型key都分配到一个reducer上。所以要注意类型转换:
select a.uid,a.event_type,b.record_data
from calendar_record_log a
left outer join (
select uid,event_type from calendar_record_log_2
where pt_date = 20190228
) b on a.uid = b.uid and b.event_type = cast(a.event_type as string)
where a.pt_date = 20190228;
join时存在数据倾斜,优化分为两大方面:
skew join
记录数超过参数hive.skewjoin.key(默认为100000)设置大小就是特殊值
涉及到分区表时,未设置分区限制
内存不足发生阶段,找到是在哪个阶段发生的并处理,map阶段,shuffle阶段,reduce阶段
map阶段:
shuffle阶段:
reduce阶段
2.增加jvm内存
3.增加reduce的个数
4.自定义分区
5.重新设计key
前面主要讲的是一些hive参数的设置,和集群方面的设计,下面主要讲下从sql层面上怎么进行优化,因为对于一个业务方来说,只需要写sql。而且sql的优化往往会大大的提高任务的执行效率
1.使用相同的连接健
2.尽量原子化操作
3.用insert into 代替union all
insert overwrite table tablename partition(ds=.....)
select * from (select ... from A
union all select .... from B
union all select .... from C)R
where ....
改为
insert overwrite table tablename partition(ds=.....) select ... from A
insert overwrite table tablename partition(ds=.....) select ... from B
insert overwrite table tablename partition(ds=.....) select ... from C
4.空值或无意义值
这种情况很常见,比如当事实表是日志类数据时,往往会有一些项没有记录到,我们视情况会将它置为null,或者空字符串、-1等。如果缺失的项很多,在做join时这些空值就会非常集中,拖累进度。
select * from log a join b on a.userId is not null and a.userId=b.userId
union all
select * from log where a.userId is null
select * from log a join b on case when a.userId is null then cancat('hive',RAND())
else a.userId End =b.userId
方案一job数为2,方案2job数为1
5.不同数据类型
这种情况不太常见,主要出现在相同业务含义的列发生过逻辑上的变化时。
举个例子,假如我们有一旧一新两张日历记录表,旧表的记录类型字段是(event_type int),新表的是(event_type string)。为了兼容旧版记录,新表的event_type也会以字符串形式存储旧版的值,比如'17'。当这两张表join时,经常要耗费很长时间。其原因就是如果不转换类型,计算key的hash值时默认是以int型做的,这就导致所有“真正的”string型key都分配到一个reducer上。所以要注意类型转换:
select a.uid,a.event_type,b.record_data
from calendar_record_log a
left outer join (
select uid,event_type from calendar_record_log_2
where pt_date = 20190228
) b on a.uid = b.uid and b.event_type = cast(a.event_type as string)
where a.pt_date = 20190228;
6.消灭子查询的group by
select * from(select * from A group by col1,col2
union all select * from B group by col1,col2)
group by col1,col2)
改为
select * from(select * from A
union all select * from B)
group by col1,col2)
sort by代替order by
HiveQL中的order by与其他SQL方言中的功能一样,就是将结果按某字段全局排序,这会导致所有map端数据都进入一个reducer中,在数据量大时可能会长时间计算不完。
如果使用sort by,那么还是会视情况启动多个reducer进行排序,并且保证每个reducer内局部有序。为了控制map端数据分配到reducer的key,往往还要配合distribute by一同使用。如果不加distribute by的话,map端数据就会随机分配到reducer。
举个例子,假如要以UID为key,以上传时间倒序、记录类型倒序输出记录数据:
select uid,upload_time,event_type,record_data
from calendar_record_log
where pt_date >= 20190201 and pt_date <= 20190224
distribute by uid
sort by upload_time desc,event_type desc;
观察hadoop的处理过程,有几个显著的特征:
优化后的任务,效率提升不止提升了一点点。。。
开发过程中还是需要注意下语句使用的。。。
套用公司大佬的一句话。优化其实就是一个思路。需要计算的内容提前把数据量缩小。。
文章灵感主要来自是公司内部的分享,可能还有不足的点,欢迎大佬指点改进
共勉。。。。。