Map 任务中的数据使用随机数作为分区函数时,相同的数据每次执行不固定到相同的 Reduce 处理。导致 Map 任务出错重试,或者推测执行时,两次执行导致部分数据丢失。
1. hive.groupby.skewindata=true;
1.1 执行第1个SQL,全程不要动,等待执行结束
set hive.execution.engine=mr;
set hive.groupby.skewindata=true;
set mapred.reduce.tasks=20;
create table test.short_1 as
select
distinct id
from
test.t_ad
where
combine_name in ('attribute_unionid', 'crowd_p3_unionid', 'score_p1_unionid');
- 正确结果
hive> select count(1) from test.short_1 ;
OK
999000
1.2 执行第2个sql 等待第1个 mr 程序,reduce 任务部分完成时 执行 kill ${MRAppMaster}。
set hive.execution.engine=mr;
set hive.groupby.skewindata=true;
set mapred.reduce.tasks=20;
create table test.short_2 as
select
distinct id
from
test.t_ad
where
combine_name in ('attribute_unionid', 'crowd_p3_unionid', 'score_p1_unionid');
- 错误结果
hive> select count(1) from test.short_2 ;
OK
992493
比第1个SQL的结果少。
2. 原因分析
2.1 am 重试
分析执行计划
set hive.execution.engine=mr;
set hive.groupby.skewindata=true;
set mapred.reduce.tasks=20;
explain create table test.short_2 as
select
distinct id
from
test.t_ad
where
combine_name in ('attribute_unionid', 'crowd_p3_unionid', 'score_p1_unionid');
可以看到,Stages 1 的输出包含内容 Map-reduce partition columns: rand() (type: double)
。
说明某一 key,对应的 partition 是随机的,第1次执行和第2次执行对应的 partition 不是固定的。
假设有4条记录,key1,key2,key3,key4 两次运行如下。
第1次运行
key1 --> reduce1
key2 --> reduce2
key3 --> reduce2
key4 --> reduce1
reduce1_0 size: 100, reduce2_0 size: 100
第2次运行
key1 --> reduce1
key2 --> reduce1
key3 --> reduce1
key4 --> reduce2
reduce1_1 size: 150, reduce2_1 size: 50
由于 reduce1_1 比 reduce_0 大, reduce2_0 比 reduce2_1 大,则取 reduce1_1 和 reduce2_0 的结果。
那么结果为 reduce1_1 中的 key1,key2,key3 和 reduce2_0 的 key2 和 key3 再聚和,结果为(key1,key2,key3)
2.2 Map 任务重试
在 reduce 任务启动之前,Map 任务是可以重试的,但是当部分 reduce 任务shuffle 数据后,map 任务重试会导致结果出错。
如以下示例:
Map 第1次运行
key1 --> reduce1
key2 --> reduce2
key3 --> reduce2
key4 --> reduce1
Map 第2次运行
key1 --> reduce1
key2 --> reduce1
key3 --> reduce1
key4 --> reduce2
在 map 第1次运行之后, reduce1 shuffle 了 map1 的数据(key1, key4)。
这时 Map 第1次运行的服务器发生故障。reduce2 不能 shuffle 数据,汇报给 MRAppMaster。
MRAppMaster 重新执行 Map task,Map task 第2次运行后,reduce2 shuffle 数据(key4)。
这时,reduce1 和 reduce2 均能成功运行,但是丢失了 (key2, key3), (key1) shuffle 了 2 次。
3. hive.groupby.skewindata=false;
执行以下SQL, 等待第1个 mr 程序,reduce 任务部分完成时 执行 kill ${MRAppMaster}。
set hive.execution.engine=mr;
set hive.groupby.skewindata=false;
set mapred.reduce.tasks=20;
create table test.short_skewindata_1 as
select
distinct id
from
test.t_ad
where
combine_name in ('attribute_unionid', 'crowd_p3_unionid', 'score_p1_unionid');
- 结果
hive> select count(1) from test.short_skewindata_1;
OK
999000
可以看到结果正确
4. 其他类似情形
distribute by rand() 等按 rand() 进行分区的,都存在此问题。
5. 解决方案
可用开发一个 hook, 在 SQL 执行之前,修改当前会话 conf 的值,在 SQL 执行后,需要恢复当前会话这些参数的默认值。
5.1 MR 引擎
当设置 hive.groupby.skewindata=true时,或者 SQL 中存在 rand() 时,设置以下参数
set mapreduce.am.max-attempts=1;
set mapreduce.map.speculative=false;
set mapreduce.reduce.speculative=false;
set mapred.map.max.attempts=1;
set mapred.map.reduce.attempts=1;
5.2 TEZ 引擎
如果引擎为 tez 时,设置 hive.groupby.skewindata=true 对执行计划没有影响。
SQL 中存在 rand() 设置以下参数
set tez.am.max.app.attempts=1;