1. 当时是怎么发现mapreduce任务出现数据倾斜的?
- 使用sqoop从oracle导出数据到hive的时候发现,控制台打印的日志卡在了75%,很长时间没有动过,怀疑是出现数据倾斜或者数据量太大。
2. 数据倾斜的确认
- 于是到Yarn的日志中查看,看到map中仅剩最后一个任务没有跑完,并且其他map任务早早结束(要么是空跑,要么是处理的数据量很少),忙的忙死,闲的闲死。
3. 为什么会出现数据倾斜
第一个是数据分布的不均匀,如果只有1个map来跑当然不会出现数据分配不均的问题。但是当涉及到多个map任务的时候,势必有数据分配不均的可能,下面先看看sqoop划分map的原理。
map数等于1时可以不指定--split-by,多个map时默认是主键,如果没有主键会报错!
举例:如果是split-by的id字段是一个数字类型,首先sqoop会执行这么一条sql,select min(id), max(id) from table_name 也就是先确定字段的范围,再对这个范围根据map数量划分出等宽的区间(范围是1-1000,如果有4个map,就会分成 1-250,260-500,501-750,751-1000)
如果数据大量分布到其中一个区间时,就会出现数据倾斜。
第二个是当split-by的字段是字符串时,可能出现数据倾斜。因为sqoop的split-by对字符串类型的支持很差,无法进行map划分,可能导致数据都集中在一个map上。
这里如果非得要用字符串作为划分依据还需要加上下面的参数
-Dorg.apache.sqoop.splitter.allow_text_splitter=true
4. 出现数据倾斜怎么解决
最容易想到的就是,根据分布较为均匀的字段来划分map,但这需要对原表的数据比较了解。
使用--query参数加上一些小方法解决,比如:
--query select ... from (select rownum as etl_id, t.* from table_name) t1 where $CONDITION
这里做了一个子查询,在原表记录基础上,加上一个行序号(该行序号从1开始),split-by 指定该字段即可解决 !
注意:
1. rownum 一定要起一个别名,不然还是会数据倾斜
2. 还需要加上下面的参数,否则会报 invalid identifier 的错误
--boundary-query "select 1 as MIn, sum(1) as Max from xxx"
因为这个字段在原表不存在,我们自己造出来的
最后跑出来的map处理的数据量都非常平均了~