Hadoop集群本身不推荐存储小文件,因为在mapreduce程序调度过程中,默认map的输入是不跨文件的,如果一个文件很小(远小于一个块的大小,目前集群块大小是256M),在调度时也会生成一个map,且一个map只处理这个小文件,这样mapreduce程序执行,其实大部分时间都消耗在调度过程中,而不是执行mapreduce程序过程中,这样会使得程序的执行效率很低。
所以在Hadoop集群上应该尽量保障存储的文件尽量大,这样在文件put到集群上时,会将文件按块大小切块,在执行mapreduce过程中,又会将块切分成片,然后将一个切片作为一个map输入,一个map处理的将是一个块大小(一个map的输入最大也就是一个块大小),这样mapreduce程序的执行时间都可以认为是map程序和reduce程序的执行时间,而调度的时间可以忽略不计,这样整个集群的执行效率是最高的。
合并小文件的方式主要有两种:
1. 将小文件归档成har文件
2. 用MR程序将小文件合并
对于第一种方法,归档成har包方法具体如下:
1.生成归档文件
hadooparchive -archiveName test.har -p /sourcePath /destPath
2. 查看归档之后的文件
hadoop dfs -ls /destPath
3. 查看归档之前的文件
hadoop dfs -ls har:///destPath
4. 归档文件也可以像归档之前文件一样进行后续的mapreduce程序的计算
hadoopjar hadoop-examples-1.0.3.jar wordcount har:///tmp/aaa.har/* /tmp/wordcount2
对于第二种方法,自己写了一个根据输入的目录大小判断最终生成多少个reduce的方法,reduce的个数,也就是最终的文件数量。
#!/bin/bash
block=2147483648; #2G
#check input param num
if [[ $# -ne 2 ]]
then
echo "the usage of this script is:";
echo $'\t sh merge.sh <input path> <temp ouput path>';
exit 1;
fi
#check input input and output path
if `hadoop dfs -test -e $1`
then
if `hadoop dfs -test -e $2`
then
echo "the <temp output path> has already exist, please choose anther and try again!";
exit 1;
else
#do the merge
size=`hadoop dfs -dus $1 | awk -F" " '{print $2}'`;
num=`expr $size / $block`;
if [[ $num -eq 0 ]]
then
num=1;
fi
hadoop jar inputtooutput.jar com.InputToOutput.InputToOutput \
-D input_dir=$1 \
-D output_dir=$2 \
-D reduce_num=$num
if [[ $? -eq 0 ]]
then
#delete source path
hadoop dfs -rmr $1 >/dev/null
#mv des path to source path
hadoop dfs -mv $2 $1;
if [[ $? -eq 0 ]]
then
echo "merge success!";
fi
fi
fi
else
echo "the <input path> is not exist, please check and try again!";
exit 1;
fi