gpt4 book ai didi

scala - 如何强制 spark/hadoop 忽略文件上的 .gz 扩展名并将其读取为未压缩的纯文本?

转载 作者:可可西里 更新时间:2023-11-01 14:19:42 28 4
gpt4 key购买 nike

我的代码如下:

val lines: RDD[String] = sparkSession.sparkContext.textFile("s3://mybucket/file.gz")

URL 以 .gz 结尾,但这是遗留代码的结果。该文件是纯文本,不涉及压缩。然而,spark 坚持将其作为 GZIP 文件读取,这显然失败了。我怎样才能让它忽略扩展名并简单地将文件作为文本读取?

基于 this article我已经尝试在不包括 GZIP 编解码器的各个地方设置配置,例如:

sparkContext.getConf.set("spark.hadoop.io.compression.codecs", classOf[DefaultCodec].getCanonicalName)

这似乎没有任何效果。

由于文件在 S3 上,我不能在不复制整个文件的情况下简单地重命名它们。

最佳答案

第一个解决方案:Shading GzipCodec

这个想法是通过包含在你自己的来源这个 java file并替换这一行:

public String getDefaultExtension() {
return ".gz";
}

与:

public String getDefaultExtension() {
return ".whatever";
}

在构建您的项目时,这将有效地使用您对 GzipCodec 的定义,而不是依赖项提供的定义(这是 GzipCodec 的阴影)。

这样,在解析您的文件时,textFile() 将被迫应用默认编解码器,因为 gzip 编解码器不再适合您的文件命名。

此解决方案的不便之处在于您将无法在同一个应用程序中处理真正的 gzip 文件。


第二种解决方案:将 newAPIHadoopFile 与自定义/修改的 TextInputFormat 结合使用

您可以使用 newAPIHadoopFile (而不是 textFile)与自定义/修改 TextInputFormat这强制使用 DefaultCodec(纯文本)。

我们将根据默认的 (TextInputFormat) 编写我们自己的行阅读器。这个想法是删除 TextInputFormat 的一部分,它发现它被命名为 .gz 并因此在读取文件之前解压缩文件。

不是调用 sparkContext.textFile

// plain text file with a .gz extension:
sparkContext.textFile("s3://mybucket/file.gz")

我们可以使用底层的sparkContext.newAPIHadoopFile,它允许我们指定如何读取输入:

import org.apache.hadoop.mapreduce.lib.input.FakeGzInputFormat
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.io.{LongWritable, Text}

sparkContext
.newAPIHadoopFile(
"s3://mybucket/file.gz",
classOf[FakeGzInputFormat], // This is our custom reader
classOf[LongWritable],
classOf[Text],
new Configuration(sparkContext.hadoopConfiguration)
)
.map { case (_, text) => text.toString }

调用 newAPIHadoopFile 的常用方法是使用 TextInputFormat。这是包装如何读取文件以及根据文件扩展名选择压缩编解码器的部分。

我们称它为 FakeGzInputFormat 并按以下方式实现它作为 TextInputFormat 的扩展(这是一个 Java 文件,让我们把它放在包 src/main/java/org/apache/hadoop/mapreduce/lib/input 中):

package org.apache.hadoop.mapreduce.lib.input;

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 com.google.common.base.Charsets;

public class FakeGzInputFormat extends TextInputFormat {

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);

// Here we use our custom `FakeGzLineRecordReader` instead of
// `LineRecordReader`:
return new FakeGzLineRecordReader(recordDelimiterBytes);
}

@Override
protected boolean isSplitable(JobContext context, Path file) {
return true; // plain text is splittable (as opposed to gzip)
}
}

事实上,我们必须更深入一层,同时替换默认的 LineRecordReader (Java) 和我们自己的(我们称它为 FakeGzLineRecordReader)。

由于LineRecordReader很难继承,我们可以复制LineRecordReader(在src/main/java/org/apache/hadoop/mapreduce/lib/input)并通过强制使用默认编解码器(纯文本)稍微修改(并简化)initialize(InputSplit genericSplit, TaskAttemptContext context) 方法:

