HBase的Get是如何执行的
之前的那篇HFile结构解析的文章分析了下HFile的结构,这篇文章来分析下HBase Get数据的流程,看下它是如何获取数据的。一般来说,HBase读取数据的流程是这样的:先从Zookeeper中找到meta表所在的Regionserver的信息,根据namespace、表名、以及rowKey查找数据所在的RegionServer的信息。向对应的regionServer建立连接并发起...
之前的那篇HFile结构解析的文章分析了下HFile的结构,这篇文章来分析下HBase Get数据的流程,看下它是如何获取数据的。
一般来说,HBase读取数据的流程是这样的:
- 先从Zookeeper中找到meta表所在的Regionserver的信息,根据namespace、表名、以及rowKey查找数据所在的RegionServer的信息。
- 向对应的regionServer建立连接并发起读取数据请求
- Regionserver会先从MemStore上找,再从SotreFile(先CacheBlocks再HFile)上读取数据。
下面,从源码角度分析下Get流程。
单个Get:
客户端流程如下:
- 根据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:
- readpoint 指向已经插入完成的ID
- 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)去获取数据了:
不得不说代码过于庞大,全部截取上来太繁琐了…所以这里就说下一些关键的地方吧:
- Scan是一行一行获取的,获取完一行之后才会去获取下一行
- 一般查找的顺序是先BlockCache,再HFile,再是Memstore
- 每拿一次数据都要判断下是否是stopRow,是的话就可以停止搜索然后返回了
- 最终返回的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一行数据是怎么读取的)
更多推荐
所有评论(0)