avro文件存储格式

994 阅读2分钟

avro是一种二进制数据编码格式,将一个类对象或者在说一种格式的数据转化二进制编码,可以将其写为文件,也可以存在于内存中,我们分析一下它在内存中的存储格式。

image.png

如图,在文件中存储时首先分为 header和datablock,文件由一个header和多个datablock组成,avro是行存顺序存储数据的,且它没有索引,所以它采用多个datablock的方式来存储数据,header和datablock之间都是使用一个Sync Marker分开标识的,这个Sync Marker一般为16字节,在写入时avro提供的api会自动在写入固定长度的数据后结束调当前的block,并写入Sync Marker,也可以手动的调用方法结束当前写入的block并将数据刷入磁盘。

在读取时,显示提供的api基本只允许你顺序读取数据,但是他会提供一个方法允许你使用一个sync方法来找到一个文件中一个偏移量之后最近的一个同步点

/**
 * Move to the next synchronization point after a position. To process a range
 * of file entires, call this with the starting position, then check
 * {@link #pastSync(long)} with the end point before each call to
 * {@link #next()}.
 */
@Override
public void sync(long position) throws IOException {
  seek(position);
  // work around an issue where 1.5.4 C stored sync in metadata
  if ((position == 0) && (getMeta("avro.sync") != null)) {
    initialize(sin); // re-init to skip header
    return;
  }
  try {
    //这里i为syncBuffer中起始位置的偏移量
    //这里采用了循环存储的方式
    int i = 0, b;
    InputStream in = vin.inputStream();
    //读取固定长度到syncBuffer中去
    vin.readFixed(syncBuffer);
    do {
      int j = 0;
      //遍历验证syncBuffer是否与Header中的sync相同
      for (; j < SYNC_SIZE; j++) {
        if (getHeader().sync[j] != syncBuffer[(i + j) % SYNC_SIZE])
          break;
      }
      //如果遍历的长度等于SYNC_SIZE,这说明匹配到一个完整的sync,将blockStart置为sync后的位置
      if (j == SYNC_SIZE) { // matched a complete sync
        blockStart = position + i + SYNC_SIZE;
        return;
      }
      //否则读取一个字节到b中
      b = in.read();
      //将b设置到syncBuffer中最前面需要丢弃的字节的位置上,然后i++
      syncBuffer[i++ % SYNC_SIZE] = (byte) b;
    } while (b != -1);
  } catch (EOFException e) {
    // fall through
  }
  // if no match or EOF set start to the end position
  blockStart = sin.tell();
  // System.out.println("block start location after EOF: " + blockStart );
}

总之这里通过一个暴力搜索的方式 匹配到一个sync 然后将偏移量设置到这里

/**
 * Move to a specific, known synchronization point, one returned from
 * {@link DataFileWriter#sync()} while writing. If synchronization points were
 * not saved while writing a file, use {@link #sync(long)} instead.
 */
public void seek(long position) throws IOException {
  sin.seek(position);
  vin = DecoderFactory.get().binaryDecoder(this.sin, vin);
  datumIn = null;
  blockRemaining = 0;
  blockStart = position;
}

其中seek方法会将sin流偏移量置为position,然后这里是使用sin生成一个vin流?

所以说这里我们要注意 比如一个文件中在1000偏移量开启一个新的block,我们希望从这里开始读取,那么我们必须sync(1000-16),因为从1000-16的位置开始,才能匹配到这个block之前的sync点,然后将偏移量移动到1000,如果我们调用sync(1000),则只能找到下一个block

/** Return true if past the next synchronization point after a position. */
@Override
public boolean pastSync(long position) throws IOException {
  return ((blockStart >= position + SYNC_SIZE) || (blockStart >= sin.length()));
}

pastSync用来判断当前读取的位置是否已经超过给定的偏移量,比如0到984结束一个block,然后985到1000是sync,那么我们只要读取到984之前的这一个block,那么我们就需要将position设置为984,并在每次读取时调用pastSync判断一下,如果返回true则表示已经超过需要读取的位置了,这里position设置成大于984的值比如1000,则会造成多读数据的问题。

header分为 magic number:固定为"Obj1" metaData:一些信息入schema信息,压缩格式等 Sync Marker

block包括 记录数 数据长度 数据 Sync Marker