首页 > 代码库 > hbase hfilev2

hbase hfilev2

HFileV2文件

HFileV2文件写入通过StoreFile.Writer-->HFileWriterV2进行写入。

文件格式通过hfile.format.version配置。默认为2,也只有2这个值在0.96可用。

可通过cf中配置DATA_BLOCK_ENCODING配置dataBlockencoding,

可配置值:NONE,PREFIX,DIFF,FAST_DIFF,PREFIX_TREE,

通过在family的配置属性中配置BLOCKSIZE,来设置hfileblock大小,默认为65536


通过在family的配置属性中配置BLOOMFILTER,来设置是否启用BLOOMFILTER,默认值为ROW,可选值:NONEROWROWCOL

如果io.storefile.bloom.enabled配置的值为true,默认为true.writer中生成一个全局的bloomfilterWriter

StoreFile.Writer中生成的generalBloomFilterWriter,实现类为:CompoundBloomFilterWriter,

bloomfilterblocksize通过io.storefile.bloom.block.size配置,默认为128*1024(128k)

如果bloomfilter属性不是ROWCOL时,同时io.storefile.delete.family.bloom.enabled配置为true,默认值为true,

StoreFile.Writer中生成的deleteFamilyBloomFilterWriter,实现类:CompoundBloomFilterWriter


writer.append操作


HFileV2文件时,在store进行flush时,会生成StoreFile.Writer实例,通过Writer.append写入kv.

publicvoid append(finalKeyValue kv) throws IOException {

如果是一个新的kv,也就是rowbloomfilter中的最后一个kvrow不相同,表示需要添加到bloomblock中。

此部分目前是在一个缓冲区中。

appendGeneralBloomfilter(kv);

如果kv是删除的KV,把row添加到deletebloomfilterblock中。

此部分目前是在一个缓冲区中。

appendDeleteFamilyBloomFilter(kv);

通过HFileWriterV2.append写入kvdatablock,

writer.append(kv);

trackTimestamps(kv);

}


HFileWriterV2.append(kv)直接调用如下方法:

privatevoid append(finallong memstoreTS, finalbyte[] key,

finalint koffset,finalint klength,

finalbyte[]value,finalintvoffset,finalintvlength)

throwsIOException {

检查key是否合法,首先检查上一个添加的key如果比当前的key大,表示有问题,因为hfile的写入需要排序写入。

如果当前的key比上次写入的key要小,返回值为false,如果返回值为true,表示两个key相同。我指的keyrowkey

booleandupKey = checkKey(key, koffset, klength);

检查value是否为null

checkValue(value, voffset,vlength);

如果rowkey与上一次的rowkey不是同一个key时,检查hfileblock是否超过了指定的大小。

如果当前的rowkey与上一次写入的rowkey相同时,

就算是block大小超过了指定的大小,相同的rowkeykv都会写到一个block中。

if(!dupKey) {

此处是检查fsBlockWriter中的大小是否超过了blocksize的大小,如果起过了。需要执行blockflush操作。

checkBlockBoundary();

}

第一次进行入时,fsBlockWriter的状态为State.INIT;此时需要生成一个新的block,并设置StateState.WRITING;

在执行newBlock操作时,生成一个DataOutputStream,使用一个baosInMemory(ByteArrayOutputStream)

每一个block中,basosInMemory的缓冲区是重用的,因此,每一个block中都会执行baosInMemory.reset操作。

并写入blockheader信息。

if(!fsBlockWriter.isWriting())

newBlock();


写入kvdatablock的缓冲区中。

//Write length of key and value and then actual key and value bytes.

//Additionally, we may also write down the memstoreTS.

{

DataOutputStream out =fsBlockWriter.getUserDataStream();

out.writeInt(klength);

totalKeyLength+= klength;

out.writeInt(vlength);

totalValueLength+= vlength;

out.write(key, koffset, klength);

out.write(value, voffset,vlength);

if(this.includeMemstoreTS){

WritableUtils.writeVLong(out,memstoreTS);

}

}

记录住此block的第一个key,firstkey主要是blockindex(leaf-level-index)记录每一个blockfirstkey.

//Are we the first key in this block?

if(firstKeyInBlock==null){

//Copy the key.

firstKeyInBlock=newbyte[klength];

System.arraycopy(key,koffset,firstKeyInBlock,0, klength);

}

记录最后一个key的值。

lastKeyBuffer= key;

lastKeyOffset= koffset;

lastKeyLength= klength;

entryCount++;

}


