当前位置: > > > Hadoop - 小文件问题的解决方案1(SequeceFile、MapFile文件的读写)

Hadoop - 小文件问题的解决方案1(SequeceFile、MapFile文件的读写)

1,小文件问题说明

(1)Hadoop HDFS MapReduce 都是针对大数据文件来设计的,在小文件的处理上不但效率低下,而且十分消耗内存资源。
  • 针对 HDFS 而言,每一个小文件在 namenode 中都会占用 150 字节的内存空间,最终会导致集群中虽然存储了很多个文件,但是文件的体积并不大,这样就没有意义了。

(2)针对 MapReduce 而言,每一个小文件都是一个 Block,都会产生一个 InputSplit。最终每一个小文件都会产生一个 map 任务,这样会导致同时启动太多的 Map 任务。Map 任务的启动是非常消耗性能的,但是启动后执行了很短时间就又停止了,因为小文件的数据量太小了,这样就会造成任务执行消耗的时间还没有启动任务消耗的时间多,这样也会影响 MapReduce 执行的效率。

(3)解决办法通常是选择一个容器,将这些小文件组织起来统一存储,HDFS 提供了两种类型的容器,分别 是SequenceFile MapFile

2,SequenceFile 的介绍与使用

(1)SequeceFile Hadoop 提供的一种二进制文件,这种二进制文件直接将 <key, value> 对序列化到文件中。
  • 一般对小文件可以使用这种文件合并,即将小文件的文件名作为 key,文件内容作为 value 序列化到大文件中。
  • 但是这个文件有一个缺点,就是它需要一个合并文件的过程,最终合并的文件会比较大,并且合并后的文件查看起来不方便,必须通过遍历才能查看里面的每一个小文件。
  • 所以这个 SequenceFile 其实可以理解为把很多小文件压缩成一个大的压缩包了。

(2)下面代码演示如何将本地指定目录下的所有文件生成为一个 SequenceFile 文件到 HDFS 上,并且再从这个 SequenceFile 文件中读取内容:
public class SmallFileSeq {

  public static void main(String[] args) throws Exception{
    //指定用户
    System.setProperty("HADOOP_USER_NAME", "root");
    //生成SequenceFile文件
    System.out.println("---- 开始生成SequenceFile文件 ----");
    write("/Volumes/BOOTCAMP/test","/seqFile");
    //读取SequenceFile文件
    System.out.println("---- 开始读取SequenceFile文件 ----");
    read("/seqFile");
  }

  /**
   * 生成SequenceFile文件
   * @param inputDir 输入目录-windows目录
   * @param outputFile 输出文件-hdfs文件
   * @throws Exception
   */
  private static void write(String inputDir,String outputFile)
          throws Exception{
    //创建一个配置对象
    Configuration conf = new Configuration();
    //指定HDFS的地址
    conf.set("fs.defaultFS", "hdfs://192.168.60.9:9000");
    SequenceFile.Writer.Option[] opts = new SequenceFile.Writer.Option[]{
            SequenceFile.Writer.file(new Path(outputFile)),
            SequenceFile.Writer.keyClass(Text.class),
            SequenceFile.Writer.valueClass(Text.class)};

    //创建一个writer实例
    SequenceFile.Writer writer = SequenceFile.createWriter(conf, opts);
    //指定要压缩的文件的目录
    File inputDirPath = new File(inputDir);
    if(inputDirPath.isDirectory()){
      File[] files = inputDirPath.listFiles();
      for (File file : files) {
        //获取文件全部内容
        String content = FileUtils.readFileToString(file, "UTF-8");
        //文件名作为key
        Text key = new Text(file.getName());
        //文件内容作为value
        Text value = new Text(content);
        writer.append(key,value);
      }
    }
    writer.close();
  }

  /**
   * 读取SequenceFile文件
   * @param inputFile SequenceFile文件路径
   * @throws Exception
   */
  private static void read(String inputFile)
          throws Exception{
    //创建一个配置对象
    Configuration conf = new Configuration();
    //指定HDFS的地址
    conf.set("fs.defaultFS","hdfs://192.168.60.9:9000");
    //创建阅读器
    SequenceFile.Reader reader = new SequenceFile.Reader(conf,
            SequenceFile.Reader.file(new Path(inputFile)));
    Text key = new Text();
    Text value = new Text();
    //循环读取数据
    while(reader.next(key,value)){
      //输出文件名称
      System.out.print("文件名:"+key.toString()+",");
      //输出文件的内容
      System.out.println("文件内容:\n"+value.toString());
    }
    reader.close();
  }
}

