HFile解析 基于0.96
什么是HFile
HBase、BigTable以及其他分布式存储、查询系统的底层存储都采用SStable的思想,HBase的底层存储是HFile,他要解决的问题就是如果将内容存储到磁盘,以及如何高效的读取数据,HFile文件格式对特定字节存储什么内容做了规定,以便可以按照格式读取数据。
sstable就是sorted string table,存储的基本数据是key-value对,string意为着key和value都是以string/bytes的形式组织的,当然也可以是其他数据结构经序列化后的二进制流形式。
sorted意为数据的存储顺序是按照key的字符串的字典排列顺序升序组织的,并且对key构建了索引。检索时,把data index加载到内存中,通过二分查找,在data index中查找当前查询的key可能在哪个data block中,接着顺序遍历这个data block,找到key及value。
对HFile添加了布隆过滤器,这样可以过滤掉Table中不存在的key,(布隆过滤器有一定误判性,即将集合中本来不存在的数据,判断为存在,不过在这里还是可用的)
写HFile
流程
HFile问题分为写文件、读文件,而分析文件结构,得先看下写了什么,怎么写的:

1、写KeyValue之前一般会先new一个KeyValue;
分析 new KeyValue(keyBytes, "info".getBytes(), "city".getBytes(), valueBytes);
可以看到HBase会按照以下组织方式将KeyValue数据加载到内存中。
this.bytes = createByteArray(row, roffset, rlength,
family, foffset, flength, qualifier, qoffset, qlength,
timestamp, type, value, voffset, vlength);
KeyValue维护了一个byte数组,里面保存了这个KeyValue要写入HFile的全部信息。 /**
* Write KeyValue format into a byte array.
*
* @param row row key
* @param roffset row offset
* @param rlength row length
* @param family family name
* @param foffset family offset
* @param flength family length
* @param qualifier column qualifier
* @param qoffset qualifier offset
* @param qlength qualifier length
* @param timestamp version timestamp
* @param type key type
* @param value column value
* @param voffset value offset
* @param vlength value length
* @return The newly created byte array.
*/
private static byte [] createByteArray(final byte [] row, final int roffset,
final int rlength, final byte [] family, final int foffset, int flength,
final byte [] qualifier, final int qoffset, int qlength,
final long timestamp, final Type type,
final byte [] value, final int voffset, int vlength) {
checkParameters(row, rlength, family, flength, qualifier, qlength, value, vlength);
// Allocate right-sized byte array.
int keyLength = (int) getKeyDataStructureSize(rlength, flength, qlength);
byte [] bytes =
new byte[(int) getKeyValueDataStructureSize(rlength, flength, qlength, vlength)];
// Write key, value and key row length.
int pos = 0;
pos = Bytes.putInt(bytes, pos, keyLength);//4B的KeyLength
pos = Bytes.putInt(bytes, pos, vlength);//4B valueLength
pos = Bytes.putShort(bytes, pos, (short)(rlength & 0x0000ffff));//2B RowLength
pos = Bytes.putBytes(bytes, pos, row, roffset, rlength);//Row内容
pos = Bytes.putByte(bytes, pos, (byte)(flength & 0x0000ff));//1B FamilyLength
if(flength != 0) {
pos = Bytes.putBytes(bytes, pos, family, foffset, flength);
}//将famliy内容加载到bytes
if(qlength != 0) {
pos = Bytes.putBytes(bytes, pos, qualifier, qoffset, qlength);
}//将qualifer内容加载到bytes
pos = Bytes.putLong(bytes, pos, timestamp);//timestamp
pos = Bytes.putByte(bytes, pos, type.getCode());//type
if (value != null && value.length > 0) {
pos = Bytes.putBytes(bytes, pos, value, voffset, vlength);
}//value
return bytes;
}
可分析出KeyValue的这个byte数组,开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是2自己长度数据,表示Row的长度,紧接着是 Row,然后是1字节的数据,表示Family的长度,然后是Family的字节内容,接着是Qualifier,然后是两个固定长度的数值,表示(8B)Time Stamp和(1B)Key Type(Put/Delete)。Value就是纯粹的二进制数据。
2、指定压缩方式,块大小,写目录等参数,创建一个HFileWriterV2对象,写入KeyValue是调用HFileWriterV2 的append方法实现的
由于HFile的写入必须顺序写入,所以首先会checkKey做校验,写入过程会将上次写入的key保存在lastKeyBuffer,当前的key和上次写入的key作比较,保证(row,family,coqualifier,ts)顺序。
可以看到HBase写入HFile其实就是将KeyValue中的bytes相应内容写到HFile,最终的格式如下(memStoreTS):