flush data block数据刷新

datablock的大小默认为65536(64k),当达到此值时,会对block进行flush操作。

HFileWriterV2中通过append会对block进行检查。

检查是否是新的一个rowkey的值,如果是检查是否需要flush当前的block,并重新创建一个新的block

boolean dupKey =checkKey(key, koffset, klength);

checkValue(value, voffset,vlength);

if(!dupKey) {

checkBlockBoundary();

}

检查是否达到flush的值,并进行flush操作。

privatevoid checkBlockBoundary()throwsIOException {

检查block是否达到指定的值。

if(fsBlockWriter.blockSizeWritten()<blockSize)

return;

datablock进行flush操作,

finishBlock();

写入索引数据到block中。

writeInlineBlocks(false);

生成一个新的block.

newBlock();

}


finishBlock方法:

privatevoid finishBlock()throwsIOException {

检查当前的fsBlockWriter的状态非State.WRITING;或者block中的值为0,不做操作。

if(!fsBlockWriter.isWriting()||fsBlockWriter.blockSizeWritten()== 0)

return;


longstartTimeNs = System.nanoTime();

//Update the first data block offset for scanning.

if(firstDataBlockOffset== -1) {

如果是第一个block,设置blockoffset的值为0,也就是block的开始位置。

firstDataBlockOffset=outputStream.getPos();

}

记录上一个block的偏移量。主要是用来记录blockindex的一些个准备信息。

outputStream是每次write一个blockpos的值就会增加。

//Update the last data block offset

lastDataBlockOffset=outputStream.getPos();

设置fsBlockWriter的状态为State.BLOCK_READY;这样就可以重新执行写入操作。

通过读取buffer中的kv的值,通过encoderblock进行操作。如profix_free等。会写入到一个buffer中。

最后把数据写入到HDFS文件中。

fsBlockWriter.writeHeaderAndData(outputStream);

intonDiskSize =fsBlockWriter.getOnDiskSizeWithHeader();


byte[]indexKey =comparator.calcIndexKey(lastKeyOfPreviousBlock,firstKeyInBlock);

把当前blockkey与当前block的偏移量,当前block的大小写入到leaflevel index(BlockIndex)中。

每一个block就会有一条blockindex记录。

dataBlockIndexWriter.addEntry(indexKey,lastDataBlockOffset,onDiskSize);

totalUncompressedBytes+=fsBlockWriter.getUncompressedSizeWithHeader();

HFile.offerWriteLatency(System.nanoTime()- startTimeNs);

是否需要写入kvcache中。如果是需要,写入到readcache中。

if(cacheConf.shouldCacheDataOnWrite()){

doCacheOnWrite(lastDataBlockOffset);

}

}




DataBlock的格式:

8byte

4byte

4byte

8byte

1byte

4byte

4byte

...

blockType

onDiskSize+checsumSize

unCompressedSize

prevOffset

checksumType

bytesPerChecksum

onDiskSize

data


BlockTypeblock类型

第二个是压缩部分下checksumsize的大小

第三部分是未压缩部分的大小

4部分是上一个block的偏移号

5部分是checksumtype的类型

6部分是是每个checksum的字节数,默认为16*1024

7部分是压缩部分的大小,但不包含checksunsize

最后是数据部分。



写入索引的block数据,要写入的索引包含如下几个:

blockIndex也就是dataBlockIndexWriter的默认实现是HFileBlockIndex.BlockIndexWriter.

BloomFilterIndex,也就是CompoundBloomFilterWriter实现。

DeleteBloomFilterIndex,也就是CompoundBloomFilterWriter实现。

