HBase查詢優化

哥不是小蘿莉發表於2018-08-05

1.概述

HBase是一個實時的非關係型資料庫,用來儲存海量資料。但是,在實際使用場景中,在使用HBase API查詢HBase中的資料時,有時會發現資料查詢會很慢。本篇部落格將從客戶端優化和服務端優化兩個方面來介紹,如何提高查詢HBase的效率。

2.內容

這裡,我們先給大家介紹如何從客戶端優化查詢速度。

2.1 客戶端優化

客戶端查詢HBase,均通過HBase API的來獲取資料,如果在實現程式碼邏輯時使用API不當,也會造成讀取耗時嚴重的情況。

2.1.1 Scan優化

在使用HBase的Scan介面時,一次Scan會返回大量資料。客戶端向HBase傳送一次Scan請求,實際上並不會將所有資料載入到本地,而是通過多次RPC請求進行載入。這樣設計的好處在於避免大量資料請求會導致網路頻寬負載過高影響其他業務使用HBase,另外從客戶端的角度來說可以避免資料量太大,從而本地機器傳送OOM(記憶體溢位)。

預設情況下,HBase每次Scan會快取100條,可以通過屬性hbase.client.scanner.caching來設定。另外,最大值預設為-1,表示沒有限制,具體實現見原始碼:

/**
   * @return the maximum result size in bytes. See {@link #setMaxResultSize(long)}
   */
  public long getMaxResultSize() {
    return maxResultSize;
  }

  /**
   * Set the maximum result size. The default is -1; this means that no specific
   * maximum result size will be set for this scan, and the global configured
   * value will be used instead. (Defaults to unlimited).
   *
   * @param maxResultSize The maximum result size in bytes.
   */
  public Scan setMaxResultSize(long maxResultSize) {
    this.maxResultSize = maxResultSize;
    return this;
  }

一般情況下,預設快取100就可以滿足,如果資料量過大,可以適當增大快取值,來減少RPC次數,從而降低Scan的總體耗時。另外,在做報表呈現時,建議使用HBase分頁來返回Scan的資料。

2.1.2 Get優化

HBase系統提供了單條get資料和批量get資料,單條get通常是通過請求表名+rowkey,批量get通常是通過請求表名+rowkey集合來實現。客戶端在讀取HBase的資料時,實際是與RegionServer進行資料互動。在使用批量get時可以有效的較少客戶端到各個RegionServer之間RPC連線數,從而來間接的提高讀取效能。批量get實現程式碼見org.apache.hadoop.hbase.client.HTable類:

public Result[] get(List<Get> gets) throws IOException {
    if (gets.size() == 1) {
      return new Result[]{get(gets.get(0))};
    }
    try {
      Object[] r1 = new Object[gets.size()];
      batch((List<? extends Row>)gets, r1, readRpcTimeoutMs);
      // Translate.
      Result [] results = new Result[r1.length];
      int i = 0;
      for (Object obj: r1) {
        // Batch ensures if there is a failure we get an exception instead
        results[i++] = (Result)obj;
      }
      return results;
    } catch (InterruptedException e) {
      throw (InterruptedIOException)new InterruptedIOException().initCause(e);
    }
  }

從實現的原始碼分析可知,批量get請求的結果,要麼全部返回,要麼丟擲異常。

2.1.3 列簇和列優化

通常情況下,HBase表設計我們一個指定一個列簇就可以滿足需求,但也不排除特殊情況,需要指定多個列簇(官方建議最多不超過3個),其實官方這樣建議也是有原因的,HBase是基於列簇的非關係型資料庫,意味著相同的列簇資料會存放在一起,而不同的列簇的資料會分開儲存在不同的目錄下。如果一個表設計多個列簇,在使用rowkey查詢而不限制列簇,這樣在檢索不同列簇的資料時,需要獨立進行檢索,查詢效率固然是比指定列簇查詢要低的,列簇越多,這樣影響越大。

而同一列簇下,可能涉及到多個列,在實際查詢資料時,如果一個表的列簇有上1000+的列,這樣一個大表,如果不指定列,這樣查詢效率也是會很低。通常情況下,在查詢的時候,可以查詢指定我們需要返回結果的列,對於不需要的列,可以不需要指定,這樣能夠有效地的提高查詢效率,降低延時。

2.1.4 禁止快取優化

