Hbase scanner逆向查询原理(上)客户端

一、背景

一直听说hbase scanner的逆向查询比正向查询要慢很多,而且多消耗了很多资源。原因在哪里,并没有找到明确的答案。分为客户端和服务端两篇,正向和逆向的原理以及其中的异同。本篇讨论客户端。参考的jar包版本是hbase-client-1.1.13。

二、代码分析

1. 创建Scanner

HTable.getScanner(Scan)
org.apache.hadoop.hbase.client.Scan负责提供产生Scanner需要的配置信息,包括startRow、stopRow、maxResultSize、caching等。下面是maxResultSize和caching对应的官网解释:


hbase.client.scanner.caching
Number of rows that we try to fetch when calling next on a scanner if it is not served from (local, client) memory. This configuration works together with hbase.client.scanner.max.result.size to try and use the network efficiently. The default value is Integer.MAX_VALUE by default so that the network will fill the chunk size defined by hbase.client.scanner.max.result.size rather than be limited by a particular number of rows since the size of rows varies table to table. If you know ahead of time that you will not require more than a certain number of rows from a scan, this configuration should be set to that row limit via Scan#setCaching. Higher caching values will enable faster scanners but will eat up more memory and some calls of next may take longer and longer times when the cache is empty. Do not set this value such that the time between invocations is greater than the scanner timeout; i.e. hbase.client.scanner.timeout.period
====>以下是上面的重点翻译:
hbase.client.scanner.caching配置的是一个int类型的值,表示一次RPC请求从Region Server端获取的数据行数。为什么叫做caching呢?由于每次next()是获取一行数据,但是一次RPC调用是多行,实际是先缓存在内存中,当某次next()发现缓存中没有数据时才会发起RPC调用。这个参数和hbase.client.scanner.max.result.size配合使用,可以使网络使用更有效率。hbase.client.scanner.caching默认值是Integer.MAX_VALUE,即231-1,这个时候就以hbase.client.scanner.max.result.size为准。

hbase.client.scanner.max.result.size
Maximum number of bytes returned when calling a scanner’s next method. Note that when a single row is larger than this limit the row is still returned completely. The default value is 2MB, which is good for 1ge networks. With faster and/or high latency networks this value should be increased.
====>以下是上面的重点翻译:
一次RPC调用返回的最大字节数。注意当表的一行数据大小大于这个限制,仍然完整返回完整的数据行。默认配置是2MB。对于更快或者高延迟的网络,这个值应该增加。


根据配置不同,可以产生4种不同的ClientScanner,分别是ClientScanner、ReversedClientScanner、ClientSmallScanner、ClientSmallReversedScanner。以上本文只讨论ClientScanner与ReversedClientScanner。见图1。


图1

正向查询走的ClientScanner,其中ReversedClientScanner是ClientScanner的子类,大部分操作是复用的ClientScanner。

2.通过Scanner获取数据

ClientScanner通过执行next()每次得到一条行记录,遍历得到查询结果。一次查询可以分别跨多个Region,在ClientScanner实例内部维护着多个ScannerCallable实例(并不是同时实例多个对象,注意,这里方便理解他们之间的关系),每个对应一个Region来获取数据,关系对应如图2。


图2

逆向查询是下面的关系


图3

下面对此过程进行详细说明。
2.1 获取region所在region server

ScannerCallable对象有两个比较重要的方法:prepare和call。其中prepare是每次向服务器发起RPC调用(就是call方法)获取数据前做的准备工作,call是从服务器获取数据。
prepare方法中根据用户表名和startRowKey在meta表中查询需要查询的region所在region server的连接信息。具体实现在:
org.apache.hadoop.hbase.client.ConnectionManager.locateRegionInMeta中。

2.2 读取数据

调用这个方法获取一个org.apache.hadoop.hbase.client.Result实例,表示一行数据,可以是多个Cell、一个Cell的多个版本。如果cache中有数据则从缓存中获取返回,如果缓存中没有数据则调用loadCache()方法,从服务器端获取多行数据,条数根据org.apache.hadoop.hbase.client.Scan中的caching配置,同时受到maxResultSize参数影响。然后再从缓存中返回一行数据。此处缓存通过LinkedList实现。
ClientScanner.next()是对外提供给用户遍历Scan命中的数据用的,内部隐藏了如何切换Region的逻辑,通过调用方法

protected boolean nextScanner(int nbRows, final boolean done)

