入口
org.apache.hadoop.hbase.io.hfile.HFileReaderImpl.HFileScannerImpl#seekTo(org.apache.hadoop.hbase.Cell, boolean) :
public int seekTo(Cell key, boolean rewind) throws IOException {
HFileBlockIndex.BlockIndexReader indexReader = reader.getDataBlockIndexReader();
BlockWithScanInfo blockWithScanInfo = indexReader.loadDataBlockWithScanInfo(key, curBlock,
cacheBlocks, pread, isCompaction, getEffectiveDataBlockEncoding(), reader);
if (blockWithScanInfo == null || blockWithScanInfo.getHFileBlock() == null) {
// This happens if the key e.g. falls before the beginning of the file.
return -1;
}
return loadBlockAndSeekToKey(blockWithScanInfo.getHFileBlock(),
blockWithScanInfo.getNextIndexedKey(), rewind, key, false);
}
大逻辑分为两步走:
- 检索 index block 定位到 data block 的位置;
- 在 data block 中根据 key进行游标定位。
检索 index block
- 获取 root index block(该 block 在 load-on-open 阶段就已经被加载并解析完毕);
- 通过二分查找在 root index block 种定位 entry,这里 key 的对比逻辑会走 CellComparatorImpl#compare(Cell, Cell),右边是 KeyValue$KeyOnlyKeyValue 如果发现比最小的 key 小,则直接返回 -1(标识游标停在文件初);
这里会开启一个循环,目的是为了定位到具体的 data block。根据 entry 取下一个要读的 block,从 entry 取出要读的 block 的 offset 和 size,以读取下一个 block,根据 lookupLevel 和 searchTreeLevel 判断要读的 block 的类型:
- searchTreeLevel 是索引树的高度,为 1 说明只有 root index block,为 2 说明 root index block 指向 leaf index block,大于 2 则中间有 n-2 层 intermediate-level block,以此类推;
- lookupLevel 是当前读到的 level,如果小于 searchTreeLevel 说明还要读 index block,否则就要读 data block;
查看读出来的 block 类型,如果是 data block 则跳出循环,否则:
- 查看该 key 在该 index block 中的位置,这里也使用二分,右边的实现是 BytebufferKeyOnlyKeyValue:HFileBlockIndex#locateNonRootIndexEntry -> HFileBlockIndex#binarySearchNonRootIndex -> PrivateCellUtil#compareKeyIgnoresMvcc
- 如果小于 0(即小于 index block 记录的最小值),则抛出异常:The key xxx is before the first key of the non-root index block,因为在上一步已经确定该 key 在该 hfile 的范围里;
- 定位到具体的 entry(这里会修改 index block 里 buffer 的 position),用于检索下一层 block;
- 回到上一层的第三步,直到找到 data block。
检索 data block
调用 HFileReaderImpl.HFileScannerImpl#loadBlockAndSeekToKey -> HFileReaderImpl.HFileScannerImpl#blockSeek,遍历 data block 中的数据(BytebufferKeyOnlyKeyValue)和指定 key 对比:
- blockSeek 有个参数 seekBefore(默认为 false),用于控制当找到匹配上的 data entry 时,是否需要回退一个单位,如果回退,则返回 1,否则返回 0(0 表示精准匹配,游标停在匹配的位置);
- 如果第一次匹配就发现 cmp <0(即比 data block 的第一个 data entry 都要小),则返回 -2;
- 不断往下匹配,直至找到第一个大于指定 key 的 data entry(cmp<0),将该 block 的 buffer position 回滚一个单位,返回 1(表示游标停在最后一个小于 key 的 entry 上,下一个 entry 就比 key 大了)
Seek 的使用场景
Seek 主要应用于两类场景:
点查:给定 key,查找符合条件的该 key 最新版本的数据。考虑到 HBase 的数据会包含 timestamp,需要考虑较为复杂的,同 key 不同 timestamp 的情况:
- 点查的 key 小于文件第一条数据:底层 scanner seek 返回 -1/-2。此时调用 seekTo 使 scanner 游标停在文件初始位置;
- 底层 scanner 的 seek 返回 0。精准匹配,直接读取数据即可。
- 底层 scanner seek 返回 1。由于 scanner 会回退一个数据单位,可直接读取当前数据比对 key。
- 区间扫描:给定 key,扫描大于等于该 key 的所有数据。该场景和点查的逻辑有一点不同,即 scanner seek 返回 1 的时候,需要往下推进一个数据单位,因为 HBase 做 seek 操作时都会有一次回退。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。