批量讀取資料時會全表掃描一次業務表,這種提現在Scan操作場景。在Scan時,客戶端與RegionServer進行資料互動(RegionServer的實際資料時儲存在HDFS上),將資料載入到快取,如果載入很大的資料到快取時,會對快取中的實時業務熱資料有影響,由於快取大小有限,載入的資料量過大,會將這些熱資料“擠壓”出去,這樣當其他業務從快取請求這些資料時,會從HDFS上重新載入資料,導致耗時嚴重。

在批量讀取(T+1)場景時,建議客戶端在請求是,在業務程式碼中呼叫setCacheBlocks(false)函式來禁止快取,預設情況下,HBase是開啟這部分快取的。原始碼實現為:

/**
   * Set whether blocks should be cached for this Get.
   * <p>
   * This is true by default.  When true, default settings of the table and
   * family are used (this will never override caching blocks if the block
   * cache is disabled for that family or entirely).
   *
   * @param cacheBlocks if false, default settings are overridden and blocks
   * will not be cached
   */
  public Get setCacheBlocks(boolean cacheBlocks) {
    this.cacheBlocks = cacheBlocks;
    return this;
  }

  /**
   * Get whether blocks should be cached for this Get.
   * @return true if default caching should be used, false if blocks should not
   * be cached
   */
  public boolean getCacheBlocks() {
    return cacheBlocks;
  }

2.2 服務端優化

HBase服務端配置或叢集有問題,也會導致客戶端讀取耗時較大,叢集出現問題,影響的是整個叢集的業務應用。

2.2.1 負載均衡優化

客戶端的請求實際上是與HBase叢集的每個RegionServer進行資料互動,在細分一下,就是與每個RegionServer上的某些Region進行資料互動,每個RegionServer上的Region個數上的情況下,可能這種耗時情況影響不大,體現不夠明顯。但是,如果每個RegionServer上的Region個數較大的話,這種影響就會很嚴重。筆者這裡做過統計的資料統計,當每個RegionServer上的Region個數超過800+,如果發生負載不均衡,這樣的影響就會很嚴重。

可能有同學會有疑問,為什麼會傳送負載不均衡?負載不均衡為什麼會造成這樣耗時嚴重的影響?

1.為什麼會發生負載不均衡?

負載不均衡的影響通常由以下幾個因素造成:

  • 沒有開啟自動負載均衡
  • 叢集維護,擴容或者縮減RegionServer節點
  • 叢集有RegionServer節點發生當機或者程式停止,隨後守護程式又自動拉起當機的RegionServer程式

針對這些因素,可以通過以下解決方案來解決:

  • 開啟自動負載均衡,執行命令:echo "balance_switch true" | hbase shell
  • 在維護叢集,或者守護程式拉起停止的RegionServer程式時,定時排程執行負載均衡命令:echo "balancer" | hbase shell

2.負載不均衡為什麼會造成這樣耗時嚴重的影響?

這裡筆者用一個例子來說,叢集每個RegionServer包含由800+的Region數,但是,由於叢集維護,有幾臺RegionServer節點的Region全部集中到一臺RegionServer,分佈如下圖所示:

這樣之前請求在RegionServer2和RegionServer3上的,都會集中到RegionServer1上去請求。這樣就不能發揮整個叢集的併發處理能力,另外,RegionServer1上的資源使用將會翻倍(比如網路、磁碟IO、HBase RPC的Handle數等)。而原先其他正常業務到RegionServer1的請求也會因此受到很大的影響。因此,讀取請求不均衡不僅會造成本身業務效能很長,還會嚴重影響其他正常業務的查詢。同理,寫請求不均衡,也會造成類似的影響。故HBase負載均衡是HBase叢集效能的重要體現。

2.2.2 BlockCache優化

BlockCache作為讀快取,合理設定對於提高讀效能非常重要。預設情況下,BlockCache和Memstore的配置各站40%,可以通過在hbase-site.xml配置以下屬性來實現:

  • hfile.block.cache.size,預設0.4,用來提高讀效能
  • hbase.regionserver.global.memstore.size,預設0.4,用來提高寫效能

本篇部落格主要介紹提高讀效能,這裡我們可以將BlockCache的佔比設定大一些,Memstore的佔比設定小一些(總佔比保持在0.8即可)。另外,BlockCache的策略選擇也是很重要的,不同的策略對於讀效能來說影響不大,但是對於GC的影響卻比較明顯,在設定hbase.bucketcache.ioengine屬性為offheap時,GC表現的很優秀。快取結構如下圖所示:

