埃尔法哥哥■深入HBase读写( 二 )


而对于hbase服务端来说 , 当一个Get请求过来后 , 还是会转换为一个特殊的scan请求 , 即startrow和endrow一致的Scan请求 。 所以 , 下文的介绍 , 就围绕scan展开 。
首先 , 我们要知道 , HBase的写入很快 , 是追加多版本的形式 , 删除也很快 , 只是插入一条打上“deteled”标签的数据 。 因此 , hbase的读操作比较复杂的 , 需要处理各种状态和关系 。
因为Store是按照columfamily来划分的 , 一张表由N个列族组成 , 就有N个StoreScanner负责该列族的数据扫描 。
当client要查询一个region , 那么就会有一个RegionScanne , 这个regionscannerr会创建N个StoreScanner 。
而一个store由多个storefile和一个memstore组成 ,
因此 , StoreScanner对象会创建一个MemStoreScanner和多个StoreFileScanner进行实际数据的读取 。
这些scanner首先根据TimeRange和RowKeyRange过滤掉一部分肯定无用的StoreFileScanner 。
剩下的scanner组成一个最小堆KeyValueHeap 。 这个最小堆的实际数据结构是一个优先级队列 , 队列中所有元素是scanner , 根据scanner指向的keyvalue进行排序(scanner类似游标 , 每次查询一个结果后 , 通过next下移找下一个kv值) 。
举个简单的例子 。
埃尔法哥哥■深入HBase读写
文章图片
假设有4个scanner组成的优先级队列 , 分布标记为ScannerABCD 。
1)查询的时候首先pop出heap的堆顶元素 。
2)第一次pop出来的是scannerA 。 调用next请求 , 将会返回ScannerA中的rowA:cf:colA , 而后ScannerA的指针移动到下一个KeyValuerowA:cf:colB;
3)重新组织堆中元素 , 堆中的Scanners排序不变;
4)第二次pop出来的还是scannerA 。 调用next请求 , 返回ScannerA中的rowA:cf:colB , ScannerA的current指针移动到下一个KeyValuerowB:cf:ColA;
5)重新组织堆中元素 , 由于此时scannerA的指针指向了rowB , 按照KeyValue排序可知rowB小于rowA,所以堆内部 , scanner顺序发生改变 , 改变之后如下图所示:
6)第三次pop出来的就是ScannerB了 。
以此类推 。
埃尔法哥哥■深入HBase读写
文章图片
当某个scanner内部数据完全检索之后会就会被close掉 , 或者rowA所有数据检索完毕 , 则查询下一条 。
【埃尔法哥哥■深入HBase读写】默认情况下返回的数据需要经过ScanQueryMatcher过滤返回的数据需要满足下面的条件:
该KeyValue不是已经删除的数据(KeyType不是Deleted/DeletedCol等)如果是就直接忽略该列所有其他版本 , 跳到下个列族;
该KeyValue的Timestamp是在用户设定的TimestampRange范围内
该KeyValue满足用户设置的各种filter过滤器
该KeyValue满足用户查询中设定的版本数 , 比如用户只查询最新版本 , 则忽略该cell的其他版本;反正如果用户查询所有版本 , 则还需要查询该cell的其他版本 。
至此 , 就是HBase大致上的读写流程 。
我们经常听说HBase数据读取要读Memstore、HFile和Blockcache , 为什么我们这里说Scanner只有StoreFileScanner和MemstoreScanner , 而没有BlockcacheScanner呢?
因为HBase中数据仅独立地存在于Memstore和StoreFile中 , Blockcache作为读缓存 , 里面有StoreFile中的部分热点数据 , 因此 , 如果有数据存在于Blockcache中 , 那么这些数据必然存在StoreFile中 。 因此使用MemstoreScanner和StoreFileScanner就可以覆盖到所有数据 。
而在实际的读操作时 , StoreFileScanner通过索引定位到待查找key所在的block之后 , 会先去查看该block是否存在于Blockcache中 , 如果存在 , 那么就会去BlockCache中取出 , 避免IO , 如果BlockCache中不存在 , 才会再到对应的StoreFile中读取 。


推荐阅读