这里面说的read既包括get,也包括scan,实际底层来看这两个操作也是一样的。
我们将要讨论的是,当我们从一张表读取数据的时候hbase到底是怎么处理的。
分二种情况来看,第一种就是表刚创建,所有put的数据还在memstore中,并没有刷新到hdfs上;第二种情况是,该store已经进行多次的flush操作,产生了多个storefile了。
在具体说明两种情况前,先考虑下表的region的问题,如果表只有一个region,那么没有说的,肯定是要扫描这个唯一的region。假设该表有多个region,此时.META.表就派上用场了,hbase会首先根据你要扫描的数据的rowkey来判断到底该数据放在哪个region上,该region所在服务器地址,然后把数据读取的请求发送给该region server。好了,实际对数据访问的任务都会放在region server上执行,为了简单起见,接下来的讨论都是在单台region server上对单个region的操作。
首先来看第一种情况,表刚创建,所有put的数据还在memstore中,并没有刷新到hdfs上。这个时候数据是在memstore中,并没有storefile产生,理所当然,hbase要查找memstore来获得相应的数据。对于memstore或者storefile来说,内存中都有关于rowkey的索引的,所以对于通过rowkey的查询速度是非常快速的。通过查询该索引就知道是否存在需要查看的数据,已经该数据在memstore中的位置。通过索引提供的信息就很容易找得到所需要的数据。这种情况很简单。
在来看第二种情况,该store已经进行多次的flush操作,产生了多个storefile了。那么数据应该从哪里查呢?所有的storefile?别忘记还有memstore。此时memstore中可能还会有没来得及flush的数据呢。如果此时该region还有很多的文件,是不是所有的文件都需要查找呢?hbase在查找先会根据时间戳或者查询列的信息来进行过滤,过滤掉那些肯定不含有所需数据的storefile或者memstore,尽量把我们的查询目标范围缩小。
尽管缩小了,但仍可能会有多个文件需要扫描的。storefile的内部有三维有序的,但是各个storefile之间并不是有序的。比如,storefile1中可能有rowkey为100到110的记录,而storefile2可能有rowkey为105到115的数据,storefile的rowkey的范围很有可能有交叉。所以查询数据的过程也不可能是对storefile的顺序查找。
hbase会首先查看每个storefile的最小的rowkey,然后按照从小到大的顺序进行排序,结果放到一个队列中,排序的算法就是按照hbase的三维顺序,按照rowkey,column,ts进行排序,rowkey和column是升序,而ts是降序。
实际上并不是所有满足时间戳和列过滤的文件都会加到这个队列中,hbase会首先对各个storefile中的数据进行探测,只会扫描扫描那些存在比当前查询的rowkey大的记录的storefile。举例来说,我当前要查找的rowkey为108,storefile1中rowkey范围为100~104,storefile2中rowkey的范围为105~110,那么对于storefile1最大的rowkey为104,小于105,所以不存在比所查rowkey105大的记录,storefile并不会被加到该队列中。根据相同的规则,storefile2则会被添加到该队列中。
队列有了,下面开始查询数据,首先通过poll取出队列的头storefile,会从storefile读取一条记录返回;接下来呢,该storefile的下条记录并不一定是查询结果的下一条记录,因为队列的比较顺序是比较的每个storefile的第一条符合要求的rowkey。所以,hbase会继续从队列中剩下的storefile取第一条记录,把该记录与头storefile的第二条记录做比较,如果前者大,那么返回头storefile的第二条记录;如果后者大,则会把头storefile放回队列重新排序,在重新取队列的头storefile。然后重复上面的整个过程。这个过程比较烦,语言描述不清楚,代码会更加清晰。
这段代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public KeyValue next() throws IOException { if(this.current == null) { return null; } KeyValue kvReturn = this.current.next(); KeyValue kvNext = this.current.peek(); if (kvNext == null) { this.current.close(); this.current = this.heap.poll(); } else { KeyValueScanner topScanner = this.heap.peek(); if (topScanner == null || this.comparator.compare(kvNext, topScanner.peek()) >= 0) { this.heap.add(this.current); this.current = this.heap.poll(); } } return kvReturn; }
|
以上的代码在KeyValueHeap.
java类中。
举个例子来说明:表sunwg01,有两个storefile,storefile1中包括rowkey100,rowkey110;storefile2中包括rowkey104,rowkey108。我现在执行scan ‘sunwg01′扫描表sunwg01中的所有的记录。
根据前面提到的排序规则,队列中会有2个元素,按顺序分别为storefile1,storefile2。
1,取出storefile1中的第一条记录rowkey100,并返回该结果
2,取出storefile1中的下一条记录rowkey110,同时取出队列剩余storefile的第一条记录rowkey104,经过比较rowkey110大于rowkey104,则将storefile1放回队列中
3,因为队列是有序的队列,会重新对storefile进行排序,因为此时storefile1的最小rowkey为110,而storefile2的最小rowkey为104,所以排序的结果为storefile2,storefile1
4,重复上面的过程,直到查不到记录为止。
最后查到的结果为:rowkey100,rowkey104,rowkey108,rowkey110。
顺便说下block cache的事情,当从storefile中读数据的时候会首先查看block cache中是否有该数据,如果有则直接查block cache,就没必要查询hdfs;如果没有该数据,那么就只能去查hdfs了。这也是为了block cache的命中率对性能有很大影响的原因。
上面描述了从hbase中read的基本的过程,还有些细节没有具体说,但是大概过程应该是都说到了。