多行文本到单个地图

时间:2010-04-26 04:13:22

标签: hadoop mapreduce

我一直在尝试使用Hadoop将N行数发送到单个映射。我不需要拆分线路。

我尝试使用NLineInputFormat,但是每次将一行N行文本从数据发送到每个映射器[在第N行之后放弃]。

我试图设置该选项,它只需要N行输入一次一行地发送给每个地图:

    job.setInt("mapred.line.input.format.linespermap", 10);

我找到了一个邮件列表,建议我覆盖LineRecordReader :: next,但这并不是那么简单,因为内部数据成员都是私有的。

我刚检查了NLineInputFormat的源代码,并且硬编码LineReader,所以覆盖无效。

另外,顺便说一句,我正在使用Hadoop 0.18与Amazon EC2 MapReduce兼容。

3 个答案:

答案 0 :(得分:7)

您必须实施自己的输入格式。您也可以定义自己的记录阅读器。

不幸的是你必须定义一个getSplits()方法。在我看来,这将比实现记录阅读器更难:这种方法必须实现一个逻辑来输入数据。

请参阅以下摘录“Hadoop - 权威指南”(我一直推荐的一本好书!):

这是界面:

public interface InputFormat<K, V> {
  InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;
  RecordReader<K, V> getRecordReader(InputSplit split,
                                     JobConf job, 
                                     Reporter reporter) throws IOException;
}

JobClient调用getSplits()方法,传递所需数量的map任务 作为numSplits参数。这个数字被视为一个提示,因为InputFormat实现了 心理可以自由地将不同数量的分割返回到指定的数量 numSplits。在计算了拆分后,客户端将它们发送到jobtracker,即 使用他们的存储位置来安排地图任务,以便在任务工具上处理它们。

在任务跟踪器上,map任务将拆分传递给getRecordReader()方法 InputFormat获取该分割的RecordReader。 RecordReader只不过是 记录上的迭代器,map任务使用一个来生成记录键值对, 它传递给地图功能。代码段(基于MapRunner中的代码) 说明了这个想法:

K key = reader.createKey();
V value = reader.createValue();
while (reader.next(key, value)) {
  mapper.map(key, value, output, reporter);
} 

答案 1 :(得分:2)

我最近解决了这个问题,只需创建我自己的InputFormat来覆盖NLineInputFormat并实现自定义的MultiLineRecordReader而不是默认的LineReader。

我选择扩展NLineInputFormat,因为我想保证每次拆分都有N行。

此记录阅读器几乎与http://bigdatacircus.com/2012/08/01/wordcount-with-custom-record-reader-of-textinputformat/

一样

我修改的唯一内容是现在使用新API的maxLineLength的属性,以及从NLineInputFormat的NLINESTOPROCESS内部硬编码读取的setNumLinesPerSplit()的值(更多信息)柔韧性)。

结果如下:

public class MultiLineInputFormat extends NLineInputFormat{
    @Override
    public RecordReader<LongWritable, Text> createRecordReader(InputSplit genericSplit, TaskAttemptContext context) {
        context.setStatus(genericSplit.toString());
        return new MultiLineRecordReader();
    }

    public static class MultiLineRecordReader extends RecordReader<LongWritable, Text>{
        private int NLINESTOPROCESS;
        private LineReader in;
        private LongWritable key;
        private Text value = new Text();
        private long start =0;
        private long end =0;
        private long pos =0;
        private int maxLineLength;

        @Override
        public void close() throws IOException {
            if (in != null) {
                in.close();
            }
        }

        @Override
        public LongWritable getCurrentKey() throws IOException,InterruptedException {
            return key;
        }

        @Override
        public Text getCurrentValue() throws IOException, InterruptedException {
            return value;
        }

        @Override
        public float getProgress() throws IOException, InterruptedException {
            if (start == end) {
                return 0.0f;
            }
            else {
                return Math.min(1.0f, (pos - start) / (float)(end - start));
            }
        }

        @Override
        public void initialize(InputSplit genericSplit, TaskAttemptContext context)throws IOException, InterruptedException {
            NLINESTOPROCESS = getNumLinesPerSplit(context);
            FileSplit split = (FileSplit) genericSplit;
            final Path file = split.getPath();
            Configuration conf = context.getConfiguration();
            this.maxLineLength = conf.getInt("mapreduce.input.linerecordreader.line.maxlength",Integer.MAX_VALUE);
            FileSystem fs = file.getFileSystem(conf);
            start = split.getStart();
            end= start + split.getLength();
            boolean skipFirstLine = false;
            FSDataInputStream filein = fs.open(split.getPath());

            if (start != 0){
                skipFirstLine = true;
                --start;
                filein.seek(start);
            }
            in = new LineReader(filein,conf);
            if(skipFirstLine){
                start += in.readLine(new Text(),0,(int)Math.min((long)Integer.MAX_VALUE, end - start));
            }
            this.pos = start;
        }

        @Override
        public boolean nextKeyValue() throws IOException, InterruptedException {
            if (key == null) {
                key = new LongWritable();
            }
            key.set(pos);
            if (value == null) {
                value = new Text();
            }
            value.clear();
            final Text endline = new Text("\n");
            int newSize = 0;
            for(int i=0;i<NLINESTOPROCESS;i++){
                Text v = new Text();
                while (pos < end) {
                    newSize = in.readLine(v, maxLineLength,Math.max((int)Math.min(Integer.MAX_VALUE, end-pos),maxLineLength));
                    value.append(v.getBytes(),0, v.getLength());
                    value.append(endline.getBytes(),0, endline.getLength());
                    if (newSize == 0) {
                        break;
                    }
                    pos += newSize;
                    if (newSize < maxLineLength) {
                        break;
                    }
                }
            }
            if (newSize == 0) {
                key = null;
                value = null;
                return false;
            } else {
                return true;
            }
        }
    }

}

答案 2 :(得分:1)

我认为在你的情况下你可以遵循委托模式并实现一个围绕LineRecordReader的包装器,它覆盖必要的方法,即next()(或新API中的nextKeyValue()),将值设置为N行的串联,而不是一条线。

我用google搜索ParagraphRecordReader的示例性实现,它使用LineRecordReader逐行读取输入数据(并连接它),直到遇到EOF或空行。然后它返回pair,其中value是段落(而不是一行)。此外,ParagraphRecordReader的ParagraphInputFormat与标准TextInputFormat一样简单。

您可以找到此实施的必要链接以及以下帖子的几个词:http://hadoop-mapreduce.blogspot.com/2011/03/little-more-complicated-recordreaders.html

最佳