privatevoid writeInlineBlocks(booleanclosing)throws IOException {

for(InlineBlockWriter ibw :inlineBlockWriters){

while(ibw.shouldWriteBlock(closing)) {

longoffset =outputStream.getPos();

booleancacheThisBlock = ibw.getCacheOnWrite();

ibw.writeInlineBlock(fsBlockWriter.startWriting(

ibw.getInlineBlockType()));

fsBlockWriter.writeHeaderAndData(outputStream);

ibw.blockWritten(offset,fsBlockWriter.getOnDiskSizeWithHeader(),

fsBlockWriter.getUncompressedSizeWithoutHeader());

totalUncompressedBytes+=fsBlockWriter.getUncompressedSizeWithHeader();


if(cacheThisBlock) {

doCacheOnWrite(offset);

}

}

}

}


1.blockIndexshouldWriteBlock主要检查大小(rootindex)是否大于128*1024(128kb),

2.bloomFilterIndexdeleteBloomFilterIndexshouldWriteBlock

只要bloomfilter中有值,也就是chunk中有数据,shouldWriteBlock的方法返回就为true,

block写入到HDFS中。

blockIndexblockTypeLEAF_INDEX,

bloomfilterblockTypeBLOOM_CHUNK


也就是说:

blockIndex中记录有每一个dataBlockfirstKey,offset,blockSize,

bloomFilterIndex中记录有每一个(row)rowkey,(rowcol)或者rowkeyQualifier,hash值,

此处的hash主要是bloomfilter的相关信息。


每一个dataBlock进行flush后,都会强制flushbloomfilterblock.

flushbloomfilter后,

会在rootBloomFilter(bloomBlockIndexWriter)的缓冲区中记录此bloomfliterfirstkey.offset,blocksize.


在每一个blockindex进行flush后,这个在datablock进行flush时不会强制flsuh,只有达到指定的值时,才进行flush.

在每一次对blockindex进行flush后,会在rootindex的缓冲区中记录住此blockindexfirstkey,offset,blocksize.


最后:

1.在执行writer.close时,写入rootindexblock

如果blockindex的大小超过了128k,会把rootindex的每128k写入一个INTERMEDIATE_INDEX

记录住所有的INTERMEDIATE_INDEXfirstkey,offset,blocksize,

此处是一个重复的迭代过程,只有当ROOT_INDEX。可以写入的blocksize小于128kb时,把最后一个写入为ROOT_INDEX

trailer中记录ROOTINDEXoffset.

2.接下来写入meta,也就是rootbloomfilter的信息。

3.写入FILE_INFO。会在trailer中记录住fileInfooffset.

4.写入trailer.


Fileinfo中包含:

MAX_SEQ_ID_KEY,记录hfile最大的seqid,

MAJOR_COMPACTION_KEY,是否做过majorcompaction

TIMERANGE,记录hfile中的timeRangeTracker.

EARLIEST_PUT_TS,hfile中最老的timestamp

DATA_BLOCK_ENCODING,记录hfileencoding的配置值

BLOOM_FILTER_TYPE,记录有全局的bloomfilter的类型

DELETE_FAMILY_COUNT,记录有deletefamily的个数。

Hfile.LASTKEY,记录此hfile中最后一个key的值,

hfile.AVG_KEY_LEN,记录key的平均长度。

Hfile.AVG_VALUE_LEN,记录value的平均长度。


Trailer中的内容:

majorVersion:hfile的版本号,固定的值2

minorVersion,hfile的最大版本号,3.

loadOnOpenDataOffsetdatablockrootindexoffset

fileInfoOffset,fileinfooffset,

numDataIndexLevels,rootindex的层级,在上面提到过的INTERMEDIATE_INDEX有几个层级。

UncompressedDataIndexSize,Uncompressedsize总大小。

firstDataBlockOffset,第一个blockoffset

lastDataBlockOffset,最后一个blockoffset.

ComparatorClassName,比较器的类名称。

dataIndexCountrootindex中存储的index个数。

.......