序
按照自己的理解进行解读,也没啥好参照的,只是每次面试别人问Hbase的读流程,讲真没有一个说正确的。
since: 2021年5月18日 22:49
auth: Hadi
前言
在各个地方都有很多写HBase读流程的,但说句实话有太多缺陷并没有说清楚,本文也旨在不断完善各位的知识体系而创建,争取完善每一步疑问,达成体系。
本文不适合一点基础都没有新手,固有名词不会进行讲解,如果有其他疑问直接百度寻找其他贴子就行。
Hbase数据读取流程
一般来说,在描述HBase读流程如下:
1. Client端会优先查看自己本地是否与regionServer有连接,或者是否有缓存知道regionServer的地址,那么就会直接进行连接;不然的话会在Zookeeper中查阅Meta表进行对应数据所在region的regionServer节点信息,再进行访问操作。
2. Client连接到具体的regionServer,提交取数需求,将会"同时"在Memstore 、BlockCache、HFile中查询数据。
3. 在MemStore中使用 Memory Scanner进行数据的扫描读取;计算布隆过滤器(如果有的话)确定哪些HFile包含这些数据信息,如果这个HFile的这个块,已经存在Block Cache中,那么直接从BlockCache中进行读取;如果不在将从磁盘中获取Block,加载到BlockCache中进行读取(这里涉及到BlockCache缓存,请参考link)。
4. 在regionServer进行数据的merge了之后,就会将数据进行返回给Client。
RegionServer的Scanner架构
前面对RegionServer说的很简单,但其实来说还是比较复杂的,需要了解的RegionServer的架构:
如上图所示,LSM架构在插入数据的时候使用HLog、MemStore、HFile的架构进行数据插入,那么必定会导致连续段的数据可能分布到多个HFile中,甚至会存储在多个Region、RegionServer中。
那么我们涉及到的数据查询就必须涉及到三层的Scanner: RegionScanner、StoreScanner、StoreFileScanner。RegionScanner会包含多个StoreScanner来扫描同一个RegionServer中不同的Region中的数据(同一张表的不同列族就会被拆分为多个Region)。而StoreScanner包含一个MemStoreScanner和多个StoreFileScanner,MemStoreScanner为扫描内存中的数据,StoreFileScanner扫描HFile文件。所以MemStoreScanner和StoreFileScanner是最后的执行者。如下图所示:
当查询任务开始的时候,RegionScanner接受到查询的需求,就开始动工,将任务下发给各个StoreScanner;各个StoreScanner又会将任务下发到MemStoreScanner和StoreFileScanner中,实际的工作者接受到任务后首先会先判断自身有没有对应要求的数据:根据 Time Range & RowKey Range & 布隆过滤器 的计算结果进行排查,没有数据的会直接进行汇报没有数据(布隆过滤器的设置请参考 布隆过滤器)。
剩余的MemStoreScanner & StoreFileScanner 会快速定位到自身HFile中有数据的Block块,将数据块保存在Block Cache中(如果已经存在则直接读取,如果不存在则需要进行添加,详细请参考 BlockCache详解)。那么怎么定位到具体的Block块呢?在HFile结构中已经提到,会首先在BlockCache中获取这个HFile的索引树,根据索引树进行对应RowKey的Block Offset 和Block Size(默认64k),具体的索引树查找在下面会进行二次详细讲解。获取到Block块之后,采用二分查找的方式进行RowKey的具体定位,这个时候数据应该是从BlockCache中取过来放入内存中了。
StoreFileScanner结果合并建立最小堆。将Store中所有满足的查询结构进行Merge操作。因为HBase不仅存储了数据,其实还存储了各类操作,必须将所有满足结果的数据进行合并才能获取到真正的结果。这个时候使用最小堆的好处就是:1.可以对版本进行排序,如果Client端只需要最新的版本(时间戳),或者N个版本,那么就可以通过最小堆进行输出。 2. 按照操作进行排序,刚刚提到了Hbase存储了各类操作,不仅仅是数据,那么需要对数据进行整合,知道哪些数据是被删除的。 3.维护最小堆代价低,容易维护,提供给客户一个从小到大的顺序输出。
HBase查询就讲完了,现在开始细节部分。
HFlie 结构
其实在以前Blog已经写过,这里简单复述下:
主要分为四大部分,Trailer和Load-On-Open是最先被加载到内存中的,其余部分在需要时才会被加载。我们实际存储数据的地方就是Data Block,当确认到需要取的数据的时候,会将DataBlock拉取到Block Cache中,然后读入内存。这个时候必须讲解下Block的分类,如上图所示已经能查看到。然后Block的内容,主要包含:BlockHeader和BlockData。在BlockHeader中藏有BlockType,OnDiskType,UncompressedSize,PrevBlockOffsett;提供快速前后Block的切换。而BlockData则是装存真正数据的地方。
Block们都有统一的大小(默认64K),方便上下块的切换和读取,在Block Cache的地方可以看出关于Block块大小的问题。
那么就来看看DataBlock存储的数据如下:
可以看到,DataBlock包含多个KeyValue结构的数据,而每个KerValue结构都包含KeyLength、ValueLength、Key、Value,而每个Key包含RowLength、RowKey、ColumnFamily Length、Column Family、TimeStamp、KeyType;其中KeyType分为put、delete、deleteFamily、deleteColumn四类。各种length主要用于快速跳跃和快速取数。
索引树
随着HFile的文件越来越大,DataBlock越来越多,对HFile块索引管理难度也越来越大,甚至导致索引可能无法全部加载到内存中,那么必须对索引进行索引,实现多级索引的操作。如下图所示:
索引树分为三级:
root Index Block:
位于load-ono-open部分
会在regionServer打开HFile时加载到内存中
记录了MidKey相关信息,用于Split操作时,快速定位HFile的切分点位置。
intermediate Index Block:
位于Non-Scanned-Block
Leaf Index Block:
位于Scanned Block
直接指向时间Data Block
在数据不足的情况下,索引树可能并没有三层,但至少会有root Index Block层。
Block Cache
在Hbase的RegionServer中,专门有一块区域用于CacheBlock,由于这一部分已经写过了,个人感觉没有重写必要(因为以前想写的已经写完,link放在这里)。
需要记得的是积累Cache policy:LRUBlockCache、SlabCache、BucketCache、CombinedBlockCache 和 配置相关设计。
后续
这个Blog争取持续更新。2021年5月19日00:15:19。