(与原始 LineRecordReader 相比的唯一变化已给出解释正在发生的事情的评论)

package org.apache.hadoop.mapreduce.lib.input;

import java.io.IOException;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.Seekable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@InterfaceAudience.LimitedPrivate({"MapReduce", "Pig"})
@InterfaceStability.Evolving
public class FakeGzLineRecordReader extends RecordReader<LongWritable, Text> {
private static final Logger LOG =
LoggerFactory.getLogger(FakeGzLineRecordReader.class);
public static final String MAX_LINE_LENGTH =
"mapreduce.input.linerecordreader.line.maxlength";

private long start;
private long pos;
private long end;
private SplitLineReader in;
private FSDataInputStream fileIn;
private Seekable filePosition;
private int maxLineLength;
private LongWritable key;
private Text value;
private byte[] recordDelimiterBytes;

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

// This has been simplified a lot since we don't need to handle compression
// codecs.
public void initialize(
InputSplit genericSplit,
TaskAttemptContext context
) throws IOException {

FileSplit split = (FileSplit) genericSplit;
Configuration job = context.getConfiguration();
this.maxLineLength = job.getInt(MAX_LINE_LENGTH, Integer.MAX_VALUE);
start = split.getStart();
end = start + split.getLength();
final Path file = split.getPath();

final FileSystem fs = file.getFileSystem(job);
fileIn = fs.open(file);

fileIn.seek(start);
in = new UncompressedSplitLineReader(
fileIn, job, this.recordDelimiterBytes, split.getLength()
);
filePosition = fileIn;

if (start != 0) {
start += in.readLine(new Text(), 0, maxBytesToConsume(start));
}
this.pos = start;
}

// Simplified as input is not compressed:
private int maxBytesToConsume(long pos) {
return (int) Math.max(Math.min(Integer.MAX_VALUE, end - pos), maxLineLength);
}

// Simplified as input is not compressed:
private long getFilePosition() {
return pos;
}

private int skipUtfByteOrderMark() throws IOException {
int newMaxLineLength = (int) Math.min(3L + (long) maxLineLength,
Integer.MAX_VALUE);
int newSize = in.readLine(value, newMaxLineLength, maxBytesToConsume(pos));
pos += newSize;
int textLength = value.getLength();
byte[] textBytes = value.getBytes();
if ((textLength >= 3) && (textBytes[0] == (byte)0xEF) &&
(textBytes[1] == (byte)0xBB) && (textBytes[2] == (byte)0xBF)) {
LOG.info("Found UTF-8 BOM and skipped it");
textLength -= 3;
newSize -= 3;
if (textLength > 0) {
textBytes = value.copyBytes();
value.set(textBytes, 3, textLength);
} else {
value.clear();
}
}
return newSize;
}

public boolean nextKeyValue() throws IOException {
if (key == null) {
key = new LongWritable();
}
key.set(pos);
if (value == null) {
value = new Text();
}
int newSize = 0;
while (getFilePosition() <= end || in.needAdditionalRecordAfterSplit()) {
if (pos == 0) {
newSize = skipUtfByteOrderMark();
} else {
newSize = in.readLine(value, maxLineLength, maxBytesToConsume(pos));
pos += newSize;
}

if ((newSize == 0) || (newSize < maxLineLength)) {
break;
}

LOG.info("Skipped line of size " + newSize + " at pos " +
(pos - newSize));
}
if (newSize == 0) {
key = null;
value = null;
return false;
} else {
return true;
}
}

@Override
public LongWritable getCurrentKey() {
return key;
}

@Override
public Text getCurrentValue() {
return value;
}

public float getProgress() {
if (start == end) {
return 0.0f;
} else {
return Math.min(1.0f, (getFilePosition() - start) / (float)(end - start));
}
}

public synchronized void close() throws IOException {
try {
if (in != null) {
in.close();
}
} finally {}
}
}

关于scala - 如何强制 spark/hadoop 忽略文件上的 .gz 扩展名并将其读取为未压缩的纯文本?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49110384/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com