Hbase 读取复杂原因:
主要基于两个方面的原因:
- 一是因为HBase一次范围查询可能会涉及多个Region、多块缓存甚至多个数据存储文件(HFile);
- 二是因为HBase中更新操作以及删除操作的实现都很简单。
插入&删除: 没有更新原始数据,而是通过时间戳属性新增版本。
删除:只是插入deleted标签。在Major Compact的时候删除真正数据。
读取过程需要根据版本进行过滤,对已经标记删除的数据也要进行过滤。
读流程步骤:
- Client-Server读取交互逻辑
- Server端Scan框架体系
- 过滤淘汰不符合查询条件的HFile
- 从HFile中读取待查找Key。
1. Client-Server读取交互逻辑
Client首先会从ZooKeeper中获取元数据hbase:meta表所在的RegionServer,然后根据待读写rowkey发送请求到元数据所在RegionServer,获取数据所在的目标RegionServer和Region(并将这部分元数据信息缓存到本地),最后将请求进行封装发送到目标RegionServer进行处理。
-
hbase的读取请求分为get和scan,而get实际是最简单的一次scan。
-
client-server端将scan没有使用一次rpc,原因:
•大量数据传输会导致集群网络带宽等系统资源短时间被大量占用,严重影响集群中其他业务。
•客户端很可能因为内存无法缓存这些数据而导致客户端OOM。
image.png -
一次大的scan操作会拆分为多个RPC请求。每个RPC是一次next请求,分会规定数量的结果。每次next()操作,客户端先检查本地缓存,没有就发一次RPC请求给服务器,然后缓存到本地。
-
单次RPC caching大小默认是Integer.MAX_VALUE。设置过大可能因为一次获取到的数据量太大导致服务器端/客户端内存OOM;设置太小会导致一次大scan进行太多次RPC,网络成本高。
-
对于一张表列过大,可以通过setBatch方法设置一次RPC请求的数据列数量。
-
客户端还可以通过setMaxResultSize方法设置每次RPC请求返回的数据量大小(不是数据条数),默认是2G。
2 Server端Scan框架体系
-
一次scan可能会同时扫描一张表的多个Region。客户端会根据hbase:meta元数据将扫描的起始区间[startKey, stopKey)进行切分,切分成多个互相独立的查询子区间,每个子区间对应一个Region。
-
HBase中每个Region都是一个独立的存储引擎,因此客户端可以将每个子区间请求分别发送给对应的Region进行处理。下文会聚焦于单个Region处理scan请求的核心流程。
-
RegionServer接收到客户端的get/scan请求之后做了两件事情:首先构建scanner iterator体系;然后执行next函数获取KeyValue,并对其进行条件过滤。
image.png
-
构建Scanner Iterator体系
1)过滤淘汰部分不满足查询条件的Scanner。
主要过滤策略有:TimeRange过滤、Rowkey Range过滤以及布隆过滤器
image.png
2)每个Scanner seek到startKey
-
根据HFile索引树定位目标Block
image.png - BlockCache中检索目标Block
Block缓存到Block Cache之后会构建一个Map,Map的Key是BlockKey,Value是Block在内存中的地址。其中BlockKey由两部分构成——HFile名称以及Block在HFile中的偏移量。BlockKey很显然是全局唯一的。根据BlockKey可以获取该Block在BlockCache中内存位置,然后直接加载出该Block对象。如果在BlockCache中没有找到待查Block,就需要在HDFS文件中查找。 -
HDFS文件中检索目标Block
image.png
根据文件索引提供的Block Offset以及Block DataSize这两个元素可以在HDFS上读取到对应的Data Block内容。
为什么HDFS的Block设计为128M,而HBase的Block设计为64K ?
1. HDFS Block 为128M,因为 主要存储文件,Block太小,会导致Block元数据(Block所在DataNode位置、文件与Block之间的对应关系等)庞大。因为HDFS元数据都存储在NameNode上,大量的元数据很容易让NameNode成为整个集群的瓶颈。
2. HBase的缓存策略是缓存整个Block,如果Block设置太大会导致缓存很容易被耗尽,尤其对于很多随机读业务,设置Block太大会让缓存效率低下。
- 从Block中读取待查找KeyValue
3)KeyValueScanner合并构建最小堆
最小堆管理Scanner可以保证取出来的KeyValue都是最小的,这样依次不断地pop就可以由小到大获取目标KeyValue集合,保证有序性。
执行next函数获取KeyValue并对其进行条件过滤
1)检查该KeyValue的KeyType是否是Deleted/DeletedColumn/DeleteFamily等
2)检查该KeyValue的Timestamp是否在用户设定的Timestamp Range范围
3)检查该KeyValue是否满足用户设置的各种filter过滤器
4)检查该KeyValue是否满足用户查询中设定的版本数
总结: 读取过程中,是先根据TimeRange,Rowkey Range,bloomfilter判断数据是否在对应文件,再将文件中的数据读取出来,判断是否删除,是否符合用户条件。
读取过程总结:
- 客户端: 将scan 转变为多次next请求。将请求转变为发送对应region(读zk,读meta找到对应region)的请求
- 服务器端:过滤掉不符合查询条件的hfile,读取符合条件的hfile,构建最小堆
(1)过滤hfile方式:TimeRange,RowKey,bloomfiler
(2) hfile索引树定位目标block
(3)检索block: blockcache中检索、hdfs文件中检索block - 获取keyvalue,next过滤:
(1)keytype判断是否删除
(2)timestamp判断timerange
(3) 判断用户filter
(4) 判断版本号
网友评论