【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跳過這個文件塊

 

好,菜雞一隻~就簡單說到這裏,大家如果有什麼問題,歡迎留言!

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