切换当前使用的ScannerCallable对象。例如:一共{1,2,3,4,5,6} 6条数据,其中{1,2,3}在Region A上,{4,5,6}在Region B上。用户代码中只需要调用ClientScanner.next()循环获取数据即可,一开始实例化ScannerCallable(A)用于查询Region A的数据,假设caching是1,每次读取1条数据。当读取到3之后,到达当前Region的最后一条数据,下一次next()会新实例化ScannerCallable(B)并开始读取4以及后面的数据。

3.正向Scan和逆向Scan在有哪些不同

3.1 ReversedClientScanner vs ClientScanner

ReversedClientScanner是ClientScanner的子类,重写了nextScanner和checkScanStopRow方法,前者用于切换下一个Region,后者判断本次查询是否读取完毕。

逆向
逆向.png
正向
正向.png
3.2 ReversedScannerCallable vs ScannerCallable

区别在于获取region server的方式上。ScannerCallable中的时间复杂度是O(1),而ReversedScannerCallabe中的时间复杂度是O(n),但事实上这个n并不大,可以近似于O(1)了。最大的区别在于ReversedScannerCallabe中获取region server地址的locateRegionsInRange方法。以下为源码:

/**
   * Get the corresponding regions for an arbitrary range of keys.
   * @param startKey Starting row in range, inclusive
   * @param endKey Ending row in range, exclusive
   * @param reload force reload of server location
   * @return A list of HRegionLocation corresponding to the regions that contain
   *         the specified range
   * @throws IOException
   */
  private List<HRegionLocation> locateRegionsInRange(byte[] startKey,
      byte[] endKey, boolean reload) throws IOException {
    final boolean endKeyIsEndOfTable = Bytes.equals(endKey,
        HConstants.EMPTY_END_ROW);
    if ((Bytes.compareTo(startKey, endKey) > 0) && !endKeyIsEndOfTable) {
      throw new IllegalArgumentException("Invalid range: "
          + Bytes.toStringBinary(startKey) + " > "
          + Bytes.toStringBinary(endKey));
    }
    List<HRegionLocation> regionList = new ArrayList<HRegionLocation>();
    byte[] currentKey = startKey;
    do {
      RegionLocations rl = RpcRetryingCallerWithReadReplicas.getRegionLocations(!reload, id,
          getConnection(), getTableName(), currentKey);
      HRegionLocation regionLocation = id < rl.size() ? rl.getRegionLocation(id) : null;
      if (regionLocation != null && regionLocation.getRegionInfo().containsRow(currentKey)) {
        regionList.add(regionLocation);
      } else {
        throw new DoNotRetryIOException("Does hbase:meta exist hole? Locating row "
            + Bytes.toStringBinary(currentKey) + " returns incorrect region "
            + regionLocation.getRegionInfo());
      }
      currentKey = regionLocation.getRegionInfo().getEndKey();
    } while (!Bytes.equals(currentKey, HConstants.EMPTY_END_ROW)
        && (endKeyIsEndOfTable || Bytes.compareTo(currentKey, endKey) < 0));
    return regionList;
  }

ScannerCallable的O(1)代码为:

RegionLocations rl = RpcRetryingCallerWithReadReplicas.getRegionLocations(!reload,
        id, getConnection(), getTableName(), getRow());
    location = id < rl.size() ? rl.getRegionLocation(id) : null;

三、结论

导致逆向查询慢于正向查询和客户端的关系不大。问题应该出在服务端上,下一篇进行服务端的分析。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 一、简介 Hbase:全名Hadoop DataBase,是一种开源的,可伸缩的,严格一致性(并非最终一致性)的分...
    菜鸟小玄阅读 2,362评论 0 12
  • HBase总结 一、数据模型 1.数据模型例子 2.数据模型解析 1)存储(keyvalue) HBase什么样...
    农民2019阅读 953评论 0 0
  • 和写流程相比,HBase读数据是一个更加复杂的操作流程,这主要基于两个方面的原因: 其一是因为整个HBase存储引...
    丨程序之道丨阅读 918评论 0 2
  • hbase scan客户端服务端流程 一:基础知识了解: scanner可分为两种InternalScanner和...
    sunTengSt阅读 971评论 0 2
  • 昨天看了一档美食节目,其中一位嘉宾讲了他在国外的经历。说看到街上有人放烟火,于是他也要放,他的一位朋友拦住他,“不...
    杰克言JACKYAN阅读 257评论 0 1