1. 任务执行过程中爆出java heap space
有三个阶段均会发生该错误,首先判断任务运行到哪个阶段报错内存不足。
map阶段、shuffle阶段、reduce阶段。
map阶段:
一般是发生mapjoin才会产生OOM。
通过设置参 set hive.auto.convert.join=false 转换成reduce端的common join。
如果common join报oom,说明切片过大。因为hdfs显示的是压缩后的大小,解压切片后会撑爆内存。
这个时候需要调整如下参数来调小map端的输入量
set mapred.max.split.size=256000000;
set mapred.min.split.size=10000000;
set mapred.min.split.size.per.node=80000000;
set mapred.min.split.size.per.rack=80000000;
shuffle阶段
一般是由于map的输出较大,shuffle阶段选择拷贝map输出到内存导致。
降低单个shuffle能够消耗的内存占reduce所有内存的比例(set mapreduce.reduce.shuffle.memory.limit.percent=0.10),使得shuffle阶段拷贝map输出时选择落磁。
reduce阶段
单个reduce处理数据量过大,通过设置参数修改reduce个数或者减少每个reduce的聚合数据量
修改reduce个数:
set mapred.reduce.tasks 或者 set mapreduce.job.reduces
减少ruduce聚合数据量
set hive.exec.reducers.bytes.per.reducer=300000000;
2. 数据倾斜
2.1 判断数据倾斜
2.1.1 map阶段处理时间过长
不可分割的文件引发数据倾斜:
压缩算法的不同会造成hdfs上的文件不可切分,无法分片,就只能被一个map获取。例如:GZIP
解决办法:需要将不能切分的文件转换为bzip2或者zip的可压缩算法
2.1.2 reduce阶段处理很慢
测试数据是A、B两表,A中id存在大量null值,B中id无null值。并且id为int类型
2.1.2.1 null值过多导致倾斜
对于null值的出现针对inner join以及left join的计算做了如上统计,可以清晰的去判断什么情况下会有倾斜产生,并且采用哪种方式去处理倾斜。具体的sql语句如下:
1、先处理非null值再union all NULL数据
select
s1.id,
s1.name,
s2.value
from skewdata_A s1
join skewdata_B s2 on s1.id=s2.id
where s1.id is not null
union all
select
id,
name,
null
from skewdata_A
where id is null;
这个语句在做inner join时候也可以不加is not null
2、on条件对null值做随机数处理后再关联
select
s1.id,
s1.name,
s2.value
from skewdata_A s1
left join skewdata_B s2 on coalesce(cast(s1.id as string),concat('hive_',rand()))=cast(s2.id as string);
此处是因为等号两边的数据类型不一致,做了统一处理。
2.1.2.2 关联key数据类型不一致导致倾斜
还是上面的例子,以下是一个对比图
可以看出当两边数据类型int、string不一致,对string类型的id进行hash,默认的hash操作会按int类型的id进行分配,然后string类型的id都会被分配到同一个id 0 ,从而进入到同一个redce数据倾斜。需要使用cast保证两边数据类型一致,处理方法同上
nvl(col1,col2)两个参数数据类型是一致的
coalesce(col1,col2,col3…),数据类型可以不一致
2.1.2.3 热点key
如果业务数据本身存在热点key,即高频访问key这样的特性,key本身就分布不均匀,那么mr join操作的时候必然会引起数据倾斜。
一:如果是单表groupby倾斜优先开启负载均衡
set hive.map.aggr=true; --map端的combiner,默认为true
set hive.groupby.skewindata=true; --开启负载均衡
set hive.groupby.mapper; --配置map端的聚合条数,可不设置
这样 MapReduce 进程则会生成两个额外的 MR Job,这两个任务的主要操作如下:
- 第一步:MR Job 中Map 输出的结果集合首先会随机分配到 Reduce 中,然后每个 Reduce 做局部聚合操作并输出结果,这样处理的原因是相同的Group By Key有可能被分发到不同的 Reduce Job中,从而达到负载均衡的目的。
- 第二步:MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 Group By Key 被分布到同一个 Reduce 中),最后完成聚合操作。
如下是一个普通的group by ,观察一下第一次的mr运行情况
如果是join倾斜,也可以通过设置来解决
set hive.skewjoin.key=10000; --join 的键对应的记录条数超过这个值则会进行分拆
set hive.optimize.skewjoin=true; --join发生倾斜开启
set hive.skewjoin.mapjoin.map.tasks; --控制第二个任务的mapper数量,默认是10000;
- 如果开启第二个参数,hive会将计数超过第一个参数的key对应的记录临时写到文件中
- 然后启动另一个job做map join生成结果
二:使用mapjoin(适用于小表join大表,某个key过大)
mapjoin原理就是将倾斜的数据存到分布式缓存中,分发到各个Map任务所在节点,在map端完成join,没有后续shuffle以及reduce阶段,避免数据倾斜,以及提高效率。
不管是小表join大表还是大表join小表,只要小表是使用mapjoin,流程如下:
- 大表的数据块被分布在多个计算节点上
- 小表加载到内存中,并广播到各个计算节点
- 每个计算节点会对小表构建一个索引结构(哈希表或者排序树)
- 对于每个大表数据块,提取关键键或者条件字段
- 使用索引结构去匹配上一步的关联键
- 匹配成功,就在两张表中找到了相同的关联键值,计算节点根据关联的键值去进行关联处理,获取记录结果
- 最后将各个计算节点的结果reduce合并
注:可以看到map的输入记录只有55条,而理论来说肯定是不止这些的,有点奇怪。
hive0.11版本之后自动开始mapjoin
如果遇到系统没有推测执行mapjoin的,可以显式执行mapjoin。设置的参数配置如下:
set hive.auto.convert.join=true; --关闭自动mapjoin
set hive.ignore.mapjoin.hint=false; --不忽略mapjoin标识
set mapreduce.map.memory.mb=100; --调整map端的内存大小,优化内存溢出
set hive.mapjoin.smalltable.filesize=10000000; --mapjoin表的阈值
三:count(distinct)造成任务缓慢
原因:如果sql语句仅有这个count,没有groupby。distinct本身有一个全局排序的过程,这样只会产生一个reduce,运行缓慢。而加入有groupby,那样又会存在某一个键对应的其他字段有大量重复数据,这样子导致该reduce缓慢,造成数据倾斜
sql语句如下:(
-- 数据倾斜
select a,count(distinct b) from t group by a;
-- 先去重后分组
select a,count(1) from (select a, b from t group by a,b) group by a;
四:多维聚合运算
如果聚合字段过多,容易引发job作业的map端输出数据膨胀,从而内存溢出。如果还存在热点key,还会加剧数据倾斜问题
多维运算如:with rollup,grouping sets,cubes
解决方法两种:
- 拆分多维运算为多个union all + group by。这样子将启动多个job,减少单个job的map端的输出数据量
- 但是一旦聚合字段过多,就要写很多个union all,不方便。引用一个配置如下,该配置自动控制作业的拆解,默认值为30。如果键组合数量超过该值,那会新启动一个job来处理之后的键组合,这样做是为避免单个job的压力过大。另外如果单个作业内有稍大的数据倾斜,将该值调小可以减弱数据倾斜的影响!
hive.new.job.grouping.set.cardinality
五:增加reduce个数
这样做适合于出现了多个大key,reduce数量增多,可以避免多个大key落到了同一个reduce中
set mapred.reduce.tasks=10;
或者
set mapreduce.job.reduces=10;
六:调整reduce运行内存
set mapreduce.reduce.memory.mb=4096; --设置reduce task的内存
set mapreduce.reduce.java.opts=-Xmx5000m -XX:MaxPermSize=128m --表示reduce任务的最大堆内存是5G,永久代空间是128M
3、谓词下推问题以及CBO的影响
在分析之前首先,明确一下概念以及在没有cbo优化时候两个原则(有了cbo之后join之后的谓词有点不太适用,也会进行下推):
概念:
left join左侧名表名称:保留表
右侧表名称:空表(null supplying table)
join中谓词:就是on条件之后的and 条件
join之后的谓词:在where中的条件
原则:
在join中的谓词如果是保留表的,则不会进行下推。
在join后的谓词如果是null supplying table的,则不会进行下推。
使用代码
1. outer join
目标:sql_a 对比 sql_b 验证cbo开启后谓词在sql中位置是否影响下推以及结果;
对比cbo开启前后 sql_b 语句的谓词下推的生效与否。
测试sql:
sql_a:select t1.*,t2.* from test1 t1 left join test2 t2 on t1.id=t2.id and (t2.openid='apple' or t2.openid='lemon') where t1.openid='pear';
sql_b:select t1.*,t2.* from test1 t1 left join test2 t2 on t1.id=t2.id where t1.openid='pear' and (t2.openid='apple' or t2.openid='lemon');
2. full join
目标:sql_c 对比 sql_d 、sql_e 验证cbo开启后谓词在sql中位置是否影响下推以及结果;
对比cbo开启前后 sql_d 语句的谓词下推的生效与否。
测试sql:
sql_c:select t1.*,t2.* from test1 t1 full join test2 t2 on t1.id=t2.id and (t2.openid='apple' or t2.openid='lemon') where t1.openid='pear';
sql_d:select t1.*,t2.* from test1 t1 full join test2 t2 on t1.id=t2.id where t1.openid='pear' and (t2.openid='apple' or t2.openid='lemon');
sql_e:select t1.*,t2.* from (select * from test1 where openid='pear') t1 full join test2 t2 on t1.id=t2.id and (t2.openid='apple' or t2.openid='lemon') ;
两张表数据如下:
通过实际运行后结果如下:
outer join
开启cbo的 sql_a 执行结果
select t1.*,t2.* from test1 t1 left join test2 t2 on t1.id=t2.id and (t2.openid='apple' or t2.openid='lemon') where t1.openid='pear';
开启cbo的 sql_b 执行结果
select t1.*,t2.* from test1 t1 left join test2 t2 on t1.id=t2.id where t1.openid='pear' and (t2.openid='apple' or t2.openid='lemon');
关闭cbo的 sql_b 执行结果
select t1.*,t2.* from test1 t1 left join test2 t2 on t1.id=t2.id where t1.openid='pear' and (t2.openid='apple' or t2.openid='lemon');
结论
1.sql_a 对比 sql_b 验证cbo开启后谓词在sql中位置是否影响下推以及结果:空表的谓词放置顺序不影响下推,但影响结果;虽然确实都会下推,但是在下推的同时对出现在join之后的属于空表的谓词增加了条件,id is not null。正式因为这个原因导致了结果的不一致。
2.对比cbo开启前后 sql_b 语句的谓词下推的生效与否:cbo确实对在join之后的谓词均进行下推。
full join
开启cbo的 sql_c 执行结果
select t1.*,t2.* from test1 t1 full join test2 t2 on t1.id=t2.id and (t2.openid='apple' or t2.openid='lemon') where t1.openid='pear';
开启cbo的 sql_d 执行结果
select t1.*,t2.* from test1 t1 full join test2 t2 on t1.id=t2.id where t1.openid='pear' and (t2.openid='apple' or t2.openid='lemon');
关闭cbo的 sql_d执行结果
select t1.*,t2.* from test1 t1 full join test2 t2 on t1.id=t2.id where t1.openid='pear' and (t2.openid='apple' or t2.openid='lemon');
开启cbo的 sql_e 执行结果
select t1.*,t2.* from (select * from test1 where openid='pear') t1 full join test2 t2 on t1.id=t2.id and (t2.openid='apple' or t2.openid='lemon') ;
结论
1.sql_c 对比 sql_d 、sql_e 验证cbo开启后谓词在sql中位置是否影响下推以及结果:可以观察到sql_c、sql_d均进行了谓词下推,而sql_e却没有,理论上如果sql_d可以进行谓词下推+id is not null, 那sql_e应该也可以进行谓词下推,只是不加is not null而已。这点很疑惑。
2.对比cbo开启前后 sql_d 语句的谓词下推的生效与否:此处可以就看到cbo开启后两张表虽然同时是空表以及保留表,但是都进行谓词下推了。并且此处谓词下推就不符合一开始提到的两个原则了。
4、数据存在特殊ascii码、unicode码
主要是数据含有一些特殊字符,清洗时需要过滤的
- 非断空格 0xa0、\u00A0
select replace(id,'\u00A0','');
- BOM标识符 0xfeff
select '\ufeff'