共计 6995 个字符,预计需要花费 18 分钟才能阅读完成。
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 原理与实际》一书