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部分的数据,具体步骤如下:
- 加载HFile version版本信息,HBase中version包含majorVersion和minorVersion两部分,前者决定了HFile的主版本——V1、V2还是V3;后者在主版本确定的基础上决定是否支持一些微小修正,比如是否支持checksum等。不同的版本使用不同的文件解析器对HFile进行读取解析。
- 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,表示不使用压缩。
- 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;
}
未完待续
未完待续