HBase Scan 中 setCaching setMaxResultSize setBatch 解惑

0. 蜜汁参数

在做 HBase 客户端 scan 优化时,经常会碰到以下几个参数,总是让人迷惑 ,不知从何优化起。

  • .setCache (缓存大小? 字节数?行数?)
  • .setMaxResultSize (最大结果数?)
  • .setBatch (批量?)

造成这种困扰很大的原因是命名问题。
先说下结论,如果把名字改成如下,语义会清晰很多 。[1]

  • .setCaching => .setNumberOfRowsFetchSize (客户端每次 rpc fetch 的行数)
  • .setMaxResultSize => .setMaxResultByteSize (客户端缓存的最大字节数)
  • .setBatch => .setColumnsChunkSize (客户端每次获取的列数)

1. Client Scan 原理及相关源码解读

HBase 每次 scan 的数据量可能会比较大,客户端不会一次性全部把数据从服务端拉回来。而是通过多次 rpc 分批次的拉取。类似于 TCP 协议里面一段一段的传输,可以做到细粒度的流量控制。至于如何调优,控制每次 rpc 拉取的数据量,就可以通过以上三个比较蛋疼的参数来控制。

我们可以先看一段来自 HBase scan 里面的核心类 ClientScanner 里的读取逻辑,通过它来了解整个流程。

@Override
public Result next() throws IOException {
  // If the scanner is closed and there's nothing left in the cache, next is a no-op.
  if (cache.size() == 0 && this.closed) {
    return null;
  }

  // 缓冲中没有就 RPC 调用读取数据进缓存
  if (cache.size() == 0) {   
        loadCache();
  }
 
  // 缓冲中有直接从缓存中取
  if (cache.size() > 0) {
    return cache.poll();
  }
 
  // if we exhausted this scanner before calling close, write out the scan metrics
  writeScanMetrics();
  return null;
}

每次从缓存 cache 中读,缓存为空则 loadCache , 实际上 cache 是通过一个链表来实现的,定义如下:
protected final LinkedList<Result> cache = new LinkedList<Result>();

继续看 loadCache() ,为了弄清大体主流程,我删除了部分代码

 protected void loadCache() throws IOException {
    Result[] values = null;
    // 剩余最大容量
    long remainingResultSize = maxScannerResultSize;
    // 行数计数 为 setCaching 的值
    int countdown = this.caching;
    // 配置 rpc 请求的条数
    callable.setCaching(this.caching);
    boolean serverHasMoreResults = false;

    // do while 循环,循环次数即为 rpc 次数
    do {
      try {
        // rpc 从 server 拉数据,请求的条数为 this.caching 默认为 Integer.Max_VALUE
        values = call(callable, caller, scannerTimeout);
         } catch (DoNotRetryIOException | NeedUnmanagedConnectionException e) {
             // 异常处理 这里略过
       }
      // Groom the array of Results that we received back from the server before adding that
      // Results to the scanner's cache
      // 将数据放入缓存前,先对数据进行一些处理,主要是处理对于部分对调用这不可见的数据
      List<Result> resultsToAddToCache =
          getResultsToAddToCache(values, callable.isHeartbeatMessage());
      if (!resultsToAddToCache.isEmpty()) {
        // 遍历 results 写入 cache
        for (Result rs : resultsToAddToCache) {
          cache.add(rs);
          // We don't make Iterator here
          for (Cell cell : rs.rawCells()) {
            // 估算每个 cell 的大小,计算剩余 byte size 
            remainingResultSize -= CellUtil.estimatedHeapSizeOf(cell);
          }
          // 剩余行数 --
          countdown--;
          this.lastResult = rs;
        }
      }

      // We expect that the server won't have more results for us when we exhaust
      // the size (bytes or count) of the results returned. If the server *does* inform us that
      // there are more results, we want to avoid possiblyNextScanner(...). Only when we actually
      // get results is the moreResults context valid.
      if (null != values && values.length > 0 && callable.hasMoreResultsContext()) {
        serverHasMoreResults = callable.getServerHasMoreResults() & partialResults.isEmpty();
      }
      // Values == null means server-side filter has determined we must STOP
    } while (doneWithRegion(remainingResultSize, countdown, serverHasMoreResults)
        && (!partialResults.isEmpty() || possiblyNextScanner(countdown, values == null)));
     // 循环条件
  }

这里重点看下do while 循环的条件 doneWithRegion()

  /**
   * @param remainingResultSize
   * @param remainingRows
   * @param regionHasMoreResults
  */
  private boolean doneWithRegion(long remainingResultSize, int remainingRows,
      boolean regionHasMoreResults) {
    // 同时满足这些才行这里的
    // remainingResultSize 初始值即为配置的 setMaxResultSize 
   //  remainingRows 初始值为配置的 setCaching (实际为:行数)
    return remainingResultSize > 0 && remainingRows > 0 && !regionHasMoreResults;
  }

因为每次 while 循环会进行一次 rpc 调用,不同的参数配置组合配置导致 rpc 调用的次数不同。必须同时满足行数与字节数的的限制才行。

3. 官方配置与建议

这几个参数对应的配置如下

  • hbase.client.scanner.caching - (setCaching):HBase-0.98 默认值为为 100,HBase-1.2 默认值为 2147483647,即 Integer.MAX_VALUE。Scan.next() 的一次 RPC 请求 fetch 的记录条数。配置建议:这个参数与 下面的hbase.client.scanner.max.result.size - (setMaxResultSize) 配置使用,在网络状况良好的情况下,自定义设置不宜太小, 可以直接采用默认值,不配置。

  • hbase.client.scanner.max.result.size - (setMaxResultSize):HBase-0.98 无该项配置,HBase-1.2 默认值为 210241024,即 2M。Scan.next() 的一次 RPC 请求 fetch 的数据量大小,目前 HBase-1.2 在 Caching 为默认值(Integer Max)的时候,实际使用这个参数控制 RPC 次数和流量。配置建议:如果网络状况较好(万兆网卡),scan 的数据量非常大,可以将这个值配置高一点。如果配置过高:则可能 loadCache 速度比较慢,导致 scan timeout 异常

  • hbase.server.scanner.max.result.size:服务端配置。HBase-0.98 无该项配置,HBase-1.2 新增,默认值为 10010241024,即 100M。该参数表示当 Scan.next() 发起 RPC 后,服务端返回给客户端的最大字节数,防止 Server OOM。[2]

  • setBatch() 坑爹的命名,这个实际上是配置获取的列数,假如表有两个列簇 cf,info,每个列簇5个列。这样每行可能有10列了,setBatch() 可以控制每次获取的最大列数,进一步从列级别控制流量。配置建议:当列数很多,数据量大时考虑配置此参数,例如100列每次只获取50列。一般情况可以默认值(-1 不受限)。

参考:
[1] HBase client’s weird API names
[2] HBase Client配置参数说明

    原文作者:tracyao
    原文地址: https://www.jianshu.com/p/c7bc56fe93c3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