之前的那篇HFile结构解析的文章分析了下HFile的结构,这篇文章来分析下HBase Get数据的流程,看下它是如何获取数据的。

一般来说,HBase读取数据的流程是这样的:

  1. 先从Zookeeper中找到meta表所在的Regionserver的信息,根据namespace、表名、以及rowKey查找数据所在的RegionServer的信息。
  2. 向对应的regionServer建立连接并发起读取数据请求
  3. Regionserver会先从MemStore上找,再从SotreFile(先CacheBlocks再HFile)上读取数据。

下面,从源码角度分析下Get流程。

 

单个Get:

客户端流程如下:

  1. 根据rowKey找regionLocation  (内部用到了tableName),HRegionLocation中包括了regionInfo和serverName两种信息。

如果客户端有Region缓存的话,从缓存中寻找Location,否则需要从Meta表中找到对应的Region信息,这样就多了一次RPC通信。

如果要和meta表交互的话,使用当前的rowKey拼接成一条metaKey,用这个metaKey去找metaLocation,再找到输入rowKey对应的regionInfo。(内部的细节暂时看不太明白,估计和HBase的meta表存储逻辑有关,后续反过来再看吧,现在先不纠结)

最终获取到region信息之后要cache下,下次就可以直接使用cache中的信息了。

 

2.创建一个callable任务,发送请求给服务端,等待服务端执行完毕并返回结果

一句话概括就是找到这条rowKey所在的Region,将请求直接发往这个Region。

 

服务端(RegionServer)如何处理Get请求:

先简单介绍下HRegion的MVCC还有写入流程:

    MVCC全称:MultiVersionConsistencyControl,多版本控制协议,是一种通过数据的多版本来解决读写一致性问题的解决方案。(为什么不使用锁是因为锁的代价太大,主流的数据库类似MySQL,Postgres之类的大多都使用MVCC来尽量避免使用锁,从而提高并发性能)。

    通俗点说就是什么样的数据可以被读到,什么样的数据不能被读到,概念有点类似数据库的commit事务。

 

    从代码中可以看出,mvcc的变量是Region级别的,各个Region的MVCC是相互独立的。

    往HBase中写数据时,如果数据已经写入memstore但是还没有写入WAL日志的时候,这条数据是算还没有写入成功,按照数据库read committed定义,用户查询的时候不能查这条数据,MVCC在这个时候就派上用场了。写入过程及MVCC作用如下:

    HBase每次Put都会指定一个唯一ID,该ID是Region级递增的。每个Region的MVCC会维护两个pos:

  1. readpoint   指向已经插入完成的ID
  2. writepoint  指向正在插入的ID

没有数据写入时两者指向的位置时一样的,当有数据正在写入,readpoint要比writepoint要小,只有readpoint之前的数据才能被读到(也就是说,写入到MemStore和WALLog,没有写到磁盘,数据也是可以查到的)。

 

再说下HBase的Nonce机制,因为在后面代码中会看到:

    网络总会有不稳定的时候,当客户端发送RPC请求给服务器的时候,如果超时重试的话,会有多个相同的请求发送到客户端,一个put操作可能会执行多次引起我们不想要的结果,为了防止服务端重复处理请求,HBase引入了Nonce机制。

    客户端请求重发的时候会附带相同的nonce,服务端只需要根据nonce就可以判断是否是同一个请求,然后根据一定的规则决定是执行、等待还是拒绝。

相关代码在ServerNonceManager,它负责管理整个RegionServer上的nonce:

 

现在,可以分析下Get()请求在服务端是怎么走的了:

“1”和“3”没啥好说的,“3”只是将获取到的List<Cell>以及其他一些信息封装到了Result里面而已,主要操作是在“2”中完成的。看下“2”里面是怎么走的:

    一个RegionScanner由多个StoreScanner组成,Get请求中设置要获取多少个CloumnFamily的数据,对应就会有多少个StoreScanner。一个StoreScanner又由一个MenstoreScanner和多个StoreFileScanner组成。

    这些Scanner会按照KeyValue的大小顺序放在PriorityQueue优先队列中,Key小的在前面,这样获取的时候就是按照Key大小的顺序进行获取了。

 