在操作过程中,根据上面的offset以及length等可以定位到任何一部分内容。比如获取row --- Bytes(bytes,10,rlength),便可以对rowkey进行读操作,
上面操作并没有真正的写到磁盘上 (fsBlockWriter.getUserDataStream())是对ByteArrayOutputStream包装 ,write操作其实是写到了baosInMemory对象下面的buf字节数组里,而newblock()操作是对其构造参数添加了一个假的Header.
private void append(final long memstoreTS, final byte[] key,
final int koffset, final int klength, final byte[] value,
final int voffset, final int vlength) throws IOException {
boolean dupKey = checkKey(key, koffset, klength);//校验是否符合规则
if (!dupKey) {
checkBlockBoundary();
}//检查边界
if (!fsBlockWriter.isWriting())
newBlock();//第一次写入,会进行这个操作,之后在checkBlockBoundary()内执行此操作 {
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);
}
}
// Are we the first key in this block?
if (firstKeyInBlock == null) {
// Copy the key.
firstKeyInBlock = new byte[klength];
System.arraycopy(key, koffset, firstKeyInBlock, 0, klength);
}
lastKeyBuffer = key;//记录最后一个key
lastKeyOffset = koffset;
lastKeyLength = klength;
entryCount++;
}
2、随着不断地加入新的KeyValue对,当到调用checkBlockBoundary()检查当前DataBlock是否超过达一定的阀值(fsBlockWriter.blockSizeWritten() < blockSize),则执行finishBlock()等操作,
在finishBlock阶段:
取出写入byte[]的uncompressedBytesWithHeader = baosInMemory.toByteArray();数据
1、按照指定压缩方式,对DataBlock(KeyValue部分)进行编码和压缩
2、组装Header,循环冗余校验信息添加等操作,将这个块加入输出流
3、dataBlockIndexWriter.addEntry(indexKey, lastDataBlockOffset, onDiskSize);
添加索引项
跟踪 writeHeaderAndData()函数
/**
* Put the header into the given byte array at the given offset.
* @param onDiskSize size of the block on disk header + data + checksum
* @param uncompressedSize size of the block after decompression (but
* before optional data block decoding) including header
* @param onDiskDataSize size of the block on disk with header
* and data but not including the checksums
*/
private void putHeader(byte[] dest, int offset, int onDiskSize,
int uncompressedSize, int onDiskDataSize) {
offset = blockType.put(dest, offset);//DATABLK*(8B)
offset = Bytes.putInt(dest, offset, onDiskSize - HConstants.HFILEBLOCK_HEADER_SIZE);//在磁盘上block的大小(不包含头,主要用来处理压缩文件)
offset = Bytes.putInt(dest, offset, uncompressedSize - HConstants.HFILEBLOCK_HEADER_SIZE);//未压缩时block的大小(不包含头)
offset = Bytes.putLong(dest, offset, prevOffset);//前一个块的标识
offset = Bytes.putByte(dest, offset, checksumType.getCode());//校验和类型
offset = Bytes.putInt(dest, offset, bytesPerChecksum);
Bytes.putInt(dest, offset, onDiskDataSize);//
}
|  | 
分析出Header的结构,进而得出Data Block的结构如下:

