0. 蜜汁參數(shù)
在做 HBase 客戶端 scan 優(yōu)化時(shí)榛臼,經(jīng)常會(huì)碰到以下幾個(gè)參數(shù)峻厚,總是讓人迷惑 ,不知從何優(yōu)化起骤铃。
-
.setCache
(緩存大小? 字節(jié)數(shù)拉岁?行數(shù)?) -
.setMaxResultSize
(最大結(jié)果數(shù)惰爬?) -
.setBatch
(批量喊暖?)
造成這種困擾很大的原因是命名問題。
先說下結(jié)論撕瞧,如果把名字改成如下陵叽,語義會(huì)清晰很多 。[1]
-
.setCaching
=>.setNumberOfRowsFetchSize
(客戶端每次 rpc fetch 的行數(shù)) -
.setMaxResultSize
=>.setMaxResultByteSize
(客戶端緩存的最大字節(jié)數(shù)) -
.setBatch
=>.setColumnsChunkSize
(客戶端每次獲取的列數(shù))
1. Client Scan 原理及相關(guān)源碼解讀
HBase 每次 scan 的數(shù)據(jù)量可能會(huì)比較大丛版,客戶端不會(huì)一次性全部把數(shù)據(jù)從服務(wù)端拉回來巩掺。而是通過多次 rpc 分批次的拉取。類似于 TCP 協(xié)議里面一段一段的傳輸页畦,可以做到細(xì)粒度的流量控制胖替。至于如何調(diào)優(yōu),控制每次 rpc 拉取的數(shù)據(jù)量,就可以通過以上三個(gè)比較蛋疼的參數(shù)來控制独令。
我們可以先看一段來自 HBase scan 里面的核心類 ClientScanner 里的讀取邏輯端朵,通過它來了解整個(gè)流程。
@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 調(diào)用讀取數(shù)據(jù)進(jìn)緩存
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 , 實(shí)際上 cache 是通過一個(gè)鏈表來實(shí)現(xiàn)的逸月,定義如下:
protected final LinkedList<Result> cache = new LinkedList<Result>();
繼續(xù)看 loadCache() ,為了弄清大體主流程遍膜,我刪除了部分代碼
protected void loadCache() throws IOException {
Result[] values = null;
// 剩余最大容量
long remainingResultSize = maxScannerResultSize;
// 行數(shù)計(jì)數(shù) 為 setCaching 的值
int countdown = this.caching;
// 配置 rpc 請(qǐng)求的條數(shù)
callable.setCaching(this.caching);
boolean serverHasMoreResults = false;
// do while 循環(huán)碗硬,循環(huán)次數(shù)即為 rpc 次數(shù)
do {
try {
// rpc 從 server 拉數(shù)據(jù),請(qǐng)求的條數(shù)為 this.caching 默認(rèn)為 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
// 將數(shù)據(jù)放入緩存前瓢颅,先對(duì)數(shù)據(jù)進(jìn)行一些處理恩尾,主要是處理對(duì)于部分對(duì)調(diào)用這不可見的數(shù)據(jù)
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()) {
// 估算每個(gè) cell 的大小,計(jì)算剩余 byte size
remainingResultSize -= CellUtil.estimatedHeapSizeOf(cell);
}
// 剩余行數(shù) --
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)));
// 循環(huán)條件
}
這里重點(diǎn)看下do while 循環(huán)的條件 doneWithRegion()
/**
* @param remainingResultSize
* @param remainingRows
* @param regionHasMoreResults
*/
private boolean doneWithRegion(long remainingResultSize, int remainingRows,
boolean regionHasMoreResults) {
// 同時(shí)滿足這些才行這里的
// remainingResultSize 初始值即為配置的 setMaxResultSize
// remainingRows 初始值為配置的 setCaching (實(shí)際為:行數(shù))
return remainingResultSize > 0 && remainingRows > 0 && !regionHasMoreResults;
}
因?yàn)槊看?while 循環(huán)會(huì)進(jìn)行一次 rpc 調(diào)用挽懦,不同的參數(shù)配置組合配置導(dǎo)致 rpc 調(diào)用的次數(shù)不同翰意。必須同時(shí)滿足行數(shù)與字節(jié)數(shù)的的限制才行。
3. 官方配置與建議
這幾個(gè)參數(shù)對(duì)應(yīng)的配置如下
hbase.client.scanner.caching - (setCaching)
:HBase-0.98 默認(rèn)值為為 100信柿,HBase-1.2 默認(rèn)值為 2147483647冀偶,即 Integer.MAX_VALUE。Scan.next() 的一次 RPC 請(qǐng)求 fetch 的記錄條數(shù)渔嚷。配置建議:這個(gè)參數(shù)與 下面的hbase.client.scanner.max.result.size - (setMaxResultSize)
配置使用进鸠,在網(wǎng)絡(luò)狀況良好的情況下,自定義設(shè)置不宜太小形病, 可以直接采用默認(rèn)值客年,不配置。hbase.client.scanner.max.result.size - (setMaxResultSize)
:HBase-0.98 無該項(xiàng)配置漠吻,HBase-1.2 默認(rèn)值為 210241024量瓜,即 2M。Scan.next() 的一次 RPC 請(qǐng)求 fetch 的數(shù)據(jù)量大小途乃,目前 HBase-1.2 在 Caching 為默認(rèn)值(Integer Max)的時(shí)候绍傲,實(shí)際使用這個(gè)參數(shù)控制 RPC 次數(shù)和流量。配置建議:如果網(wǎng)絡(luò)狀況較好(萬兆網(wǎng)卡)耍共,scan 的數(shù)據(jù)量非常大烫饼,可以將這個(gè)值配置高一點(diǎn)。如果配置過高:則可能 loadCache 速度比較慢划提,導(dǎo)致 scan timeout 異常hbase.server.scanner.max.result.size
:服務(wù)端配置枫弟。HBase-0.98 無該項(xiàng)配置,HBase-1.2 新增鹏往,默認(rèn)值為 10010241024淡诗,即 100M骇塘。該參數(shù)表示當(dāng) Scan.next() 發(fā)起 RPC 后,服務(wù)端返回給客戶端的最大字節(jié)數(shù)韩容,防止 Server OOM款违。[2]setBatch()
坑爹的命名,這個(gè)實(shí)際上是配置獲取的列數(shù)群凶,假如表有兩個(gè)列簇 cf插爹,info,每個(gè)列簇5個(gè)列请梢。這樣每行可能有10列了赠尾,setBatch() 可以控制每次獲取的最大列數(shù),進(jìn)一步從列級(jí)別控制流量毅弧。配置建議:當(dāng)列數(shù)很多气嫁,數(shù)據(jù)量大時(shí)考慮配置此參數(shù),例如100列每次只獲取50列够坐。一般情況可以默認(rèn)值(-1 不受限)寸宵。
參考:
[1] HBase client’s weird API names
[2] HBase Client配置參數(shù)說明