Hbase之Hfile索引机制

HFile索引结构根据索引层级不同分为2种:Single-Level 和 Multi-Level。前者表示单级索引,后者表示多级索引,一般分为2级或者3级。之所以HFile v2引入多级索引是因为,随着HFile文件越来越大,Data Block越来越多,索引数据也越来越多,大到已经无法全部加载到内存,而多级索引可以只是加载部分索引,降低内存的使用空间。

 

Index Block分为2种:Root Index Block和 Non-Root Index Block.

Non-Root Index Block又分为Intermediate Index Block和Leaf Index

Block2种。HFile中索引结构类似于一棵树,Root Index Block表示索引数据根节点,Intermediate Index Block表示中间节点,Leaf Index Block表示叶子节点,叶子结点直接指向实际数据块。

 

对于Data Block,由于HFile刚开始数据量比较小,采用single-level结构,只有root index 一级索引,直接指向数据块。当数据量慢慢变大时,root index block满了之后,索引就会变成multi-level结构,由一级索引变成两级索引,根节点指向叶子节点,叶子结点指向实际数据块。如果数据量在变大,索引就会变成三级索引。

 

我们来着重分析Index Block结构:

一 Root Index Block

Root Index Block表示索引树根节点索引块,可以作为Bloom Block的直接索引,也可以作为Data Block索引的根索引。在Single-Level和Multi-Level中两种索引结构对应的Root Index Block稍有差异,如图示:


Index Entry: 表示具体的索引对象,每一个索引由BlockOffset,BlockDataSize,BlockKey三个字段组成。

# BlockOffset: 指的是当前索引指向数据块的偏移量

# BlockDataSize: 指的是索引指向的数据块在磁盘上的大小

# BlockKey: 指的是索引指向的数据块中的第一个key

 

如果在Multi-Level还有MidKey相关信息:

MidKey表示HFile中所有Data Block中中间的一个Data Block,用于对HFile进行split操作,快速定位HFile中间的位置。

 

Root Index Block会在HFile解析的时候,直接加载进内存,我们知道Trailer Block中有一个字段为dataIndexCount,就表示此处Index Entry个数。因为Index Entry并不定长,只有知道Entry的个数才能正确将所有Index Entry加载进内存。

 

二 Non-Root Index Block

当HFile越来越大的时候,Single-Level结构的索引,已经不足以支撑所有数据都加载到内存,需要分化为Multi-Level结构。Multi-Level结构中,Non-Root Index Block可作为中间节点,也可以直接作为叶子节点,他们都拥有相同的结构:



和Root Index Block相比,Non-Root Index Block也有Index Entry,用于指向叶子节点或者实际的数据块。对于Intermediate Index Block就是指向叶子节点,对于Leaf Index Block就是指向实际的数据块。但是Non-Root Index Block多了EntryOffset 和 numEntires

numEntires: 记录entry的数量

EntryOffset: 是Non-Root Index Block内部索引字段,表示Index Entry在该block中的相对偏移量,相对于第一个Index Entry,用于实现Block内部的二分查找,故针对Non-Root Index Block,在其内部定位一个key的具体索引,不是通过遍历而是通过二分查找实现,可以更加高效快速定位到待查找的key

 

三 索引的分裂

我们知道,数据量少的时候,文件小的时候,只需要一个Root Index

Block 就可以完成索引,即索引树只有一级。但是当数据不断写入的的时候,文件变大之后,索引数据也会相应变大,索引结构就会由Single-Level转化为Multi-Level,这期间会涉及到索引块写入和分裂,我们就分析一下数据写入是如何引起索引分裂的?

 

我们知道MemStore的flush主要分为三个阶段:

第一阶段:将MemStore的KeyValue数据进行snapshot

第二阶段: 再将这部分数据flush到HFile,并生成临时目录

第三阶段: 将临时文件移动到指定的Column Family目录下

在flush阶段又可以具体分为两个阶段:

Append阶段:MemStore中的KeyValue首先会写入到HFile中的数据块

# 预检查: 检查key的大小是否大于前一个key,如果大于则不符合HBase顺序排列,抛出异常;检查value是否为null,如果为null也会抛出异常

# block是否写满:检查当前Data Block是否已经写满,如果没有则直接写入KeyValue;否则需要执行数据块写入磁盘以及索引块修改操作

# 数据块落盘并修改索引: 如果Data Block已满,首先将block写入流;再生成一个Leaf Index Entry,写入Leaf Index Block;再检查该Leaf Index Block是否已经写满需要落盘,如果已经写满,就将该Leaf Index Block写入输出流,并且为索引树根节点Root Index Block新增一个索引,指向叶子节点

# 生成一个新的block:reset 输出流,初始化startOffset = -1

# 写入KeyValue: 将KeyVlaue以流的方式写入输出流,同时需要写入memstoreTS,除此之外,如果该key是第一个key,还要赋值给变量firstKeyInBlock

 

Finalized阶段: 修改HFile中meta元数据,索引块数据以及Trailer数据块

当数据落盘并修改索引会使得Root Index 不断增多,当增大到一定程度之后就需要分裂。



根节点索引指向叶子节点索引块。finalize阶段系统会对Root Index Block进行大小检查,如果大小大于规定的大小就需要进行分裂,图中分裂过程实际上就是将原来的Root Index Block块分割成4块,每块独立形成中间节点InterMediate Index Block,系统再重新生成一个Root Index Block(图中红色部分),分别指向分割形成的4个interMediate Index Block。此时索引结构就变成了third-level结构。

 

四HFile数据完整索引流程

了解了HFile中数据索引块的两种结构之后,就来看看如何使用这些索引数据块进行数据的高效检索。整个索引体系类似于MySQL的B+树结构,但是又有所不同,比B+树简单,并没有复杂的分裂操作。具体见下图所示:



图中上面三层为索引层,在数据量不大的时候只有最上面一层,数据量大了之后开始分裂为多层,最多三层,如图所示。最下面一层为数据层,存储用户的实际keyvalue数据。这个索引树结构类似于InnoSQL的聚集索引,只是HBase并没有辅助索引的概念。

图中红线表示一次查询的索引过程(HBase中相关类为HFileBlockIndex和HFileReaderV2),基本流程可以表示为:

1. 用户输入rowkey为fb,在root index block中通过二分查找定位到fb在’a’和’m’之间,因此需要访问索引’a’指向的中间节点。因为root index block常驻内存,所以这个过程很快。

2. 将索引’a’指向的中间节点索引块加载到内存,然后通过二分查找定位到fb在index ‘d’和’h’之间,接

猜你喜欢

转载自blog.csdn.net/xiaoyutongxue6/article/details/79855805