ORC源码阅读(1) - mapreduce module

ORC原来是作为Hive源码一部分的,先在独立出来成为Apache顶级项目,最新的Hive版本中也已经不再使用内置的ORC实现。但是在一些其他的系统,比如Presto中,依然有自己的Reader实现,但是和Apache的类似,只是代码风格和实现细节做了一些优化。我们项目打算基于最新的apache ORC源码做二次开发。

ORC源码也是Maven管理的,clone下来之后,用intellij打开java目录之后,就可以开始阅读了。ORC源码的工程结构比Carbondata简单多了,主要就是core和mapreduce这两个module。其中mapreduce module是Hadoop的InputFormat和OutPutFormat。就从这里开始看吧。

这个module下面有两个包,一个mapred,一个mapreduce分别是符合MapReduce V1和V2的Input/Output Format。我们就读读V2这个就好了,这下面就四个类:OrcInputFormat、OrcOutputFormat、OrcMapreduceRecordReader和OrcMapreduceRecordWriter。

OrcInputFormat与OrcOutputFormat

OrcInputFormat继承了FileInputFormat,getSplits用的就是FileInputFormat的实现,只是重写了createRecordReader方法。

  @Override
  public RecordReader
      createRecordReader(InputSplit inputSplit,
                         TaskAttemptContext taskAttemptContext
                         ) throws IOException, InterruptedException {
    FileSplit split = (FileSplit) inputSplit;
    Configuration conf = taskAttemptContext.getConfiguration();
    Reader file = OrcFile.createReader(split.getPath(),
        OrcFile.readerOptions(conf)
            .maxLength(OrcConf.MAX_FILE_LENGTH.getLong(conf)));
    return new OrcMapreduceRecordReader<>(file,
        org.apache.orc.mapred.OrcInputFormat.buildOptions(conf,
            file, split.getStart(), split.getLength()));
  }

这里面用到了org.apache.orc.Reader和org.apache.orc.OrcFile. 模板里面的V通常会是OrcStruct,Orc中一个表的schema可以表示为一个OrcStruct。
同样滴,OrcOutputFormat当中也是重写了craeteRecordWriter方法:

  @Override
  public RecordWriter
       getRecordWriter(TaskAttemptContext taskAttemptContext
                       ) throws IOException {
    Configuration conf = taskAttemptContext.getConfiguration();
    Path filename = getDefaultWorkFile(taskAttemptContext, EXTENSION);
    Writer writer = OrcFile.createWriter(filename,
        org.apache.orc.mapred.OrcOutputFormat.buildOptions(conf));
     return new OrcMapreduceRecordWriter(writer);
  }

这里面用到了org.apache.orc.Writer和org.apache.orc.OrcFile.
以上用到的这三个类都是core module里的,之后再去读。

OrcMapreduceRecordReader

这个类里面其实复用了org.apache.orc.mapred包下面的一些Writable类,这些Writable类用来在MapReduce中支持Orc中的一些特殊数据类型,比如Map、List、Struct等等,可以将这些数据类型从DataInput中反序列化出来,也可以将数据序列化到DataOutput中去。

从构造方法可以看到:

  public OrcMapreduceRecordReader(Reader fileReader,
                                  Reader.Options options) throws IOException {
    this.batchReader = fileReader.rows(options);
    if (options.getSchema() == null) {
      schema = fileReader.getSchema();
    } else {
      schema = options.getSchema();
    }
    this.batch = schema.createRowBatch();
    rowInBatch = 0;
    this.row = (V) OrcStruct.createValue(schema);
  }

其中batchReader是org.apache.orc.RecordReader类型的,这个RecordReader在core中。batch是真正有数据的地方。batch的类型是org.apache.hadoop.hive.ql.exec.vector.VectorizedRowBatch,从java doc可以看出来它是干嘛用的:

/**
 * A VectorizedRowBatch is a set of rows, organized with each column
 * as a vector. It is the unit of query execution, organized to minimize
 * the cost per row and achieve high cycles-per-instruction.
 * The major fields are public by design to allow fast and convenient
 * access by the vectorized query execution code.
 */

而schema是org.apache.orc.TypeDescription类型的,而TypeDescription其实是一个数据类型的描述,它有一个category,可以是INT、FLOAT等等,也可以是STRUCT。这里schema的category通常会是STRUCT。STRUCT和OrcStruct是对应的,是一个复合类型,其中可以包含其他类型的属性,用来表示一个表的schema。

row就是batchReader从batch中读出来的一行记录。

OrcMapreduceRecordReader中另外一个重要的方法是nextKeyValue:

  @Override
  public boolean nextKeyValue() throws IOException, InterruptedException {
    if (!ensureBatch()) {
      return false;
    }
    if (schema.getCategory() == TypeDescription.Category.STRUCT) {
      OrcStruct result = (OrcStruct) row;
      List children = schema.getChildren();
      int numberOfChildren = children.size();
      for(int i=0; i < numberOfChildren; ++i) {
        result.setFieldValue(i, OrcMapredRecordReader.nextValue(batch.cols[i], rowInBatch,
            children.get(i), result.getFieldValue(i)));
      }
    } else {
      OrcMapredRecordReader.nextValue(batch.cols[0], rowInBatch, schema, row);
    }
    rowInBatch += 1;
    return true;
  }

可以看出来,这个方法中,判断当schema是STRUCT的category时,将它看做一个表的schema,取出里面包含的children,即各个字段的TypeDescription,然后读取各个字段的值,存入row中。这其中复用了OrcMapredRecordReader.nextValue()方法.

OrcMapreduceRecordWriter

OrcMapreduceRecordWriter和OrcMapreduceRecordReader类似,只不过其中的batchReader换成了org.apache.orc
.Writer类型的writer。构造方法如下:

  public OrcMapreduceRecordWriter(Writer writer) {
    this.writer = writer;
    schema = writer.getSchema();
    this.batch = schema.createRowBatch();
    isTopStruct = schema.getCategory() == TypeDescription.Category.STRUCT;
  }

这个类的主要方法就是write方法:

  @Override
  public void write(NullWritable nullWritable, V v) throws IOException {
    // if the batch is full, write it out.
    if (batch.size == batch.getMaxSize()) {
      writer.addRowBatch(batch);
      batch.reset();
    }

    // add the new row
    int row = batch.size++;
    // skip over the OrcKey or OrcValue
    if (v instanceof OrcKey) {
      v = (V)((OrcKey) v).key;
    } else if (v instanceof OrcValue) {
      v = (V)((OrcValue) v).value;
    }
    if (isTopStruct) {
      for(int f=0; f < schema.getChildren().size(); ++f) {
        OrcMapredRecordWriter.setColumn(schema.getChildren().get(f),
            batch.cols[f], row, ((OrcStruct) v).getFieldValue(f));
      }
    } else {
      OrcMapredRecordWriter.setColumn(schema, batch.cols[0], row, v);
    }
  }

可以看出来,write先把V类型(通常是OrcStruct)的记录写入batch,如果batch写满了,就批量写入writer。

你可能感兴趣的:(数据库)