【spark】自定义数据读取的InputFormat(异常:incorrect data check)

其实大多数场景下,各种大数据框架预定义的InputFormat(数据读取器)是够用的,除了一些比较特殊的情况,特殊的数据格式,我们才会需要自定义读取数据的方式。

然后有一天,我在接入一个hdfs上gz格式数据的时候,遇到了一个报错:

仔细看了报错,是输入流在read数据的时候,调用LineRecordReader的nextKeyValue方法报错了,百度了下,没有什么太准确的答案,大致来说是读取的gz文件块异常,但是这部分hadoop的原生代码,并没有异常的捕获或者往外抛,导致遇到异常,spark任务直接挂掉,所以,有没有办法丢弃损坏的数据,继续往后读呢?

 

所以我们就需要自定义InputFormat,然后在关键关键位置做异常处理(LineRecordReader的nextKeyValue

1、使用spark的默认的方式如下(无法处理异常):

//如果该路径下有多个文件,都可以读到
Dataset<String> stringDataset = spark.read().textFile(路径);

 

2、解决异常:

-1.自定义InputFormat数据读取方式

//new Configuration()这个是引入org.apache.hadoop.conf.Configuration
//数据返回的是rdd<偏移量,具体数据>,处理数据的时候,直接拿具体数据处理就好了
JavaRDD<Tuple2<LongWritable, Text>> tuple2RDD = spark
                        .sparkContext()
                        .newAPIHadoopFile(路径,
                        TextInputFormatSelf.class,
                        LongWritable.class,
                        Text.class,
                        new Configuration())
                        .toJavaRDD();

-2.自定义的inputFormat

import com.google.common.base.Charsets;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;


public class TextInputFormatSelf extends TextInputFormat {
    @Override
    public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) {

        String delimiter = context.getConfiguration().get(
                "textinputformat.record.delimiter");
        byte[] recordDelimiterBytes = null;
        if (null != delimiter)
            recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
        return new LineRecordReaderSelf(recordDelimiterBytes);

    }

    @Override
    protected boolean isSplitable(JobContext context, Path file) {
        return super.isSplitable(context, file);
    }
}

-3.自定义LineRecordReader


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.Seekable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.Decompressor;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.LineRecordReader;
import org.apache.hadoop.util.LineReader;

import java.io.IOException;


public class LineRecordReaderSelf extends LineRecordReader {

    private static final Log LOG = LogFactory.getLog(LineRecordReaderSelf.class);

    private long start;
    private long pos;
    private long end;
    private LineReader in;
    private FSDataInputStream fileIn;
    private Seekable filePosition;
    private int maxLineLength;
    private LongWritable key;
    private Text value;
    private boolean isCompressedInput;
    private Decompressor decompressor;
    private byte[] recordDelimiterBytes;

    public LineRecordReaderSelf() {
    }

    public LineRecordReaderSelf(byte[] recordDelimiter) {
        this.recordDelimiterBytes = recordDelimiter;
    }

    @Override
    public boolean nextKeyValue() throws IOException {
        boolean flag = false;
        try {
            flag = super.nextKeyValue();
        }catch (Exception e){
            LOG.error("============解析gz压缩文件异常==============");
            //e.printStackTrace();
            LOG.error(e.getMessage());
        }
        return flag;
    }



    private int maxBytesToConsume(long pos) {
        return isCompressedInput
                ? Integer.MAX_VALUE
                : (int) Math.min(Integer.MAX_VALUE, end - pos);
    }

    private long getFilePosition() throws IOException {
        long retVal;
        if (isCompressedInput && null != filePosition) {
            retVal = filePosition.getPos();
        } else {
            retVal = pos;
        }
        return retVal;
    }

    @Override
    public void initialize(InputSplit genericSplit, TaskAttemptContext context) throws IOException {
        super.initialize(genericSplit, context);
    }

    @Override
    public LongWritable getCurrentKey() {
        return super.getCurrentKey();
    }

    @Override
    public Text getCurrentValue() {
        return super.getCurrentValue();
    }

    @Override
    public float getProgress() throws IOException {
        return super.getProgress();
    }

    @Override
    public synchronized void close() throws IOException {
        super.close();
    }
}

 

可以看到我基本什么方法都是继承父类的,唯一就是加了trycatch,所以这代码简陋到啥都没做,就是捕获了异常,为了让spark不会挂掉,当然如果你有其他想法,可以尝试添加更多的东西。举例:想找到异常数据的原因,是否可以将异常数据存到其他地方统一人工处理。

==========================再更新下=============================

有朋友说遇到如下的报错:

 

我们经过讨论后,觉得有两个方法

第一:

这个Could not obtain block:XXX报错,是hdfs的文件块有损坏缺快的问题,讲道理,应该去修复有异常的块来解决这个问题,基本任何框架读这个块的时候都会出问题,并不是spark的问题

第二:

但是如果我就是想跳过无视这个块,该怎么做呢,注意看报错,报错的是initialize,也就是说在做初始化的时候报错了

那讲道理,我们也可以在初始化方法的时候加上trycatch跳过这个文件块

 

好,菜鸡一只~就简单说到这里,大家如果有什么问题,欢迎留言!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章