3、上面提到每次finish block会添加一条索引记录,将这个块的firstKey,lastDataBlockOffset,onDiskDataSize加入LEAF_INDEX索引项,(索引大小大概(56+AvgKeySize)*NumBlocks)
索引记录内容如下:
/**
* Add one index entry to the current leaf-level block. When the leaf-level
* block gets large enough, it will be flushed to disk as an inline block.
*
* @param firstKey the first key of the data block
* @param blockOffset the offset of the data block
* @param blockDataSize the on-disk size of the data block ({@link HFile}
* format version 2), or the uncompressed size of the data block (
* {@link HFile} format version 1).
*/
public void addEntry(byte[] firstKey, long blockOffset, int blockDataSize)
{
curInlineChunk.add(firstKey, blockOffset, blockDataSize);
++totalNumEntries;
}
得出 Data Block的块索引项:
key=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZI/info:city/LATEST_TIMESTAMP/Put offset=2155038, dataSize=38135 key=aaaaaaaaaaaaaaaaabbbabbaaaabbbbaTU/info:city/LATEST_TIMESTAMP/Put offset=4343199, dataSize=38358 key=aaaaaaaaaaaaaaaabbbababbbbababaaxe1T70L5R6F2/info:city/LATEST_TIMESTAMP/Put offset=6539008, dataSize=38081 .....
|  | 
并将其加入curInlineChunk (BlockIndexChunk用来保存HFileBlockIndex的索引信息,此时索引类型为为 LEAF_INDEX)
当索引的内容达到一定阀值(默认HFileBlockIndex.DEFAULT_MAX_CHUNK_SIZE 128KB)或者关闭时,便会调用 writeInlineBlocks 方法通过InlineBlockWriter将之前累积的索引项写入输出流,
会在DataBlock之后写入一个Leaf index block,其写入过程和DataBlock基本一致,值得注意的是索引也是被压缩的。
LEAF_INDEX归根到底是一个InlineBlock,所以包含和DataBlock 结构一致的Header,写入过程参见:HFileBlockIndex.BlockIndexChunk.writeNonRoot(DataOutput out)方法
LEAF_INDEX主要包含:
- 索引项的个数;
- currentSecondaryIndexes //TODO 为了提高查询速度 
- 多个索引项,每个索引项在磁盘的顺序为
- lastDataBlockOffset
- onDiskDataSize
- firstKey
 
