天天看點

大資料最佳實踐 | HBase用戶端

1減少RPC調用的方法

1.1.問題提出

HBase中rowkey是索引,任何對全表的掃描或是統計都需要用到scan接口,一般都是通過next()方法擷取資料。而每一個next()調用都會為每行資料生成一個單獨的RPC請求,這樣會産生大量的RPC請求,性能不會很好。

1.2.解決思路

如果執行一次RPC請求就可以擷取多行資料,那肯定會大大提高系統的性能。這一塊主要分為面向行級的緩存以及面向列級的緩存:

1)面向行級的緩存

我們可以通過使用掃描緩存方法來實作,不過這個緩存預設是關閉的,要用得打開。在表的層面使用時,這個表所有的掃描執行個體的緩存都會生效,在掃描層面也隻會影響目前的掃描執行個體。

使用者可以使用HTable.setScannerCaching()方法設定表級的掃描緩存,以及使用Scan.setCaching()方法設定掃描級的緩存。

2)面向列級的批量

使用者可以使用Scan.setBatch()方法設定傳回多少列。

通過組合使用掃描器緩存和批量大小的方式,可以讓使用者友善的控制掃描一個範圍内的行健時所需要的RPC調用次數。

1.3.實踐情況

舉例如下:

我們建立了一張有兩個列族的表,添加了10行資料,每個行的每個列族下有10列。這意味着整個表一共有200列(或單元格,因為每個列隻有一個版本),其中每行有20列。公式如下:

RPC請求的次數 =(行數×每行的列數)/Min(每行的列數,批量大小)/掃描器緩存

表說明如下:

緩存 批量處理 Result個數 RPC次數 說明
1 200 201 每個列都作為一個Result執行個體傳回。最後還多一個RPC确認掃描完成。
2 每個Result執行個體都隻包含一列的值,不過它們都被一次RPC請求取回(加一次完成檢查)。
10 20 11 批量參數是一行所包含的列數的一半,是以200列除以10,需要20個Result執行個體。同時需要10次RPC請求取回(加一次完成檢查)。
5 100 3 對于一行來講,這個批量參數太大了,是以一行的20列都被放入了一個Result執行個體中。同時緩存為5,是以10個Result執行個體被兩次RPC請求取回(加一次完成檢查)。
同上,不過這次的批量值與一行的列數正好相同,是以輸出與上面一種情況相同。
這次把表分成了較小的Result執行個體,但使用了較大的緩存值,是以也是隻用了兩次RPC請求就取回了資料。

要計算一次掃描操作的RPC請求的次數,使用者需要先計算出行數和每行列數的乘積(至少了解大概情況)。然後用這個值除以批量大小和每行列數中較小的那個值。最後再用除得的結果除以掃描器緩存值。

1.4.效果評價

合理的組合使用掃描器緩存和批量大小,可以有效的減少client端和伺服器的RPC互動次數,提供系統整體性能。

1.5.注意事項

  • scanner需要通過用戶端的記憶體來維持這些被cache的行記錄,合理設定catching大小,防止出現OOM;
  • cache使用的記憶體計算公式為:并發數×cache數×單個result大小。

2用戶端其它最佳實踐方法

2.1.問題提出

平常情況下,很多的應用主要是通過使用用戶端來通路HBase叢集,進而完成業務。是以整個系統的性能有很大一部分依賴于用戶端的性能。用戶端的開發主要是使用HBase提供的API,往往又由于不同的程式員對API的掌握程度不一,導緻了用戶端的性能差别很大。

2.2.解決思路

用戶端是使用HBase提供的API來完成讀寫資料,是以我們針對API的使用整理了一些最佳實踐。

1)禁止自動重新整理

當有大量的寫入操作時,使用setAutoFlush(false)方法,确認HTable自動重新整理的特性已經被關閉。否則Put執行個體将會被逐個傳送到region伺服器。通過HTable.add(Put)添加的Put執行個體都會添加到一個相同的寫入緩存中,如果使用者禁用了自動重新整理,這些操作直到寫緩沖區被填滿時才會被送出。如果要顯示地刷寫資料,使用者可以調用flushCommits()方法。調用HTable執行個體的close()方法也會隐式地調用flushCommits()。

預設的用戶端寫緩存是2M,我們可以通過修改hbase.client.write.buffer配置來設定大小,以滿足應用的需要。

2)使用掃描緩存

如果HBase被用作一個MapReduce作業的輸入源,最好将作為MapReduce作業輸入掃描器執行個體的緩存用setCaching()方法設定為比預設值100大得多的值。使用預設的值意味着map任務會在處理每條記錄時請求region伺服器。例如,将這個值設定為500,則一次可以傳送500行資料到用戶端進行處理。這裡使用者需要權衡傳輸資料的開銷和記憶體的開銷,因為緩存更大之後,無論是用戶端還是伺服器端都将消耗更多記憶體緩存資料,是以大的緩存并不一定最好。

3)限定掃描範圍

當Scan被用來處理大量行時(特别是被用作MapReduce輸入源時),注意哪些屬性被選中了。如果Scan.addFamily(byte [] family)被調用了,那麼特定列族中的所有都将被傳回到用戶端。

如果隻處理列,則應當隻有這列被添加到Scan的輸入中,如scan.addColumn(byte [] family,byte [] qualifier),因為選中了過多的列将導緻大資料集上極大的效率損失。

如果是選擇多列,可以使用scan. setFamilyMap(Map<byte[], NavigableSet<byte []>> familyMap)添加多個列族下的多列。

4)關閉ResultScanner

這不會帶來性能提升,但是會避免可能的性能問題。如果使用者忘記關閉由HTable.getScanner()傳回的ResultScanner執行個體,則可能對伺服器端造成影響。

是以建議在在try/catch的finally塊中關閉ResultScanner,例如:

Scan scan = newScan();              ResultScannerscanner = table.getScanner(scan);              try {              for (Resultresult: scanner) {              //procrss result...              }              } catcah (IOExceptione){              //throwexception              } finally {              scanner.close();              }              table.close();           

5)優化擷取行健的方式

當執行一個表的掃描以擷取需要的行鍵時(沒有列族、列名、列值和時間戳),在Scan中用setFilter()方法添加一個帶MUST_PASS_ALL操作符的FilterList。FilterList中包含FirstKeyOnlyFilter和KeyOnlyFilter兩個過濾器,使用以上組合的過濾器将會把發現的第一個KeyValue行鍵(也就是第一列的行鍵)傳回給用戶端,這将會最大程度地減少網絡傳輸。

推薦閱讀:

1,Hbase源碼系列之BufferedMutator的Demo和源碼解析

2,HBase原理和設計

3,HBase的安裝部署

繼續閱讀