hbase源码系列(十五)终结篇&Scan续集–>如何查询出来下一个KeyValue

撸了今年阿里、腾讯和美团的面试,我有一个重要发现…….

作者:岑玉海

出处:https://www.cnblogs.com/cenyuhai/tag/hbase%E6%BA%90%E7%A0%81%E7%B3%BB%E5%88%97/


Hbase 版本:0.96

这是这个系列的最后一篇了,实在没精力写了,本来还想写一下hbck的,这个东西很常用,当hbase的Meta表出现错误的时候,它能够帮助我们进行修复,无奈看到3000多行的代码时,退却了,原谅我这点自私的想法吧。

在讲《Get、Scan在服务端是如何处理?》当中的nextInternal流程,它的第一步从storeHeap当中取出当前kv,这块其实有点儿小复杂的,因为它存在异构的Scanner(一个MemStoreScanner和多个StoreFileScanner),那怎么保证从storeHeap里面拿出来的总是离上一个kv最接近的kv呢?

这里我们知道,在打开这些Scanner之后,就对他们进行了一下seek操作,它们就已经调整到最佳位置了。

我们看看KeyValueHeap的构造函数里面去看看吧。


public KeyValueHeap(List<? extends KeyValueScanner> scanners, KVComparator comparator) throws IOException { this.comparator = new KVScannerComparator(comparator); if (!scanners.isEmpty()) { this.heap = new PriorityQueue<KeyValueScanner>(scanners.size(), this.comparator); //...    this.current = pollRealKV(); } }

它内部有一个叫heap的PriorityQueue队列,它会对所有的Scanner进行排序,排序的比较器是KVScannerComparator, 然后current又调用了pollRealKV通过比较获得当前的Scanner,后面会讲。

那好,我们直接进去KVScannerComparator看看它的compare方法就能知道怎么回事了。


public int compare(KeyValueScanner left, KeyValueScanner right) { // 先各取出来一个KeyValue进行比较 int comparison = compare(left.peek(), right.peek()); if (comparison != 0) { return comparison; } else { // key相同,选择最新的那个 long leftSequenceID = left.getSequenceID(); long rightSequenceID = right.getSequenceID(); if (leftSequenceID > rightSequenceID) { return -1; } else if (leftSequenceID < rightSequenceID) { return 1; } else { return 0; } } }

额,从上面代码看得出来,把left和right各取出一个kv来进行比较,如果一样就比较SequenceID,SequenceID越大说明这个文件越新,返回-1,在升序的情况下,这个Scanner就跑到前面去了。
这样就实现了heap里面拿出来的第一个就是最小的kv的最新版。

在继续将之前,我们看一下在KeyValue是怎么被调用的,这样我们好理清思路。

 //从storeHeap里面取出一个来
KeyValue current = this.storeHeap.peek();
//后面是一顿比较,比较通过,把结果保存到results当中
KeyValue nextKv = populateResult(results, this.storeHeap, limit, currentRow, offset, length);

接着看populateResult方法。


private KeyValue populateResult(List<Cell> results, KeyValueHeap heap, int limit, byte[] currentRow, int offset, short length) throws IOException { KeyValue nextKv; do { //从heap当中取出剩下的结果保存在results当中 heap.next(results, limit - results.size()); //如果够数了,就返回了 if (limit > 0 && results.size() == limit) { return KV_LIMIT; } nextKv = heap.peek(); } while (nextKv != null && nextKv.matchingRow(currentRow, offset, length)); return nextKv; }

我们对KeyValueHeap的使用,就是先peek,然后再next,我们接下来就按这个顺序看吧。

先从peek取出来一个,peek就是从heap队列取出来的current的scanner取出来的当前的KeyValue。

 if (this.current == null) {
      return null;
}
return this.current.peek();

然后我们看next方法。


public boolean next(List<Cell> result, int limit) throws IOException { if (this.current == null) { return false; } InternalScanner currentAsInternal = (InternalScanner)this.current; boolean mayContainMoreRows = currentAsInternal.next(result, limit); KeyValue pee = this.current.peek(); if (pee == null || !mayContainMoreRows) { this.current.close(); } else { this.heap.add(this.current); } this.current = pollRealKV(); return (this.current != null); }

1、通过currentAsInternal.next继续获取kv,它是只针对通过通过检查的当前行的剩下的KeyValue,这个过程在之前那篇文章讲过了。

2、如果后面没有值了,就关闭这个Scanner。

3、然后还有,就把这个Scanner放回heap上,等待下一次调用。

4、使用pollRealKV再去一个新的Scanner出来。


private KeyValueScanner pollRealKV() throws IOException { KeyValueScanner kvScanner = heap.poll(); if (kvScanner == null) { return null; } while (kvScanner != null && !kvScanner.realSeekDone()) { if (kvScanner.peek() != null) { //查询之前没有查的 kvScanner.enforceSeek(); //把之前的查到位置的kv拿出来 KeyValue curKV = kvScanner.peek(); if (curKV != null) { //再选出来下一个的scanner KeyValueScanner nextEarliestScanner = heap.peek(); if (nextEarliestScanner == null) { // 后面没了,只能是它了 return kvScanner; } // 那下一个Scanner的kv也出来比较比较 KeyValue nextKV = nextEarliestScanner.peek(); if (nextKV == null || comparator.compare(curKV, nextKV) < 0) { // 它确实小,那么就把它放出去吧 return kvScanner; } // 把它放回去,和别的kv进行竞争 heap.add(kvScanner); } else { // 它没东西了,关闭完事 kvScanner.close(); } } else { // 它没东西了,关闭完事 kvScanner.close(); } kvScanner = heap.poll(); } return kvScanner; }

鉴于它每次都要比较的情况,如果一个列族下的HFile比较多的话,它的比较次数也会增大,会影响查询效率,查询时间和HFile的数量成线性关系。

另外补充点内容,是前面写Scan的时候拉下的:

由于写入同一个rowkey相关的KeyValue的时候时间戳在前的先写入,查询的时候又需要总是读该rowkey最新的KeyValue,所以在查询的时候会先seek到该rowkey的时间戳最大的位置,具体查的时候,不断的向前seekBefore,直到这个rowkey的KeyValue全部查完位置,然后再向前定位到一个rowkey的位置。

简而言之:

不同rowkey的向前查,从rowkey小的查到rowkey大的;查相同rowkey的向后查,从最新的时间戳到查到最久的时间戳。

总结:

这就把如何查询出来下一个KeyValue的过程讲完了,它的peek方法、next方法、比较的方法,希望对大家有帮助,这个系列的文章到此也就结束了,下个目标是跟随超哥学习Spark源码,感谢广大读者的支持,觉得我写得好的,可以关注一下我的博客,谢谢!

赞(2) 打赏

如未加特殊说明,此网站文章均为原创,转载必须注明出处。Java 技术驿站 » hbase源码系列(十五)终结篇&Scan续集–>如何查询出来下一个KeyValue
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

关注【Java 技术驿站】公众号,每天早上 8:10 为你推送一篇技术文章

扫描二维码关注我!


关注【Java 技术驿站】公众号 回复 “VIP”,获取 VIP 地址永久关闭弹出窗口

免费获取资源

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