如何使用Spark高效读取多个小镶木地板文件?有一个CombineParquetInputFormat?

时间:2017-01-24 23:28:53

标签: apache-spark apache-spark-sql spark-streaming spark-dataframe parquet

Spark生成了多个小镶木地板文件。如何在生产者和消费者Spark工作中有效地处理少量镶木地板文件。

2 个答案:

答案 0 :(得分:1)

import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.CombineFileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.CombineFileRecordReader;
import org.apache.hadoop.mapreduce.lib.input.CombineFileRecordReaderWrapper;
import org.apache.hadoop.mapreduce.lib.input.CombineFileSplit;
import parquet.avro.AvroReadSupport;
import parquet.hadoop.ParquetInputFormat;

import java.io.IOException;

public class CombineParquetInputFormat<T> extends CombineFileInputFormat<Void, T> {


    @Override
    public RecordReader<Void, T> createRecordReader(InputSplit split, TaskAttemptContext
            context) throws IOException {
        CombineFileSplit combineSplit = (CombineFileSplit) split;
        return new CombineFileRecordReader(combineSplit, context, CombineParquetrecordReader.class);
    }

    private static class CombineParquetrecordReader<T> extends CombineFileRecordReaderWrapper<Void, T> {


        public  CombineParquetrecordReader(CombineFileSplit split, TaskAttemptContext context, Integer idx) throws
                IOException, InterruptedException {
            super(new ParquetInputFormat<T>(AvroReadSupport.class), split, context, idx);
        }
    }
}

在消费者方面,请使用CombinedParquetInputFile:这将强制从单个任务中读取多个小文件。

在制片人方面: 用户合并(numFiles)使得没有足够的文件作为输出。

如何在spark中使用customInputFileFormat并形成RDD和Dataframes:

     JavaRDD<Row> javaRDD = sc.newAPIHadoopFile(hdfsInputPath, CombineParquetInputFormat.class, Void.class, "AvroPojo.class", sc.hadoopConfiguration())
                                            .values()
                                            .map(p -> {
                                                Row row = RowFactory.create(avroPojoToObjectArray((p));
                                                return row;
                                            });


   sc.hadoopConfiguration().setBoolean(FileInputFormat.INPUT_DIR_RECURSIVE,true);


//set max split size else only 1 task wil be spawned    
 sc.hadoopConfiguration().setLong("mapreduce.input.fileinputformat.split.maxsize", (long) (128 * 1024 * 1024));


     StructType outputSchema = (StructType) SchemaConverters.toSqlType(Profile.getClassSchema()).dataType();
            final DataFrame requiredDataFrame = sqlContext.createDataFrame(javaRDD, outputSchema);

请参阅http://bytepadding.com/big-data/spark/combineparquetfileinputformat/以获得深入了解

答案 1 :(得分:1)

最简单的方法是在编写镶木地板文件之前使用重新分区/合并(更喜欢合并,除非数据偏斜并且你想创建相同大小的输出),这样你就不会创建小文件了。

df
  .map(<some transformation>)
  .filter(<some filter>)
  ///...
  .coalesce(<number of partitions>)
  .write
  .parquet(<path>)

可以根据数据帧中的总行数计算分区数除以某个因子,通过试验和错误将为您提供适当的大小。

大多数大数据框架中的最佳做法是将少量较大的文件存储到许多小文件中(我通常使用的文件大小为100-500MB)

如果您已经拥有小文件中的数据并且想要合并它,我知道您必须使用Spark重新分区将其读取到更少的分区并再次写入。