HBase源码分析6—HFile剖析
在上一篇中我们介绍了一下BloomFilter的相关内容,这部分内容对于我们理解HFile的结构很重要(其实也没那么重要,跳过不看就是了= =||)。这一篇我们就来针对HFile下手,研究下他的结构究竟是什么样的。
历史
HFile一共经历了三个版本的变迁。我们有必要先了解下每次修改的缘由。
(在HFile出现前还有基于MapFile的解决方案,因为当时并没有HFile这个东西,就不提了。Compaction和标记删除就是那个时候出现的。)
V1
从JIRA(HBASE-61)上看,HFile的出现似乎是为了挖掘潜在的性能优化能力。在V1中,data和index保存在同一个文件中,支持保存metadata(如BloomFilter),FileInfo用于保存合并等场景需要的文件信息。
图片来自hbase.apache.org
V2
随着数据量的变大,V1的缺点也逐渐暴露出来:每个HFile中的索引等信息必须全部加载到内存中,这些数据有时会很大。在V2中主要引入了多级索引,不需要把所有索引加载到内存、块级索引等。
在这个图中:
- Scanned Block Section:顺序扫描时使用的块,包括了data block、leaf index block、bloom block
- Non-Scanned Block Section:顺序扫描时不使用的块,包括了meta block、intermediate data block等
- Load-on-Open Section:启动时需要被加载到内存的块,包括root data index、meta index、file info等
- Trailer:记录HFile基本信息和上面三个块的偏移量等信息
Block
HFileBlock
所有的块(跟data block同级的这些)都继承自HFileBlock(位置在 main/java/org/apache/hadoop/hbase/io/hfile/HFileBlock.java ),结构如下图
BlockType 表示了这个块的类型。HBase提供了以下几种类型( MagicStr 就是实际填在 BlockType 位置的数据)
BlockType 这个类里还定义了一些基本的读写操作,比较简单不列举了。
HFile是如何被读的呢?可以看同一个包下 HFileReaderV2.java ,在构造的时候有一个循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
while (offset < end) { if (Thread.interrupted()) { break; } // Perhaps we got our block from cache? Unlikely as this may be, if it happens, then // the internal-to-hfileblock thread local which holds the overread that gets the // next header, will not have happened...so, pass in the onDiskSize gotten from the // cached block. This 'optimization' triggers extremely rarely I'd say. long onDiskSize = prevBlock != null? prevBlock.getNextBlockOnDiskSize(): -1; HFileBlock block = readBlock(offset, onDiskSize, true, false, false, false, null, null); prevBlock = block; offset += block.getOnDiskSizeWithHeader(); } |
追踪 readBlock 这个函数可以追到 HFileBlock.java 中的 readBlockDataInternal ,比较简单,先读header,然后根据长度建一个新的buffer,把header拷贝过去,再把data读到buffer的后面。但是上面这个函数里block搞出来也没有下一步动作,其实是都整个被缓存起来了。代码在 readBlock函数中
1 2 3 4 5 6 7 8 9 10 |
// Cache the block if necessary if (cacheBlock && cacheConf.shouldCacheBlockOnRead(category)) { cacheConf.getBlockCache().cacheBlock(cacheKey, cacheConf.shouldCacheCompressed(category) ? hfileBlock : unpacked, cacheConf.isInMemory(), this.cacheConf.isCacheDataInL1()); } if (updateCacheMetrics && hfileBlock.getBlockType().isData()) {   |