設定BlockCache可以在hbase-site.xml檔案中,配置如下屬性:

<!-- 分配的記憶體大小盡可能的多些,前提是不能超過 (機器實際實體記憶體-JVM記憶體) -->
<property>  
   <name>hbase.bucketcache.size</name>  
   <value>16384</value> 
</property>
<property>
 <name>hbase.bucketcache.ioengine</name>
 <value>offheap</value> 
</property>

設定塊記憶體大小,可以參考入下表格:

標號 描述 計算公式或值 結果
A 實體記憶體選擇:on-heap(JVM)+off-heap(Direct) 單臺物理節點記憶體值,單位MB 262144
B HBASE_HEAPSIZE('-Xmx) 單位MB 20480
C -XX:MaxDirectMemorySize,off-heap允許的最大記憶體值 A-B 241664
Dp hfile.block.cache.size和hbase.regionserver.global.memstore.size總和不要超過0.8 讀取比例佔比*0.8 0.5*0.8=0.4
Dm JVM Heap允許的最大BlockCache(MB) B*Dp 20480*0.4=8192
Ep hbase.regionserver.global.memstore.size設定的最大JVM值 0.8-Dp 0.8-0.4=0.4
F 用於其他用途的off-heap記憶體,例如DFSClient 推薦1024到2048 2048
G BucketCache允許的off-heap記憶體 C-F 241664-2048=239616

另外,BlockCache策略,能夠有效的提高快取命中率,這樣能夠間接的提高熱資料覆蓋率,從而提升讀取效能。

2.2.3 HFile優化

HBase讀取資料時會先從BlockCache中進行檢索(熱資料),如果查詢不到,才會到HDFS上去檢索。而HBase儲存在HDFS上的資料以HFile的形式存在的,檔案如果越多,檢索所花費的IO次數也就必然增加,對應的讀取耗時也就增加了。檔案數量取決於Compaction的執行策略,有以下2個屬性有關係:

  • hbase.hstore.compactionThreshold,預設為3,表示store中檔案數超過3個就開始進行合併操作
  • hbase.hstore.compaction.max.size,預設為9223372036854775807,合併的檔案最大閥值,超過這個閥值的檔案不能進行合併

 另外,hbase.hstore.compaction.max.size值可以通過實際的Region總數來計算,公式如下:

hbase.hstore.compaction.max.size = RegionTotal / hbase.hstore.compactionThreshold

2.2.4 Compaction優化

Compaction操作是將小檔案合併為大檔案,提高後續業務隨機讀取的效能,但是在執行Compaction操作期間,節點IO、網路頻寬等資源會佔用較多,那麼什麼時候執行Compaction才最好?什麼時候需要執行Compaction操作?

1.什麼時候執行Compaction才最好?

實際應用場景中,會關閉Compaction自動執行策略,通過屬性hbase.hregion.majorcompaction來控制,將hbase.hregion.majorcompaction=0,就可以禁止HBase自動執行Compaction操作。一般情況下,選擇叢集負載較低,資源空閒的時間段來定時排程執行Compaction。

如果合併的檔案較多,可以通過設定如下屬性來提生Compaction的執行速度,配置如下:

<property>
    <name>hbase.regionserver.thread.compaction.large</name>
    <value>8</value>
    <description></description>
</property>
<property>
    <name>hbase.regionserver.thread.compaction.small</name>
    <value>5</value>
    <description></description>
</property>

2.什麼時候需要執行Compaction操作?

一般維護HBase叢集后,由於叢集發生過重啟,HBase資料本地性較低,通過HBase頁面可以觀察,此時如果不執行Compaction操作,那麼客戶端查詢的時候,需要跨副本節點去查詢,這樣來回需要經過網路頻寬,對比正常情況下,從本地節點讀取資料,耗時是比較大的。在執行Compaction操作後,HBase資料本地性為1,這樣能夠有效的提高查詢效率。

3.總結

本篇部落格HBase查詢優化從客戶端和服務端角度,列舉一些常見有效地優化手段。當然,優化還需要從自己實際應用場景出發,例如程式碼實現邏輯、物理機的實際配置等方面來設定相關引數。大家可以根據實際情況來參考本篇部落格進行優化。

4.結束語

這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!

另外,博主出書了《Hadoop大資料探勘從入門到進階實戰》,喜歡的朋友或同學, 可以在公告欄那裡點選購買連結購買博主的書進行學習,在此感謝大家的支援。

相關文章