RegionScanner创建:

    代码很多,这里我就摘取了一些个人认为是比较核心的地方。

    一个StoreScanner中对应了这个Store上的memstore和HFile,并且要求这些HFile不处于compaction,这也说明了正在compaction的HFile是无法检索的

    执行store.getScanner()方法的时候会放入readPt,也就是说读取的时候最多只能读到readPt以内的值。所以,此处的个人理解是,HBase在读取的时候是不能读取到最新刚进来的数据,除非这些数据在readPt的范围之内

 

细分析下getScanner()这个方法:

方法实现内部是初始化了一个StoreScanner,构造函数内部有很多重要的操作:

    主要是根据Time Range以及RowKey Range对StoreFileScanner以及MemstoreScanner进行过滤,淘汰肯定不存在待检索结果的Scanner。

    还有是构造了一个filter数据用的ScanQueryMatcher,用该类过滤出符合用户条件的数据。

 

    接着是执行initializeKVHeap()方法,将这些Scanner放到一个优先队列中,这些scanner在优先队列中的顺序是根据CellComparator来确定的:

    在HFile或者memstore中,数据是按rowKey从小到大的顺序进行排列的,rowKey小的排在前面。rowKey相同的情况下比较cf+cq的大小,还相同则比较时间戳,时间戳值越大则数据越新,在队列中的位置越靠前。最后比较TYPE('DeleteFamily' < 'DeleteColumn' < 'Delete' < 'Put')。

 

队列构建好以后,接着就是调用scanner.next(results)去获取数据了:

不得不说代码过于庞大,全部截取上来太繁琐了…所以这里就说下一些关键的地方吧:

  1. Scan是一行一行获取的,获取完一行之后才会去获取下一行
  2. 一般查找的顺序是先BlockCache,再HFile,再是Memstore
  3. 每拿一次数据都要判断下是否是stopRow,是的话就可以停止搜索然后返回了
  4. 最终返回的List<Cell> results要封装成Result[]格式返回

 

数据的读取是调用populateResult()方法完成的:

方法内部调用的是KeyValueHeap的peek()和next()方法,它们都是返回堆顶元素(实际上是堆顶KeyValueScanner的堆顶Cell),不同在于next()会将堆顶元素出堆,并重新调整堆,对外来说就是迭代器向前移动了;而peek()不会将堆顶出堆,堆顶不变。peek()和next()返回的都是指向KeyValueHeap堆顶scanner的元素current的堆顶cell。

 

一行数据的读取会进行如下步骤:

首先peek()出heap的堆顶元素,拿到一个KeyValue然后进行如下判定:

1.检查该KeyValue的KeyType是否是Deleted/DeletedCol等,如果是就直接忽略该列所有其他版本,跳到下列(列族)

2.检查该KeyValue的Timestamp是否在用户设定的Timestamp Range范围,如果不在该范围,忽略

3.检查该KeyValue是否满足用户设置的各种filter过滤器,如果不满足,忽略

4.检查该KeyValue是否满足用户查询中设定的版本数,比如用户只查询最新版本,则忽略该cell的其他版本;反正如果用户查询所有版本,则还需要查询该cell的其他版本。

 

    现在假设用户查询所有版本而且该keyvalue检查通过,此时当前的堆顶元素需要执行next方法去检索下一个值,并重新组织最小堆,再获取新的一个keyvalue进行一系列判定,再next,再重新组织最小堆,不断重复这个过程,直至一行数据全部被检索得到。继续下一行。

    从源码中也可以看出,数据是逐行获取的:

也可以看到Filter是在StoreScanner中获取数据的时候生效的:

(ps:一个Cell针对的是一个version的一个column)

 

多个Get怎么处理:

    可以看到HBaseClient会把请求封装到batch中,提交到服务端并行执行,gets操作是并行执行的,get的批量读请求会按照目标Region进行分组,不同分组的get请求会并发执行读取。注意的是一个Scan扫描的范围包含多个Region的话,也不会并发读取的。

 

 

 

参考:

https://blog.csdn.net/yunlong34574/article/details/54094875(HBase Client流程分析)

https://www.jianshu.com/p/86246d8bee24 (HBase的MVCC机制介绍)

https://blog.csdn.net/lw_ghy/article/details/60778843(HBase一行数据是怎么读取的)

 

 

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