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继承了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里的,之后再去读。
这个类里面其实复用了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和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。