Quantcast
Viewing latest article 4
Browse Latest Browse All 5

HDFS中小文件处理方案汇总

HDFS被设计来存储大文件,而有时候会有大量的小文件生成,造成NameNode资源的浪费,同时也影响MapReduce的处理效率。

在实际工作中使用Flume做数据收集,将日志类文本信息存入HDFS。由于配置不当导致大量的小文件生成,如:
shell> hadoop fs -ls -h /hive/request/2013-10-15/
-rw-r–r– 3 hdfs hadoop 20.0 K 2013-11-06 11:45 /hive/request/2013-10-15/FlumeData.1381803387852
-rw-r–r– 3 hdfs hadoop 34.3 K 2013-11-06 11:45 /hive/request/2013-10-15/FlumeData.1381803387853
-rw-r–r– 3 hdfs hadoop 20.5 K 2013-11-06 11:45 /hive/request/2013-10-15/FlumeData.1381803387854
-rw-r–r– 3 hdfs hadoop 13.6 K 2013-11-06 11:45 /hive/request/2013-10-15/FlumeData.1381803387855
-rw-r–r– 3 hdfs hadoop 24.6 K 2013-11-06 11:45 /hive/request/2013-10-15/FlumeData.1381803387856

有哪些方案可以合并这些小文件,或者提高处理小文件的效率呢?

方案1.所有HDFS小文件数据导出到本地单个文件后,再存入HDFS

利用hadoop fs -cat或hadoop fs -text命令,将所有内容导出到本地文件,然后put到HDFS即可。如:

shell> hadoop fs -text /hive/request/2013-10-15/FlumeData.* > FlumeData1
shell> hadoop fs -put FlumeData1 /hive/request/2013-10-15/

或者使用管道:
shell> hadoop fs -text /hive/request/2013-10-15/FlumeData.* | hadoop fs -put – /hive/request/2013-10-15/FlumeData1

然后删除原有文件,注意避免删除新上传的FlumeData1(通过模糊匹配的方式即可)
shell> hadoop fs -rm -skipTrash /hive/request/2013-10-15/FlumeData.*

补充:

  1. 这个合并方案适用于文件格式一致,文件合并顺序不敏感(或者按照文件名为序)的场景,例如这里收集的日志信息,每一条都是一样的格式,每一条记录本身有生成时间信息,所以不依赖与在文件中的位置。
  2. 如果文件中使用数字用于命名,而期望以数字顺序而不是字符串顺序进行合并,会遇到如下问题:
    1. 问题:1,10,100,1000,11,110这些数字从小到大排列,如果安装字符串顺序,是1,10,100,1000,11,110,而我们知道数字的期望顺序是1,10,11,100,110,1000。
    2. 这里的一个参考方法可以如下:hadoop fs -text [0-9]_fileName.txt [0-9][0-9]_fileName.txt [0-9][0-9[0-9]_fileName.txt | hadoop fs -put – targetFilename.txt 以此类推实现更多位数的数字排序。当然,如果可以的话,使用数字前补零的命名方式(如000009),使得所有文件名称长度一致,可以使得字符顺序与数字的顺序一致。
  3. -text包含-cat的功能,-cat只能针对平面文件,而-text可以处理压缩(compressed)和顺序(sequence)文件。

方案2.调用API方法org.apache.hadoop.fs.FileUtil.copyMerge或自行开发

public static boolean copyMerge(FileSystem srcFS,
                                Path srcDir,
                                FileSystem dstFS,
                                Path dstFile,
                                boolean deleteSource,
                                Configuration conf,
                                String addString)
                         throws IOException

拷贝指定目录下的所有文件复制、合并到一个文件。copyMerge更加通用,可以在不同FileSystem中移动,通过deleteSource标识来指定是否删除,如果设定为true,则会删除整个srcDir目录。而conf的传入其实只是为了获取io.file.buffer.size的设置。而addString则是在合并时,每个文件后添加的字符串。

我们可以参考copyMerge的写法,编写我们需要的合并程序,如下例,在本FileSystem中将srcDir下的所有文件写入同一个文件dstFile,而删除则是针对被合并的文件而不是整个目录。

public boolean dirMergeToFile(String srcDir, Path dstFile, boolean deleteSource)
      {
             boolean rtcd = true;
             try {
                  FileSystem fs = FileSystem. get(conf);

                  Path sDir = new Path(srcDir);
                  Path dFile = dstFile;
                   if (!fs.getFileStatus(sDir).isDirectory()) {
                        System. out.println(sDir.getName() + " is not a directory!");
                         return false ;
                  }
                  OutputStream out = null;
                   try {
                         //排除隐藏的文件,即以.开头。
                        FileStatus contents[] = fs.listStatus(sDir);

                         if(contents.length == 0)
                        {
                              return true ;
                        }

                         if (fs.exists(dFile)) {
                              System. out.println(dFile.getName() + " exists!");
                               return false ;
                        }

                        out = fs.create(dFile);
                        Arrays. sort(contents);

                         for (int i = 0; i < contents.length; i++) {
                               if (contents[i].isFile()) {
                                    InputStream in = fs.open(contents[i].getPath());
                                     try {
                                          IOUtils. copyBytes(in, out, conf, false );
                                    } finally {
                                          in.close();
                                    }
                                     if (deleteSource && !fs.delete(contents[i].getPath(), false)) {
                                          rtcd = false;
                                    }
                              }
                        }
                  } finally {
                         if (out != null)
                              out.close();
                  }
                   return rtcd;
            } catch (IOException e) {
                  System. out.println(e.getMessage());
                   return false ;
            }
      }

本质上,这种方案还是先把数据内容读到客户端,再写入到FileSystem。

方案3.Hadoop自带方案Hadoop Archive

hadoop archive命令运行MapReduce job来并行处理输入文件,将小文件的内容合并形成少量大文件,然后再利用index文件,指出小文件在大文件中所属的坐标,以此来减少小文件的量。Hadoop Archives生成归档文件格式为HAR,关于Hadoop Archive详见这篇文章

方案4.使用CombineFileInputFormat针对小文件进行合并输入

CombineFileInputFormat是Hadoop自带的多文件合并处理方案,指定输入目录,将其下的大量小文件进行合并分片,减少map任务数量。详细见MapReduce应用中CombineFileInputFormat原理与用法

参考:
http://stackoverflow.com/questions/14831117/merging-hdfs-files
http://dongxicheng.org/mapreduce/hdfs-small-files-solution/
http://blog.cloudera.com/blog/2009/02/the-small-files-problem/

The post HDFS中小文件处理方案汇总 appeared first on SQLParty.


Viewing latest article 4
Browse Latest Browse All 5

Trending Articles