(3)测试一下,假设我们本地目录有如下三个文件:

  • 上面代码运行后可以看到 HDFS 上已经成功生成 SequenceFile 文件:

  • 同时查看控制台日志,可以发现从这个 SequenceFile 文件中读取内容也是成功的:

3,MapFile 的介绍和使用

(1)MapFile 是排序后的 SequenceFileMapFile 由两部分组成,分别是 indexdata
  • index 作为文件的数据索引,主要记录了每个 Record key 值,以及该 Record 在文件中的偏移位置。
  • MapFile 被访问的时候,索引文件会被加载到内存,通过索引映射关系可迅速定位到指定 Record 所在文件位置。
  • 因此,相对 SequenceFile 而言,MapFile 的检索效率是高效的,缺点是会消耗一部分内存来存储 index 数据。

(2)下面代码演示如何将本地指定目录下的所有文件生成为 MapFile 文件到 HDFS 上,并且再从这个 MapFile 文件中读取内容:
/**
 * 小文件解决方案之MapFile
 */
public class SmallFileMap {

  public static void main(String[] args) throws Exception{
    //指定用户
    System.setProperty("HADOOP_USER_NAME", "root");
    //生成MapFile文件
    System.out.println("---- 开始生成MapFile文件 ----");
    write("/Volumes/BOOTCAMP/test","/mapFile");
    //读取MapFile文件
    System.out.println("---- 开始读取MapFile文件 ----");
    read("/mapFile");
  }

  /**
   * 生成MapFile文件
   * @param inputDir 输入目录-windows目录
   * @param outputDir 输出目录-hdfs目录
   * @throws Exception
   */
  private static void write(String inputDir,String outputDir)
          throws Exception{
    //创建一个配置对象
    Configuration conf = new Configuration();
    //指定HDFS的地址
    conf.set("fs.defaultFS","hdfs://192.168.60.9:9000");

    //构造opts数组,有两个元素(第一个是key类型、第二个是value类型)
    SequenceFile.Writer.Option[] opts = new SequenceFile.Writer.Option[]{
            MapFile.Writer.keyClass(Text.class),
            MapFile.Writer.valueClass(Text.class)};

    //创建一个writer实例
    MapFile.Writer writer = new MapFile.Writer(conf,new Path(outputDir),opts);
    //指定要压缩的文件的目录
    File inputDirPath = new File(inputDir);
    if(inputDirPath.isDirectory()){
      //获取目录中的文件
      File[] files = inputDirPath.listFiles();
      //对获取到的文件进行排序[如果文件默认无序的情况下,需要先进行排序]
      List<File> fileList = Arrays.asList(files);
      Collections.sort(fileList, new Comparator<File>() {
        @Override
        public int compare(File f1, File f2) {
          return f1.getName().compareTo(f2.getName());
        }
      });
      for (File file : fileList) {
        //获取文件全部内容
        String content = FileUtils.readFileToString(file, "UTF-8");
        //文件名作为key
        Text key = new Text(file.getName());
        //文件内容作为value
        Text value = new Text(content);
        writer.append(key,value);
      }
    }
    writer.close();
  }

  /**
   * 读取MapFile文件
   * @param inputDir MapFile文件路径
   * @throws Exception
   */
  private static void read(String inputDir)
          throws Exception{
    //创建一个配置对象
    Configuration conf = new Configuration();
    //指定HDFS的地址
    conf.set("fs.defaultFS","hdfs://192.168.60.9:9000");
    //创建阅读器
    MapFile.Reader reader = new MapFile.Reader(new Path(inputDir),conf);
    Text key = new Text();
    Text value = new Text();
    //循环读取数据
    while(reader.next(key,value)){
      //输出文件名称
      System.out.print("文件名:"+key.toString()+",");
      //输出文件的内容
      System.out.println("文件内容:"+value.toString());
    }
    reader.close();
  }
}

(3)测试一下,假设我们本地目录有如下三个文件:

  • 上面代码运行后可以看到在 HDFS 上会产生一个 /mapFile 目录,这个目录里面有两个文件,一个 index 索引文件,一个 data 数据文件:

  • 同时查看控制台日志,可以发现从这个 MapFile 文件中读取内容也是成功的:
评论0