Hadoop Streaming 中的数据倾斜坑
1. 背景
最近用 hadoop streaming 跑一个数据集,不算大,每小时150G左右,但是每次耗时特别长,而且基本是卡在了reduce 98%
的地方。
看了下输出,基本上是数据集中到一两个reducer上了,因此每次吐出最后几个reducer耗时特别长。
处理的数据在mapper输出类似于下面这种形式(真实数据企业敏感度高,就不公开了), 第一列取值范围很小,假设为[0-9],第二列大一些[0-100],第三列[0-1000],第四列[0-10000]。
1 10 100 1000
2 21 211 2110
...
9 10 992 9982
一开始出现数据倾斜的脚本如下,设定了map、reducer的数量、容量、内存上限等。
...省略各种变量定义
${HADOOP_CLIENT_BIN} streaming \
-D mapred.map.tasks=1024 \
-D mapred.reduce.tasks=256 \
-D mapred.job.map.capacity=256 \
-D mapred.job.name="Task_${DATE}_${HOUR}" \
-D mapred.job.priority="HIGH" \
-D stream.memory.limit=8000 \
-D mapred.job.reduce.memory.mb=8000 \
-file Mapper.py \
-file Reducer.py \
-input ${SOURCE_PATH}/*/*.log* \
-output ${OUT_PATH} \
-mapper "python Mapper.py hadoop" \
-reducer "python Reducer.py hadoop"
2. 为什么出现数据倾斜
在Hadoop streaming 中,一次maper后默认会以第一列作为key进行分桶shuffle,将hash到同一个分桶的key放到一个reducer中进行输出。由于我的mapper输出的第一列取值范围很小,而且如果以第一列为key的话,每个key下的数据规模也是不同的。
我的脚本中指定了256个reducer,而key的取值只有10个,假设每个key被hash到了不同的分桶,那么最多只有10个reducer被合理使用到了,换言之,其他reducer都浪费了。而且,实际上这些key很有可能被hash到少量的几个分桶中甚至同一个分桶,于是就出现了开头的情况:reducer到了98%会执行一个多小时,那么实际150G的数据都是由这一两个reducer写到了HDFS上。
3. 解决方案
明白了出现数据倾斜的原因了,那么就好解决了。hadoop streaming中有个参数叫做stream.num.map.output.key.fields
,这个参数的含义是,从左往右以多少列数据作为mapper的key,比如我将该参数设为4,那么整行数据将作为一个key算hash分桶,这样的话,key的取值范围就成了[1 * 100 * 1000 * 10000, 10 * 100 * 1000 * 10000]
,这样就能有效避免mapper结果shuffle后数据倾斜了。
...省略各种变量定义
${HADOOP_CLIENT_BIN} streaming \
-D mapred.map.tasks=1024 \
-D mapred.reduce.tasks=256 \
-D mapred.job.map.capacity=256 \
-D mapred.job.name="Task_${DATE}_${HOUR}" \
-D mapred.job.priority="HIGH" \
-D stream.memory.limit=8000 \
-D mapred.job.reduce.memory.mb=8000 \
-D stream.num.map.output.key.fields=4 \
-file Mapper.py \
-file Reducer.py \
-input ${SOURCE_PATH}/*/*.log* \
-output ${OUT_PATH} \
-mapper "python Mapper.py hadoop" \
-reducer "python Reducer.py hadoop"
4. 举一反三
相关参数:
1. stream.num.map.output.key.fields
,stream.num.reducer.output.key.fields
;指定mapper和reducer输出的key的列范围,例如,值为2表示第1、2列一起作为key。
2. stream.map.output.field.separator
可以自定义分割符号,比如你希望用\x01
分割数据,可以再这里进行指定。
3. mapred.text.key.partitioner.options
用来保证指定key后,相同的key被分配到同一个reducer上,设置值为-ki,j
,表示从第i到第j列作为分区的key。