同时将LEAF_INDEX的信息加入ROOT_INDEX,rootChunk.add(firstKey, offset, onDiskSize, totalNumEntries);
可以看到ROOT_INDEX,的每一个索引项包含了firstKey, offset, onDiskSize, totalNumEntries等内容。
4、当写完之后,调用write的close方法,首先执行finishBlock()将剩余的数据加入输出流,并写InlineBlockIndex,添加ROOT_INDEX对这个InlineBlockIndex的索引项。
之后顺序添加metadata、rootIndex、metaBlockIndex、FileInfo、Trailer等信息。
我比较关心的是ROOT_INDEX的构造,是由HFlieBlockIndex.IndexBlockWriter.writeIndexBlock来完成:
while (rootChunk.getRootSize() > maxChunkSize) {
    rootChunk = writeIntermediateLevel(out, rootChunk);
    numLevels += 1;
}
发现如果root_index的size达到一定阀值(128K),便会加一级索引,达到3级;
之后设置 trailer.setLoadOnOpenOffset(rootIndexOffset);从这里我们便可以知道如果读取索引必须先读取Trailer。
HFile的fix trailer 包含了指向其它块的offsets信息
各个模块的内容:
执行 bin/hbase org.apache.hadoop.hbase.io.hfie.HFile -m -f /opt/local/hbase/uuid_rtgeo/06faaf5e0ea384d277061bf00757710e/info/a596111cbc8f4e1b96da230fd588f8a5
| Block index size as per heapsize: 1528reader=/opt/local/hbase/uuid_rtgeo/06faaf5e0ea384d277061bf00757710e/info/a596111cbc8f4e1b96da230fd588f8a5,    compression=lzo,    cacheConf=CacheConfig:enabled [cacheDataOnRead=true] [cacheDataOnWrite=false] [cacheIndexesOnWrite=false] [cacheBloomsOnWrite=false] [cacheEvictOnClose=false] [cacheCompressed=false],    firstKey=F40000408A862DC6426C8E32B0F9971AAAA7B386C2F43FD841B40A362EA09259/info:recent/1385032809401/Put,    lastKey=F4FCC94050C968B4CFDE49EA4F4A1F185FB1D72D7346005A227A40A9A7358BC8/info:recent/1382710394100/Put,    avgKeyLen=85,    avgValueLen=68,    entries=4186248,    length=142479945Trailer:    fileinfoOffset=142479169,    loadOnOpenDataOffset=142478318,    dataIndexCount=9,    metaIndexCount=0,    totalUncomressedBytes=683010319,    entryCount=4186248,    compressionCodec=LZO,    uncompressedDataIndexSize=1060555,    numDataIndexLevels=2,    firstDataBlockOffset=0,    lastDataBlockOffset=142336376,    comparatorClassName=org.apache.hadoop.hbase.KeyValue$KeyComparator,    majorVersion=2,    minorVersion=0Fileinfo:    BLOOM_FILTER_TYPE = ROW    DATA_BLOCK_ENCODING = NONE    DELETE_FAMILY_COUNT = \x00\x00\x00\x00\x00\x00\x00\x00    EARLIEST_PUT_TS = \x00\x00\x01A\x9D\x18\xE6\x8E    LAST_BLOOM_KEY = F4FCC94050C968B4CFDE49EA4F4A1F185FB1D72D7346005A227A40A9A7358BC8    MAJOR_COMPACTION_KEY = \xFF    MAX_SEQ_ID_KEY = 304738752    TIMERANGE = 1381320156814....1386635121792    hfile.AVG_KEY_LEN = 85    hfile.AVG_VALUE_LEN = 68    hfile.LASTKEY = \x00@F4FCC94050C968B4CFDE49EA4F4A1F185FB1D72D7346005A227A40A9A7358BC8\x04inforecent\x00\x00\x01A\xEF\xF6<\xF4\x04Mid-key: \x00@F47AB33AB35809CB16CEE830C04F5A7FB530C4E6AC75EAB8C67E4ABD7F488100\x04inforecent\x00\x00\x01B(q2)\x04\x00\x00\x00\x00\x04:h*\x00\x005lBloom filter:    BloomSize: 131072    No of Keys in bloom: 94949    Max Keys forbloom: 109306    Percentage filled: 87%    Number of chunks: 1    Comparator: ByteArrayComparatorDelete Family Bloom filter:    Not present | 

之于HFileV1
V1:
HFileV2相对于V1:主要做了几个改进,其中2个主个改进都主要是为了降低内存使用和启动时间,思路都是将它们切分为多个block。
- 增加了树状结构的数据块索引(data block index)支持。原因是在数据块的索引很大时,很难全部load到内存,比如当前的一个data block会在data block indxe区域对应一个数据项,假设每个block 64KB,每个索引项64Byte,这样如果每条机器上存放了6TB数据,那么索引数据就得有6GB,因此这个占用的内存还是很高的。通过对这些索引以树状结构进行组织,只让顶层索引常驻内存,其他索引按需读取并通过LRU cache进行缓存,这样就不需要将全部索引加载到内存。参见https://issues.apache.org/jira/browse/HBASE-3856。
 相当于把原先平坦的索引结构以树状的结构进行分散化的组织。现在的index block也是与data block一样是散布到整个文件之中,而不再是单纯的在结尾处。同时为支持对序列化数据进行二分查找,还为非root的block index设计了新的"non-root index block"。
 具体实现来看,比如在写入HFile时,在内存中会存放当前的inline block index,当inline block index大小达到一定阈值(默认128KB)时就直接flush到磁盘,而不再是最后做一次flush,这样就不需要在内存中一直保持所有的索引数据。当所有的inline block index生成之后,HFile writer会生成更上一级的block index,它里面的内容就是这些inline block index的offset,依次递归,逐步生成更上层的block index,上层的包含的就是下层的offset,直到最顶层大小小于阈值时为止。所以整个过程就是自底向上的通过下层index block逐步构建出上层index block。
