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

这是这个系列的最后一篇了,实在没精力写了,本来还想写一下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<KeyValueScanner>队列,它会对所有的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;
}

尽管它已经排过序了,它还是要再不停的比较这些个Scanner的取出来的kv,可能是因为它不是每次放进去都排序,只是在初始化的时候排序?这个就不管了,反正给出我们要的结果就行了。

总结:

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

时间: 2024-12-29 09:44:58

hbase源码系列(十五)终结篇&Scan续集-->如何查询出来下一个KeyValue的相关文章

hbase源码系列(五)单词查找树

在上一章中提到了编码压缩,讲了一个简单的DataBlockEncoding.PREFIX算法,它用的是前序编码压缩的算法,它搜索到时候,是全扫描的方式搜索的,如此一来,搜索效率实在是不敢恭维,所以在hbase当中单独拿了一个工程出来实现了Trie的数据结果,既达到了压缩编码的效果,亦达到了方便查询的效果,一举两得,设置的方法是在上一章的末尾提了. 下面讲一下这个Trie树的原理吧. hbase源码系列(五)单词查找树,布布扣,bubuko.com

hbase源码系列(十二)Get、Scan在服务端是如何处理?

继上一篇讲了Put和Delete之后,这一篇我们讲Get和Scan, 因为我发现这两个操作几乎是一样的过程,就像之前的Put和Delete一样,上一篇我本来只打算写Put的,结果发现Delete也可以走这个过程,所以就一起写了. Get 我们打开HRegionServer找到get方法.Get的方法处理分两种,设置了ClosestRowBefore和没有设置的,一般来讲,我们都是知道了明确的rowkey,不太会设置这个参数,它默认是false的. if (get.hasClosestRowBef

hbase源码系列(十)HLog与日志恢复

HLog概述 hbase在写入数据之前会先写入MemStore,成功了再写入HLog,当MemStore的数据丢失的时候,还可以用HLog的数据来进行恢复,下面先看看HLog的图. 旧版的HLog是实际上是一个SequceneFile,0.96的已经使用Protobuf来进行序列化了.从Writer和Reader上来看HLog的都是Entry的,换句话说就是,它的每一条记录就是一个Entry. class Entry implements Writable { private WALEdit e

hbase源码系列(十四)Compact和Split

先上一张图讲一下Compaction和Split的关系,这样会比较直观一些. Compaction把多个MemStore flush出来的StoreFile合并成一个文件,而Split则是把过大的文件Split成两个. 之前在Delete的时候,我们知道它其实并没有真正删除数据的,那总不能一直不删吧,下面我们就介绍一下它删除数据的过程,它就是Compaction. 在讲源码之前,先说一下它的分类和作用. Compaction主要起到如下几个作用: 1)合并文件 2)清除删除.过期.多余版本的数据

hbase源码系列(二)HTable 如何访问客户端

hbase的源码终于搞一个段落了,在接下来的一个月,着重于把看过的源码提炼一下,对一些有意思的主题进行分享一下.继上一篇讲了负载均衡之后,这一篇我们从client开始讲吧,从client到master再到region server,按照这个顺序来开展,网友也可以对自己感兴趣的部分给我留言或者直接联系我的QQ. 现在我们讲一下HTable吧,为什么讲HTable,因为这是我们最常见的一个类,这是我们对hbase中数据的操作的入口. 1.Put操作 下面是一个很简单往hbase插入一条记录的例子.

hbase源码系列(七)Snapshot的过程

在看这一章之前,建议大家先去看一下snapshot的使用.可能有人会有疑问为什么要做Snapshot,hdfs不是自带了3个备份吗,这是个很大的误区,要知道hdfs的3个备份是用于防止网络传输中的失败或者别的异常情况导致数据块丢失或者不正确,它不能避免人为的删除数据导致的后果.它就想是给数据库做备份,尤其是做删除动作之前,不管是hbase还是hdfs,请经常做Snapshot,否则哪天手贱了... 直接进入主题吧,上代码. public void takeSnapshot(SnapshotDes

hbase源码系列(十三)缓存机制MemStore与Block Cache

这一章讲hbase的缓存机制,这里面涉及的内容也是比较多,呵呵,我理解中的缓存是保存在内存中的特定的便于检索的数据结构就是缓存. 之前在讲put的时候,put是被添加到Store里面,这个Store是个接口,实现是在HStore里面,MemStore其实是它底下的小子. 那它和Region Server.Region是什么关系? Region Server下面有若干个Region,每个Region下面有若干的列族,每个列族对应着一个HStore. HStore里面有三个很重要的类,在这章的内容都

hbase源码系列(十一)Put、Delete在服务端是如何处理?

在讲完之后HFile和HLog之后,今天我想分享是Put在Region Server经历些了什么?相信前面看了<HTable探秘>的朋友都会有印象,没看过的建议回去先看看,Put是通过MultiServerCallable来提交的多个Put,好,我们就先去这个类吧,在call方法里面,我们找到了这句. responseProto = getStub().multi(controller, requestProto); 它调用了Region Server的multi方法.好,我们立即杀到HReg

hbase源码系列(三)Client如何找到正确的Region Server

客户端在进行put.delete.get等操作的时候,它都需要数据到底存在哪个Region Server上面,这个定位的操作是通过HConnection.locateRegion方法来完成的. loc = hConnection.locateRegion(this.tableName, row.getRow()); 这里我们首先要讲hbase的两张元数据表-ROOT-和.META.表,它们一个保存着region的分部信息,一个保存着region的详细信息.在<hbase实战>这本书里面详细写了