2021SC@SDUSC hbase代码分析(十一)HFile分析(3)

2021SC@SDUSC hbase源码分析(十一)HFile分析(3)

2021SC@SDUSC 2021SC@SDUSC
2021SC@SDUSC 2021SC@SDUSC

核心BlockType

Hbase中定义了8中BlockType,每种BlockType对应的Block都存储不同的内容,有的存储用户数据,有的存储索引数据,有的存储元数据(meta)。对于任意一种类型的HFileBlock,都拥有相同结构的BlockHeader,但是BlockData结构却不尽相同。

最核心的几种BlockType:
在这里插入图片描述

1.Trailer Block

简介

TrailerBlock主要记录了HFile的基本信息、各个部分的偏移值和寻址信息,下图为Trailer内存和磁盘中的数据结构,其中只显示了部分核心字段
在这里插入图片描述

RegionServer在打开HFile时会加载所有HFile的Trailer部分以及load-on-open部分到内存中。实际加载过程会首先会解析Trailer Block,然后再进一步加载load-on-open部分的数据,具体步骤如下:

  1. 加载HFile version版本信息,HBase中version包含majorVersion和minorVersion两部分,前者决定了HFile的主版本——V1、V2还是V3;后者在主版本确定的基础上决定是否支持一些微小修正,比如是否支持checksum等。不同的版本使用不同的文件解析器对HFile进行读取解析。
  2. HBase会根据version信息计算Trailer Block的大小(不同version的TrailerBlock大小不同),再根据Trailer Block大小加载整个HFileTrailer Block到内存中。Trailer Block中包含很多统计字段,例如,TotalUncompressedBytes表示HFile中所有未压缩的KeyValue总大小。NumEntries表示HFile中所有KeyValue总数目。Block中字段CompressionCodec表示该HFile所使用的压缩算法,HBase中压缩算法主要有lzo、gz、snappy、lz4等,默认为none,表示不使用压缩。
  3. Trailer Block中另两个重要的字段是LoadOnOpenDataOffset和LoadOnOpenDataSize,前者表示load-on-open Section在整个HFile文件中的偏移量,后者表示load-on-open Section的大小。根据此偏移量以及大小,HBase会在启动后将load-on-open Section的数据全部加载到内存中。load-on-open部分主要包括FileInfo模块、Root Data Index模块以及布隆过滤器Metadata模块,FileInfo是固定长度的数据块,主要记录了文件的一些统计元信息,比较重要的是AVG_KEY_LEN和AVG_VALUE_LEN,分别记录了该文件中所有Key和Value的平均长度。Root Data Index表示该文件数据索引的根节点信息,布隆过滤器Metadata记录了HFile中布隆过滤器的相关元数据。
代码分析

加载HFile version版本信息相关代码:

/**
 * The {@link HFile} format major version.
 */
private final int majorVersion;

/**
 * The {@link HFile} format minor version.
 */
private final int minorVersion;

FixedFileTrailer(int majorVersion, int minorVersion) {
  this.majorVersion = majorVersion;
  this.minorVersion = minorVersion;
  HFile.checkFormatVersion(majorVersion);
}

根据version信息计算Trailer Block的大小,再根据Trailer Block大小加载整个HFileTrailer Block到内存中。Trailer Block中包含很多统计字段:

@org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting
HFileProtos.FileTrailerProto toProtobuf() {
  HFileProtos.FileTrailerProto.Builder builder = HFileProtos.FileTrailerProto.newBuilder()
    .setFileInfoOffset(fileInfoOffset)
    .setLoadOnOpenDataOffset(loadOnOpenDataOffset)
    .setUncompressedDataIndexSize(uncompressedDataIndexSize)
    .setTotalUncompressedBytes(totalUncompressedBytes)
    .setDataIndexCount(dataIndexCount)
    .setMetaIndexCount(metaIndexCount)
    .setEntryCount(entryCount)
    .setNumDataIndexLevels(numDataIndexLevels)
    .setFirstDataBlockOffset(firstDataBlockOffset)
    .setLastDataBlockOffset(lastDataBlockOffset)
    .setComparatorClassName(getHBase1CompatibleName(comparatorClassName))
    .setCompressionCodec(compressionCodec.ordinal());
    /*
    字段CompressionCodec表示该HFile所使用的压缩算法,HBase中压缩算法主要有lzo、gz、snappy、lz4等,默认为none,表示不使用压缩。
    */
  if (encryptionKey != null) {
    builder.setEncryptionKey(UnsafeByteOperations.unsafeWrap(encryptionKey));
  }
  return builder.build();
}