- 之前的bloom filter数据是存放在单独的一个meta block里,新版里它将可以被存为多个block。这就允许在处理一个查询时不必将所有的数据都load到内存。
- 对压缩数据的支持,记录compressed size可以在扫描HFile时不用解压,直接skip过当前块。
读HFile
读操作比较简单,在创建reader的时候会先加载trailer,seek的过程中首先会根据各级BlockIndex通过二分查找,找到key所在的HFileBlock,之后判断HFileBlock是否在内存,不在则将HFileBlock加载到内存,之后顺序扫描这个块,进而seek到对应的key。

在创建Reader时会首先加载trailler,并根据trailer加载rootBlockIndex,FileInfo等信息,
读取Trailer,首先将磁盘上Trailer加载到内存,之后按照Trailer固定的格式解析出Trailer的内容
trailer = FixedFileTrailer.readFromStream(fsdis.getStream(isHBaseChecksum), size);
public static FixedFileTrailer readFromStream(FSDataInputStream istream,
long fileSize) throws IOException {
int bufferSize = MAX_TRAILER_SIZE;
long seekPoint = fileSize - bufferSize; //trailer 位置
if (seekPoint < 0) {
// It is hard to imagine such a small HFile.
seekPoint = 0;
bufferSize = (int) fileSize;
}
//加载trailer
istream.seek(seekPoint);
ByteBuffer buf = ByteBuffer.allocate(bufferSize);
istream.readFully(buf.array(), buf.arrayOffset(),
buf.arrayOffset() + buf.limit());
.....
FixedFileTrailer fft = new FixedFileTrailer(majorVersion, minorVersion);
fft.deserialize(new DataInputStream(new ByteArrayInputStream(buf.array(),
buf.arrayOffset() + bufferSize - trailerSize, trailerSize)));
return fft;
}
trailer内容:
fileinfoOffset=43649909, loadOnOpenDataOffset=43649226, dataIndexCount=20, metaIndexCount=3, totalUncomressedBytes=78307153, entryCount=600000, compressionCodec=GZ, uncompressedDataIndexSize=2608931, numDataIndexLevels=2, firstDataBlockOffset=0, lastDataBlockOffset=43613833, comparatorClassName=org.apache.hadoop.hbase.KeyValue$KeyComparator, majorVersion=2, minorVersion=3
读取trailer之后便可以获取rootIndex的位置,加载rootIndex size=2
key=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZI/info:city/LATEST_TIMESTAMP/Put offset=3944715, dataSize=38949 key=aaaaaaaaaaaaaaaabbbaaabbaabaabbaiSje/info:city/LATEST_TIMESTAMP/Put offset=6825203, dataSize=27845
获取rootLevel的索引
int rootLevelIndex = rootBlockContainingKey(key, keyOffset, keyLength);
查找的过程实质上就是对各级索引进行二分查找,并根据BlockType来判断块是索引还是Data已决定是否需要继续向下寻找。
int pos = Bytes.binarySearch(blockKeys, key, offset, length, comparator);
public static int binarySearch(byte [][]arr, byte []key, int offset,
int length, RawComparator<?> comparator) {
int low = 0;
int high = arr.length - 1;
while (low <= high) {
int mid = (low+high) >>> 1;
// we have to compare in this order, because the comparator order
// has special logic when the 'left side' is a special key.
int cmp = comparator.compare(key, offset, length,
arr[mid], 0, arr[mid].length);
// key lives above the midpoint
if (cmp > 0)
low = mid + 1;
// key lives below the midpoint
else if (cmp < 0)
high = mid - 1;
// BAM. how often does this really happen?
else
return mid;
}
return - (low+1);
}
|  | 
进而获取下一级索引的offset等信息,
接着继续向下寻找,直到
if (block.getBlockType().equals(BlockType.DATA)
即查找的块是一个DataBlock时结束;
之后 loadBlockAndSeekToKey ,然后块内查找:
BlockWithScanInfo初始化会产生一个ByteBuffer对象,是去掉了头的block块,之后在block内的查找操作均在ByteBuffer中进行.
| /**  * Returns a buffer that does not include the header. The array offset points  * to the start of the block data right after the header. The underlying data  * array is not copied. Checksum data is not included in the returned buffer.  *  * @return the buffer with header skipped  */ publicByteBuffer getBufferWithoutHeader() {   returnByteBuffer.wrap(buf.array(), buf.arrayOffset() + headerSize(),       buf.limit() - headerSize() - totalChecksumBytes()).slice(); } | 
值得注意的是:如果开启cache则首先会 HFileBlock cachedBlock = (HFileBlock) cacheConf.getBlockCache().getBlock(cacheKey, cacheBlock, useLock)从cache中获取块;
如果没有则load:HFileBlock hfileBlock = fsBlockReader.readBlockData(dataBlockOffset, onDiskBlockSize, -1,pread);
并加入BlockCache
| // Cache the block if necessary  if(cacheBlock && cacheConf.shouldCacheBlockOnRead(hfileBlock.getBlockType().getCategory())) {       cacheConf.getBlockCache().cacheBlock(cacheKey, hfileBlock, cacheConf.isInMemory());  } | 
0.96中Cache包含:BuketCache(性能不错,值得推广,后续会仔细研究下),LRUBlockCache,SlabCache,DoubleBlockCache等。
//TODO HBase中的Cache可以作为一个切入点来研究下,CacheConfig类记录了配置Cache的详细参数及默认值。
blockSeek(key, offset, length, seekBefore)函数其实就是做了一个顺序查找,seek到指定位置。
| privateintblockSeek(byte[] key, intoffset, intlength,    booleanseekBefore) {  intklen, vlen;  longmemstoreTS = 0;  intmemstoreTSLen = 0;  intlastKeyValueSize = -1;//循环扫描block 查找key  do{    blockBuffer.mark();    klen = blockBuffer.getInt();//key的长度    vlen = blockBuffer.getInt();//value的长度    blockBuffer.reset();    if(this.reader.shouldIncludeMemstoreTS()) {      if(this.reader.decodeMemstoreTS) {        try{          intmemstoreTSOffset = blockBuffer.arrayOffset()              + blockBuffer.position() + KEY_VALUE_LEN_SIZE + klen + vlen;          memstoreTS = Bytes.readVLong(blockBuffer.array(),              memstoreTSOffset);          memstoreTSLen = WritableUtils.getVIntSize(memstoreTS);        } catch(Exception e) {          thrownewRuntimeException("Error reading memstore timestamp", e);        }      } else{        memstoreTS = 0;        memstoreTSLen = 1;      }    }    intkeyOffset = blockBuffer.arrayOffset() + blockBuffer.position()        + KEY_VALUE_LEN_SIZE;    intcomp = reader.getComparator().compareFlatKey(key, offset, length,        blockBuffer.array(), keyOffset, klen); //比较    if(comp == 0) {      if(seekBefore) {        if(lastKeyValueSize < 0) {          thrownewIllegalStateException("blockSeek with seekBefore "              + "at the first key of the block: key="              + Bytes.toStringBinary(key) + ", blockOffset="              + block.getOffset() + ", onDiskSize="              + block.getOnDiskSizeWithHeader());        }        blockBuffer.position(blockBuffer.position() - lastKeyValueSize);        readKeyValueLen();        return1; // non exact match.      }      currKeyLen = klen;      currValueLen = vlen;      if(this.reader.shouldIncludeMemstoreTS()) {        currMemstoreTS = memstoreTS;        currMemstoreTSLen = memstoreTSLen;      }      return0; // indicate exact match    } elseif(comp < 0) {      if(lastKeyValueSize > 0)        blockBuffer.position(blockBuffer.position() - lastKeyValueSize);      readKeyValueLen();      if(lastKeyValueSize == -1&& blockBuffer.position() == 0          && this.reader.trailer.getMinorVersion() >= MINOR_VERSION_WITH_FAKED_KEY) {        returnHConstants.INDEX_KEY_MAGIC;      }      return1;    }    // The size of this key/value tuple, including key/value length fields.    lastKeyValueSize = klen + vlen + memstoreTSLen + KEY_VALUE_LEN_SIZE;    blockBuffer.position(blockBuffer.position() + lastKeyValueSize);  } while(blockBuffer.remaining() > 0);//扫描到块末尾时结束  blockBuffer.position(blockBuffer.position() - lastKeyValueSize);  readKeyValueLen();  return1; // didn't exactly find it.} | 
seek 到之后,进而获取value
public KeyValue getKeyValue() {
if (!isSeeked())
return null;
KeyValue ret = new KeyValue(blockBuffer.array(),
blockBuffer.arrayOffset() + blockBuffer.position(),
KEY_VALUE_LEN_SIZE + currKeyLen + currValueLen,
currKeyLen);
if (this.reader.shouldIncludeMemstoreTS()) {
ret.setMvccVersion(currMemstoreTS);
}
return ret;
}
这里用到比较关键的类包含ByteBuffer的各种操作,比较重要。
过程中关键类:
- ByteBuffer :block在内存的载体
- HFileReaderV2:包含对HFile进行读写操作的各种方法
- Bytes :块内二分查找方法
- HFileScanner 由reader构造出来,负责读取HFile操作
- HFileBlockIndex 记录块的索引信息,如每个块的起始key,midKey等信息
- BlockCache、BlockCacheKey
- FixedFileTrailer
- HFileBlock block对象
- ByteArrayOutputStream 
阅读HBase代码时maven依赖hadoop源码无法查看,解决办法:1、下载hadoop-1.1.2.tar.gz 2、找到其源码包,打包jar,buildpath添加、刷新即可
思考
1、如果建表时,一个row对应多个列,或者多个版本,那么一个HFileBlock所能存放的row数就会越少,这样一来块内seek速度会变快,但是对于按顺序查找或者扫描同样个数的row来说,需要不停地load块,反而效率变低。
2、扫描操作会不停地将块加到缓存,如果多个进行扫描缓存块,而只有一个进程来淘汰块,那么很可能会发生内存溢出,所以在线下计算需要大量扫描的情况下,最好能关闭cache.
目前默认缓存策略为LRU
1.一个Block从被缓存至被淘汰,基本就伴随着Heap中的位置从New区晋升到Old区 
2.晋升在Old区的Block被淘汰后,最终由CMS进行垃圾回收,随之带来的是Heap碎片 
3.因为碎片问题,随之而来的是GC时晋升失败的FullGC。  
4.对于高频率的,如果缓存的速度比淘汰的速度快,有OOM的风险
3、StoreFile过多会影响读性能,因为一个读操作很可能会需要打开多个HFile,io次数太多,会影响性能
4、HFile block越大越适合顺序扫描(大的快解压速度会变慢)但是随机读写性能降低,小的HFile block更加适合随机读写,但是需要更多地内存来存放index.默认(64 * 1024B)
5、HFileV2的分级索引,虽然可以减少内存的使用量,加快启动速度,但是多了1-2次IO,尤其是HFile文件较大时,随机读很可能比V1要多2次IO操作,进而读取速度变慢。
6、gz压缩方式比lzo好,但是压缩和解压时消耗的CPU大于lzo
参考:
http://hbase.apache.org/book/hfilev2.html
http://duanple.blog.163.com/blog/static/709717672011916103311428/
HFile解析 基于0.96的更多相关文章
- String,StringBuffer和StringBuilder源码解析[基于JDK6]
		最近指导几位新人,学习了一下String,StringBuffer和StringBuilder类,从反馈的结果来看,总体感觉学习的深度不够,没有读出东西.其实,JDK的源码是越读越有味的.下面总结一下 ... 
- 【甘道夫】HBase(0.96以上版本号)过滤器Filter具体解释及实例代码
		说明: 本文參考官方Ref Guide,Developer API和众多博客.并结合实測代码编写.具体总结HBase的Filter功能,并附上每类Filter的对应代码实现. 本文尽量遵从Ref Gu ... 
- HBase(0.96以上版本)过滤器Filter详解及实例代码
		说明: 本文参考官方Ref Guide,Developer API和众多博客,并结合实测代码编写,详细总结HBase的Filter功能,并附上每类Filter的相应代码实现. 本文尽量遵从Ref Gu ... 
- Hbase 0.96 比 hbase 0.94的改变
		转载:http://blog.csdn.net/hxpjava1/article/details/20043703 环境: hadoop:hadoop-2.2.0 hbase:hbase-0.96.0 ... 
- 灵感手环第一步——0.96寸OLED显示实验
		这算是我这个系列的第一篇博客吧.首先要解决的就是屏幕显示问题.我选择了目前新兴起的OLED显示模块. OLED(OrganicLightEmittingDiode),中文译作有机发光二极管,目前被广泛 ... 
- netty源码解解析(4.0)-11 Channel NIO实现-概览
		结构设计 Channel的NIO实现位于io.netty.channel.nio包和io.netty.channel.socket.nio包中,其中io.netty.channel.nio是抽象实 ... 
- netty源码解解析(4.0)-10 ChannelPipleline的默认实现--事件传递及处理
		事件触发.传递.处理是DefaultChannelPipleline实现的另一个核心能力.在前面在章节中粗略地讲过了事件的处理流程,本章将会详细地分析其中的所有关键细节.这些关键点包括: 事件触发接口 ... 
- Hadoop 2.2 & HBase 0.96 Maven 依赖总结
		由于Hbase 0.94对Hadoop 2.x的支持不是非常好,故直接添加Hbase 0.94的jar依赖可能会导致问题. 但是直接添加Hbase0.96的依赖,由于官方并没有发布Hbase 0.96 ... 
- netty源码解解析(4.0)-17 ChannelHandler: IdleStateHandler实现
		io.netty.handler.timeout.IdleStateHandler功能是监测Channel上read, write或者这两者的空闲状态.当Channel超过了指定的空闲时间时,这个Ha ... 
随机推荐
- 在这个年纪,打DOTA冲分有那么可笑么?
			大学四年,毕业两年,6年dota,总是在最无助的时候让心灵得到一丝安宁的东西,烟和dota. 我不知道dota对别人的意义是什么,一盘dota,在最多不超过1个半小时的时间里,仿佛经历了一个小人生,每 ... 
- bzoj 1109 [POI2007]堆积木Klo(LIS)
			[题意] n个数的序列,删除一个数后序列左移,求最后满足i==a[i]的最大个数. [思路] 设最终得到a[i]==i的序列为s,则s应满足: i<j,a[i]<a[j],i-a[i]&l ... 
- 未能加载Connector/NET :: v6.7.4
			//从*.config文件获取连接字符串和提供程序 string dp = ConfigurationManager.AppSettings["provider"]; string ... 
- Python相关工具清单[持续更新]
			SublimeJEDI : awesome Python autocompletion with SublimeText. Awesome Python : A curated list of awe ... 
- 产品经理如何赢得开发人员的尊重和支持?-摘自infoq
			对于产品经理来说,赢得开发人员的尊重和支持,从某种意义上讲,是产品迈向成功的坚实一步.最近,知乎社区上的开发人员和管理者在前.后两个帖子中对此展开了激烈的讨论,其中不乏真知灼见. 林志霖Cray认为产 ... 
- Spark计算工作流
			下图 中描述了 Spark 的输入.运行转换.输出.在运行转换中通过算子对 RDD进行转换.算子是 RDD 中定义的函数,可以对 RDD 中的数据进行转换和操作. 输入:在 Spark 程序运行中, ... 
- html5 标签
			按字母顺序排列的标签列表 4: 指示在 HTML 4.01 中定义了该元素 5: 指示在 HTML 5 中定义了该元素 标签 描述 4 5 <!--...--> 定义注释. 4 5 < ... 
- Struts2在Action中访问WEB资源
			什么是WEB资源? 这里所说的WEB资源是指:HttpServletRequest, HttpSession, ServletContext 等原生的 Servlet API. 为什么需要访问WEB资 ... 
- PLSQL Developer 常用设置及快捷键
			1.登录后自动选中My Objects(已验证可用) 默认情况下,PLSQL Developer登录后,Brower里会选择all Objects,如果你登录的用户是DBA, 要展开tables目录, ... 
- keil中编译时出现*** ERROR L107: ADDRESS SPACE OVERFLOW
			解决方法: http://zhidao.baidu.com/link?url=DWTVVdALVqPtUt0sWPURD6c1eEppyu9CXocLTeRZlZlhwHOA1P1xdesqmUQNw ... 
