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 for bloom: 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 */ public ByteBuffer getBufferWithoutHeader() { return ByteBuffer.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到指定位置。
private int blockSeek(byte[] key, int offset, int length, boolean seekBefore) { int klen, vlen; long memstoreTS = 0; int memstoreTSLen = 0; int lastKeyValueSize = -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 { int memstoreTSOffset = blockBuffer.arrayOffset() + blockBuffer.position() + KEY_VALUE_LEN_SIZE + klen + vlen; memstoreTS = Bytes.readVLong(blockBuffer.array(), memstoreTSOffset); memstoreTSLen = WritableUtils.getVIntSize(memstoreTS); } catch (Exception e) { throw new RuntimeException("Error reading memstore timestamp", e); } } else { memstoreTS = 0; memstoreTSLen = 1; } } int keyOffset = blockBuffer.arrayOffset() + blockBuffer.position() + KEY_VALUE_LEN_SIZE; int comp = reader.getComparator().compareFlatKey(key, offset, length, blockBuffer.array(), keyOffset, klen); //比较 if (comp == 0) { if (seekBefore) { if (lastKeyValueSize < 0) { throw new IllegalStateException("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(); return 1; // non exact match. } currKeyLen = klen; currValueLen = vlen; if (this.reader.shouldIncludeMemstoreTS()) { currMemstoreTS = memstoreTS; currMemstoreTSLen = memstoreTSLen; } return 0; // indicate exact match } else if (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) { return HConstants.INDEX_KEY_MAGIC; } return 1; } // 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(); return 1; // 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 ...