获得MaxTrailerSize值,最大Trailer的大小

private static int getMaxTrailerSize() {
  int maxSize = 0;
  for (int version = HFile.MIN_FORMAT_VERSION; version <= HFile.MAX_FORMAT_VERSION; ++version) {
    maxSize = Math.max(getTrailerSize(version), maxSize);
  }
  return maxSize;
}

两个重要的字段是LoadOnOpenDataOffset和LoadOnOpenDataSize

void deserializeFromPB(DataInputStream inputStream) throws IOException {
  // read PB and skip padding
  int start = inputStream.available();
  HFileProtos.FileTrailerProto trailerProto =
    HFileProtos.FileTrailerProto.PARSER.parseDelimitedFrom(inputStream);
  int size = start - inputStream.available();
  inputStream.skip(getTrailerSize() - NOT_PB_SIZE - size);

  // process the PB
  if (trailerProto.hasFileInfoOffset()) {
    fileInfoOffset = trailerProto.getFileInfoOffset();
  }
  if (trailerProto.hasLoadOnOpenDataOffset()) {
    loadOnOpenDataOffset = trailerProto.getLoadOnOpenDataOffset();
  }
  if (trailerProto.hasUncompressedDataIndexSize()) {
    uncompressedDataIndexSize = trailerProto.getUncompressedDataIndexSize();
  }
  if (trailerProto.hasTotalUncompressedBytes()) {
    totalUncompressedBytes = trailerProto.getTotalUncompressedBytes();
  }
  if (trailerProto.hasDataIndexCount()) {
    dataIndexCount = trailerProto.getDataIndexCount();
  }
  if (trailerProto.hasMetaIndexCount()) {
    metaIndexCount = trailerProto.getMetaIndexCount();
  }
  if (trailerProto.hasEntryCount()) {
    entryCount = trailerProto.getEntryCount();
  }
  if (trailerProto.hasNumDataIndexLevels()) {
    numDataIndexLevels = trailerProto.getNumDataIndexLevels();
  }
  if (trailerProto.hasFirstDataBlockOffset()) {
    firstDataBlockOffset = trailerProto.getFirstDataBlockOffset();
  }
  if (trailerProto.hasLastDataBlockOffset()) {
    lastDataBlockOffset = trailerProto.getLastDataBlockOffset();
  }
  if (trailerProto.hasComparatorClassName()) {
    setComparatorClass(getComparatorClass(trailerProto.getComparatorClassName()));
  }
  if (trailerProto.hasCompressionCodec()) {
    compressionCodec = Compression.Algorithm.values()[trailerProto.getCompressionCodec()];
  } else {
    compressionCodec = Compression.Algorithm.NONE;
  }
  if (trailerProto.hasEncryptionKey()) {
    encryptionKey = trailerProto.getEncryptionKey().toByteArray();
  }
}

FileTrailerProto内容如下,这些信息就是存在trailer部分的内容:

// HFile file trailer
message FileTrailerProto {
  optional uint64 file_info_offset = 1;
  optional uint64 load_on_open_data_offset = 2;
  optional uint64 uncompressed_data_index_size = 3;
  optional uint64 total_uncompressed_bytes = 4;
  optional uint32 data_index_count = 5;
  optional uint32 meta_index_count = 6;
  optional uint64 entry_count = 7;
  optional uint32 num_data_index_levels = 8;
  optional uint64 first_data_block_offset = 9;
  optional uint64 last_data_block_offset = 10;
  optional string comparator_class_name = 11;
  optional uint32 compression_codec = 12;
  optional bytes encryption_key = 13;
}

2.Data Block

简介

Data Block是HBase中文件读取的最小单元。Data Block中主要存储用户的KeyValue数据,而KeyValue结构是HBase存储的核心。HBase中所有数据都是以KeyValue结构存储在HBase中。

内存和磁盘中的Data Block结构如图:
在这里插入图片描述

KeyValue由4个部分构成,分别为Key Length、Value Length、Key和Value。其中,Key Length和Value Length是两个固定长度的数值,Value是用户写入的实际数据,Key是一个复合结构,由多个部分构成:Rowkey、Column Family、Column Qualif ier、TimeStamp以及KeyType。其中,KeyType有四种类型,分别是Put、Delete、DeleteColumn和DeleteFamily。

