MemStore中数据落盘之后会造成一个文件写入HDFS,这个文件称为HFile。HFile参考BigTable的SSTable和Hadoop的TFile实现。从HBase诞生到当初,HFile经验了3个版本,其中V2在0.92引入,V3在0.98引入。HFile V1版本在理论应用过程中发现占用内存过多,HFile V2版本针对此问题进行了优化,HFile V3版本和V2版本基本相同,只是在cell层面增加了对Tag数组的反对。鉴于此,本文次要针对V2版本进行剖析,对V1和V3版本感兴趣的读者能够参考社区官网文档。
HFile逻辑构造
HFile V2的逻辑构造如图所示
HFile文件次要分为4个局部:Scanned block局部、Non-scanned block局部、Load-on-open局部和Trailer。
•Scanned Block局部:顾名思义,示意程序扫描HFile时所有的数据块将会被读取。这个局部蕴含3种数据块:Data Block,Leaf Index Block以及BloomBlock。其中Data Block中存储用户的KeyValue数据,Leaf Index Block中存储索引树的叶子节点数据,Bloom Block中存储布隆过滤器相干数据。
•Non-scanned Block局部:示意在HFile程序扫描的时候数据不会被读取,次要包含Meta Block和Intermediate Level Data Index Blocks两局部。
•Load-on-open局部:这部分数据会在RegionServer关上HFile时间接加载到内存中,包含FileInfo、布隆过滤器MetaBlock、Root Data Index和MetaIndexBlock。
•Trailer局部:这部分次要记录了HFile的版本信息、其余各个局部的偏移值和寻址信息。
HFile物理构造
HFile物理构造如图所示。
实际上,HFile文件由各种不同类型的Block(数据块)形成,尽管这些Block的类型不同,但却领有雷同的数据结构。
Block的大小能够在创立表列簇的时候通过参数blocksize=> '65535'指定,默认为64K。通常来讲,大号的Block有利于大规模的程序扫描,而小号的Block更有利于随机查问。因而用户在设置blocksize时须要依据业务查问特色进行衡量,默认64K是一个绝对折中的大小。
HFile中所有Block都领有雷同的数据结构,HBase将所有Block对立形象为HFile-Block。HFileBlock反对两种类型,一种类型含有checksum,另一种不含有checksum。为不便解说,本节所有HFileBlock都选用不含有checksum的HFileBlock。HFileBlock构造如图所示。
HFileBlock次要蕴含两局部:BlockHeader和BlockData。其中BlockHeader次要存储Block相干元数据,BlockData用来存储具体数据。Block元数据中最外围的字段是BlockType字段,示意该Block的类型,HBase中定义了8种BlockType,每种BlockType对应的Block都存储不同的内容,有的存储用户数据,有的存储索引数据,有的存储元数据(meta)。对于任意一种类型的HFileBlock,都领有雷同构造的BlockHeader,然而BlockData构造却不尽相同。下表列举了最外围的几种BlockType。
HFile的根底Block
1. Trailer Block
Trailer Block次要记录了HFile的版本信息、各个局部的偏移值和寻址信息,图为Trailer Block的数据结构,其中只显示了局部外围字段。
RegionServer在关上HFile时会加载所有HFile的Trailer局部以及load-on-open局部到内存中。理论加载过程会首先会解析Trailer Block,而后再进一步加载load-on-open局部的数据,具体步骤如下:
1)加载HFile version版本信息,HBase中version蕴含majorVersion和minorVersion两局部,前者决定了HFile的主版本——V1、V2还是V3;后者在主版本确定的根底上决定是否反对一些渺小修改,比方是否反对checksum等。不同的版本应用不同的文件解析器对HFile进行读取解析。
2)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,示意不应用压缩。
3)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中布隆过滤器的相干元数据。
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是一个比较复杂的复合构造,这点最早在第1章介绍HBase数据模型时就提到过。因为任意KeyValue中都蕴含Rowkey、Column Family以及ColumnQualif ier,因而这种存储形式实际上比间接存储Value占用更多的存储空间。这也是HBase零碎在表结构设计时常常强调Rowkey、Column Family以及ColumnQualif ier尽可能设置短的根本原因。
HFile中与布隆过滤器相干的Block
布隆过滤器对HBase的数据读取性能优化至关重要。HBase是基于LSM树结构构建的数据库系统,数据首先写入内存,而后异步f lush到磁盘造成文件。这种架构人造对写入敌对,而对数据读取并不非常敌对,因为随着用户数据的一直写入,零碎会生成大量文件,用户依据Key获取对应的Value,实践上须要遍历所有文件,在文件中查找指定的Key,这无疑是很低效的做法。应用布隆过滤器能够对数据读取进行相应优化,对于给定的Key,通过布隆过滤器解决就能够晓得该HFile中是否存在待检索Key,如果不存在就不须要遍历查找该文件,这样就能够缩小理论IO次数,进步随机读性能。布隆过滤器通常会存储在内存中,所以布隆过滤器解决的整个过程耗时根本能够疏忽。
HBase会为每个HFile调配对应的位数组,KeyValue在写入HFile时会先对Key通过多个hash函数的映射,映射后将对应的数组地位为1,get申请进来之后再应用雷同的hash函数看待查问Key进行映射,如果在对应数组位上存在0,阐明该get申请查问的Key必定不在该HFile中。当然,如果映射后对应数组位上全副为1,则示意该文件中有可能蕴含待查问Key,也有可能不蕴含,须要进一步查找确认。
能够设想,HFile文件越大,外面存储的KeyValue值越多,位数组就会相应越大。一旦位数组太大就不适宜间接加载到内存了,因而HFile V2在设计上将位数组进行了拆分,拆成了多个独立的位数组(依据Key进行拆分,一部分间断的Key应用一个位数组)。这样,一个HFile中就会蕴含多个位数组,依据Key进行查问时,首先会定位到具体的位数组,只须要加载此位数组到内存进行过滤即可,从而升高了内存开销。
在文件构造上每个位数组对应HFile中一个Bloom Block,因而多个位数组实际上会对应多个Bloom Block。为了不便依据Key定位对应的位数组,HFile V2又设计了相应的索引Bloom Index Block,对应的内存和逻辑构造如图所示。
Bloom Index Block构造
整个HFile中仅有一个Bloom Index Block数据块,位于load-on-open局部。Bloom Index Block从大的方面看由两局部内容形成,其一是HFile中布隆过滤器的元数据根本信息,其二是构建了指向Bloom Block的索引信息。
Bloom Index Block构造中TotalByteSize示意位数组大小,NumChunks示意Bloom Block的个数,HashCount示意hash函数的个数,HashType示意hash函数的类型,TotalKeyCount示意布隆过滤器以后曾经蕴含的Key的数目,TotalMaxKeys示意布隆过滤器以后最多蕴含的Key的数目。
Bloom Index Entry对应每一个Bloom Block的索引项,作为索引别离指向scanned block局部的Bloom Block,Bloom Block中理论存储了对应的位数组。Bloom Index Entry的构造见图5-13两头局部,其中BlockKey是一个十分要害的字段,示意该Index Entry指向的Bloom Block中第一个执行Hash映射的Key。BlockOffset示意对应Bloom Block在HFile中的偏移量。
因而,一次get申请依据布隆过滤器进行过滤查找须要执行以下三步操作:
1)首先依据待查找Key在Bloom Index Block所有的索引项中依据BlockKey进行二分查找,定位到对应的Bloom Index Entry。
2)再依据Bloom Index Entry中BlockOffset以及BlockOndiskSize加载该Key对应的位数组。
3)对Key进行Hash映射,依据映射的后果在位数组中查看是否所有位都为1,如果不是,示意该文件中必定不存在该Key,否则有可能存在。
HFile中索引相干的Block
依据索引层级的不同,HFile中索引构造分为两种:single-level和multi-level,前者示意单层索引,后者示意多级索引,个别为两级或三级。HFile V1版本中只有single-level一种索引构造,V2版本中引入多级索引。之所以引入多级索引,是因为随着HFile文件越来越大,Data Block越来越多,索引数据也越来越大,曾经无奈全副加载到内存中,多级索引能够只加载局部索引,从而升高内存应用空间。同布隆过滤器内存应用问题一样,这也是V1版本升级到V2版本最重要的因素之一。
V2版本Index Block有两类:Root Index Block和NonRoot Index Block。NonRoot Index Block又分为Intermediate Index Block和Leaf Index Block两种。HFile中索引是树状构造,Root Index Block示意索引数根节点,Intermediate Index Block示意两头节点,Leaf Index Block示意叶子节点,叶子节点间接指向理论Data Block,如图所示。
HFile文件索引
须要留神的是,这三种Index Block在HFile中位于不同的局部,Root Index Block位于“ load-on-open”局部,会在RegionServer关上HFile时加载到内存中。Intermediate Index Block位于“Non-Scanned block”局部,Leaf Index Block位于“scanned block”局部。
HFile中除了Data Block须要索引之外,Bloom Block也须要索引,Bloom索引构造实际上采纳了单层构造,Bloom Index Block就是一种Root Index Block。
对于Data Block,因为HFile刚开始数据量较小,索引采纳单层构造,只有RootIndex一层索引,间接指向Data Block。当数据量缓缓变大,Root Index Block大小超过阈值之后,索引就会决裂为多级构造,由一层索引变为两层,根节点指向叶子节点,叶子节点指向理论Data Block。如果数据量再变大,索引层级就会变为三层。
上面针对Root index Block和NonRoot index Block两种构造进行解析(Intermediate Index Block和Ieaf Index Block在内存和磁盘中存储格局雷同,都为NonRoot Index Block格局)。
1. Root Index Block
Root Index Block示意索引树根节点索引块,既能够作为Bloom Block的间接索引,也能够作为Data Block多极索引树的根索引。对于单层和多级这两种索引构造,对应的Root Index Block构造略有不同,单层索引构造是多级索引构造的一种简化场景。本书以多级索引构造中的Root Index Block为例进行剖析,图为Root Index Block的结构图。
图中,Index Entry示意具体的索引对象,每个索引对象由3个字段组成:Block Offset示意索引指向Data Block的偏移量,BlockDataSize示意索引指向Data Block在磁盘上的大小,BlockKey示意索引指向Data Block中的第一个Key。
除此之外,还有另外3个字段用来记录MidKey的相干信息,这些信息用于在对HFile进行split操作时,疾速定位HFile的切分点地位。须要留神的是单层索引构造和多级索引构造相比,仅短少与MidKey相干的这三个字段。
Root Index Block位于整个HFile的“ load-on-open ”局部,因而会在RegionServer关上HFile时间接加载到内存中。此处须要留神的是,在Trailer Block中有一个字段为DataIndexCount,示意Root Index Block中Index Entry的个数,只有晓得Entry的个数能力正确地将所有Index Entry加载到内存。
2. NonRoot Index Block
当HFile中Data Block越来越多,单层构造的根索引会一直收缩,超过肯定阈值之后就会决裂为多级构造的索引构造。多级构造中根节点是Root Index Block。而索引树的中间层节点和叶子节点在HBase中存储为NonRoot Index Block,但从Block构造的视角剖析,无论是两头节点还是叶子节点,其都领有雷同的构造,如图所示。
和Root Index Block雷同,NonRoot Index Block中最外围的字段也是IndexEntry,用于指向叶子节点块或者Data Block。不同的是,NonRoot Index Block构造中减少了Index Entry的外部索引Entry Offset字段,Entry Offset示意IndexEntry在该Block中的绝对偏移量(绝对于第一个Index Entry),用于实现Block内的二分查找。通过这种机制,所有非根节点索引块(包含Intermediate Index Block和Leaf Index Block)在其外部定位一个Key的具体索引并不是通过遍历实现,而是应用二分查找算法,这样能够更加高效疾速地定位到待查找Key。
文章基于《HBase原理与实际》一书