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的安裝部署