由Data Block的结构可以看出,HBase中数据在最底层是以KeyValue的形式存储的,其中Key是一个比较复杂的复合结构。因为任意KeyValue中都包含Rowkey、Column Family以及ColumnQualif ier,因此这种存储方式实际上比直接存储Value占用更多的存储空间。这也是HBase系统在表结构设计时经常强调Rowkey、Column Family以及ColumnQualif ier尽可能设置短的根本原因。

代码分析

Key Length、Value Length、Key和Value相关配置的方法代码:

static void checkParameters(final byte [] row, final int rlength,
    final byte [] family, int flength, int qlength, int vlength)
        throws IllegalArgumentException {
  if (rlength > Short.MAX_VALUE) {
    throw new IllegalArgumentException("Row > " + Short.MAX_VALUE);
  }
  if (row == null) {
    throw new IllegalArgumentException("Row is null");
  }
  //家族长度
  flength = family == null ? 0 : flength;
  if (flength > Byte.MAX_VALUE) {
    throw new IllegalArgumentException("Family > " + Byte.MAX_VALUE);
  }
  // Qualifier length
  if (qlength > Integer.MAX_VALUE - rlength - flength) {
    throw new IllegalArgumentException("Qualifier > " + Integer.MAX_VALUE);
  }
  // Key长度
  long longKeyLength = getKeyDataStructureSize(rlength, flength, qlength);
  if (longKeyLength > Integer.MAX_VALUE) {
    throw new IllegalArgumentException("keylength " + longKeyLength + " > " +
        Integer.MAX_VALUE);
  }
  // Value长度
  if (vlength > HConstants.MAXIMUM_VALUE_LENGTH) { // FindBugs INT_VACUOUS_COMPARISON
    throw new IllegalArgumentException("Value length " + vlength + " > " +
        HConstants.MAXIMUM_VALUE_LENGTH);
  }
}

合并Row相关方法(只复制了其中的一个)

public int compareRows(byte [] left, int loffset, int llength,
    byte [] right, int roffset, int rlength) {
  int leftDelimiter = getDelimiter(left, loffset, llength,
      HConstants.DELIMITER);
  int rightDelimiter = getDelimiter(right, roffset, rlength,
      HConstants.DELIMITER);
  // Compare up to the delimiter
  int lpart = (leftDelimiter < 0 ? llength :leftDelimiter - loffset);
  int rpart = (rightDelimiter < 0 ? rlength :rightDelimiter - roffset);
  int result = Bytes.compareTo(left, loffset, lpart, right, roffset, rpart);
  if (result != 0) {
    return result;
  } else {
    if (leftDelimiter < 0 && rightDelimiter >= 0) {
      return -1;
    } else if (rightDelimiter < 0 && leftDelimiter >= 0) {
      return 1;
    } else if (leftDelimiter < 0 && rightDelimiter < 0) {
      return 0;
    }
  }
  // Compare middle bit of the row.
  // Move past delimiter
  leftDelimiter++;
  rightDelimiter++;
  int leftFarDelimiter = getDelimiterInReverse(left, leftDelimiter,
      llength - (leftDelimiter - loffset), HConstants.DELIMITER);
  int rightFarDelimiter = getDelimiterInReverse(right,
      rightDelimiter, rlength - (rightDelimiter - roffset),
      HConstants.DELIMITER);
  // Now compare middlesection of row.
  lpart = (leftFarDelimiter < 0 ? llength + loffset: leftFarDelimiter) - leftDelimiter;
  rpart = (rightFarDelimiter < 0 ? rlength + roffset: rightFarDelimiter)- rightDelimiter;
  result = super.compareRows(left, leftDelimiter, lpart, right, rightDelimiter, rpart);
  if (result != 0) {
    return result;
  }  else {
    if (leftDelimiter < 0 && rightDelimiter >= 0) {
      return -1;
    } else if (rightDelimiter < 0 && leftDelimiter >= 0) {
      return 1;
    } else if (leftDelimiter < 0 && rightDelimiter < 0) {
      return 0;
    }
  }
  // Compare last part of row, the rowid.
  leftFarDelimiter++;
  rightFarDelimiter++;
  result = Bytes.compareTo(left, leftFarDelimiter, llength - (leftFarDelimiter - loffset),
      right, rightFarDelimiter, rlength - (rightFarDelimiter - roffset));
  return result;
}

未完待续

未完待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值