Bootstrap

Hive 任务重试导致结果错误的原因分析及处理方法

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;
;