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配置参数说明