其實大多數場景下,各種大數據框架預定義的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跳過這個文件塊