1. 程式人生 > >HBase Block Cache(塊緩存)

HBase Block Cache(塊緩存)

efault 可能 hat 有趣 values 負載 property site set

Block Cache

HBase提供了兩種不同的BlockCache實現,用於緩存從HDFS讀出的數據。這兩種分別為:

  1. 默認的,存在於堆內存的(on-heap)LruBlockCache
  2. 存在堆外內存的(off-heap)BucketCache

下面我們會討論每種方法的優點和缺點、如何對兩種方式做選擇,以及這兩種類型的相關配置。

Cache Choices

LruBlockCache是最初始的實現,並且全部存在Java堆內存中。BucketCache是另一個選擇,主要用於將block cache的數據存在off-heap(堆外內存),不過BlockCache也可以作為一種文件備份式的緩存。

當開啟了BucketCache後,便啟用了兩級緩存的系統。以前我們會用“L1”和“L2”來描述這兩個等級,但是現在這個術語已經在hbase-2.0.0後被棄用了。現在“L1” cache 直接指的是LruBlockCache,“L2”指的是一個off-heap的BucketCache。(hbase-2.0.02之後)當BucketCache啟用後,所有數據塊(DATA block)會被存在BucketCache 層,而meta 數據塊(INDEX 以及BLOOM塊)被存在on-heap的LruBlockCache中。管理這兩層緩存,以及指示數據塊如何在它們之間移動的策略,由CombinedBlockCache完成。

Cache的常規配置

除了緩存它自己的實現以外,我們也可以設置一些常規的配置選項,用於控制cache的行為。具體可以參考CacheConfig的文檔:

https://hbase.apache.org/devapidocs/org/apache/hadoop/hbase/io/hfile/CacheConfig.html

在設置或修改了任何屬性後,需要重啟HBase集群以讓配置文件生效。若是遇到異常,可以進一步查看HBase中的報錯。

LruBlockCache的設計

LruBlockCache是一個LRU緩存,包括三種優先級,以適應於於:scan-resistance 以及 in-memory ColumnFamilies場景。三種優先級分別為:

  1. Single Access 優先級:當一個數據塊第一次從HDFS讀取時,它會具有這種優先級,並且在緩存空間需要被回收(置換)時,它屬於優先被考慮範圍內。它的優點在於:一般被掃描(scanned)讀取的數據塊,相較於之後會被用到的數據塊,更應該被優先清除
  2. Multi Access優先級:如果一個數據塊,屬於Single Access優先級,但是之後被再次訪問,則它會升級為Multi Access優先級。在緩存裏的內容需要被清除(置換)時,這部分內容屬於次要被考慮的範圍
  3. In-memory Access優先級:如果數據塊族被配置為“in-memory”,則會具有這種優先級,並且與它被訪問的次數無關。HBase Catalog便是被配置的這個優先級。在緩存裏的內容需要被置換時,這部分內容屬於最後被考慮的範圍。若是需要將一個列族標註為此優先級:
    1. 在Java中可以調用: HColumnDescriptor.setInMemory(true);
    2. 在hbase shell 中創建或修改一個表時,可以使用 IN_MEMORY => true,例如:create ‘t’, {NANME => ‘f’, IN_MEMORY => ‘true’}

若是想了解更具體的信息,可以參考LruBlockCache 源碼

LruBlockCache 的使用

一般來說,BlockCache在所有用戶表中默認是開啟的,也就是說,任何的讀操作均會加載LRU Cache。這個方案可能適用於大部分場景,但是,如果需要達到更優的performance,仍需要做一些調整。其中一個很重要的概念是working set size(或WSS),它的意思是:為了解決一個問題所需要的資源(內存)大小。對於一個網站來說,這個便是在短時間內響應請求所需要的數據量。計算在HBase中到底有多少內存可供cache的方法為:

number of region servers * heap size * hfile.block.cache.size * 0.99

block.cache的默認值是0.4,表示可用堆內存的40%。最後一個值(99%)是:在緩存內存收回開始後,默認可被回收的比率(這裏若是值是100%,則不太現實,因為在置換時也會有新塊寫入?)。下面是使用這個公式的一些例子:

  1. 一個region server,設置了1GB的堆內存,使用默認的block.cache參數,則會有405MB的block cache可用
  2. 20個region server,設置了8GB大小的堆內存,使用默認的block.cache參數,則會有63GB大小的block cache可用(20 × 8 × 0.4 × 0.99)
  3. 100 個region server,設置了24GB 大小的堆內存,使用block.cache=0.5,則會有1.16TB的可用block cache

當然,被存在block cache中的並不僅僅只是你的數據,下面是其他一些需要被考慮到的地方:

  1. Catalog :hbase:meta 表被強制載入block cache,並且具有in-memory 優先級,也就是說,它的內容幾乎很少會被從緩存移除。(根據regions的數量,hbase:meta 表會占據一小部分內存)
  2. HFile 索引:HFile是HBase用於在HDFS上存儲數據數據文件格式。它包含多級索引,可以讓HBase在不需要讀入整個文件的情況下找到目標數據。決定這些索引大小的因素是:數據塊大小(默認是64KB),你的key大小,以及存儲數據的量。對於大數據集來說,一個region server 的cache中包含大約1GB的索引大小也屬正常,盡管不是所有的索引均會被放入cache(因為LRU會將那些不常用的索引移除)。
  3. Keys:The values that are stored are only half the picture, since each value is stored along with its keys (row key, family qualifier, and timestamp).
  4. Bloom Filter:如HFile的索引一樣,那些數據結構(若是在enabled之後),會被存在LRU

當前來說,衡量HFile索引以及Bloom filter 大小的一種推薦的方法是:查看region server UI,並查看相關指標。對於鍵來說,獲取抽樣時,可以使用HFile的命令行工具,並查看鍵的平均大小。從HBase 0.98.3以後,可以在UI界面中的Block Cache部分,查看BlockCache的詳細狀態以及指標。

一般來說,如果當前可用內存並不足以支持WSS,則不建議使用block caching。舉個例子:假設在集群中一共有40GB的可用內存分布於各個region server的block cache,但是你需要處理1TB的數據,則這種場景不太適合使用block caching。其中一個原因是:回收(置換)緩存會打亂內存分布,並觸發更多本沒必要的垃圾回收。下面是兩個場景:

  1. 完全隨機的讀模式:這種場景一般是,在短時間內,應用幾乎不會重復讀取表中同一行的內容,所以在這種情況下,命中cache的機會基本接近於0。在這個表中設置block caching屬於浪費內存以及CPU的時間片。不僅如此,它還會產生更多的JVM垃圾回收事件。
  2. Mapping a table:比如在某個MapReduce任務中,任務的輸入是一張表。每一行僅會被讀取一次,所以就沒必要將這些數據放入block cache。在Java中,Scan對象有一個關閉block cache的功能:setCaching(設置為false)。當然,如果你需要快速的隨機讀訪問,也可以在這個表上保持開啟block caching功能。

僅緩存META數據塊(DATA 數據塊在fscache

一個有趣的設置是:僅緩存 META數據塊,每次在讀取數據時,均去訪問DATA數據塊。在這種情況下如果DATA數據塊適應於fscache,且當訪問數據的操作在一個很大的集群中完全是隨機時,這種設置是可取的。若要開啟這個設置,可以直接修改表:對某個列族,設置BLOCKCACHE => ‘false’。這樣就關閉了對這個列族的BlockCache。不過,META數據塊的block caching無法被關閉,即使它的被關閉了,META數據塊也仍會被載入緩存。

堆外(Off-heapBlock Cache

如何開啟BucketCache

一個通常的部署BucketCache方式是通過一個管理類,它設置兩級緩存:一個堆內的緩存,由LruBlockCache實現;以及第二層緩存,由BucketCache實現。默認管理的類為CombinedBlockCache。簡單的說,這個類實現的緩存規則是:將meta數據塊(INDEX以及BLOOM)放在堆內緩存(LruBlockCache層),而將DATA數據放入BucketCache層。

在HBase-2.00 版本之前

在hbase 2.00 版本以前,從BucketCache取數據時都會比較慢(對比使用堆內存的LruBlockCache)。然而,從表現來看,讀操作的延遲時間基本趨於穩定。因為在使用BucketCache時,會有較少的垃圾回收(BucketCache管理BlockCache的分配,而不是GC)。如果BucketCache被部署為堆外(off-heap)模式,則這部分內存根本不會被GC管理。這就是為什麽你在2.0.0版本之前的HBase使用BucketCache時,延遲時間基本趨於穩定,並可以減輕GC以及堆內存碎片的影響,這樣可以安全的使用更多內存。如果你希望緩存不被GC管理,可以使用BucketCache。

在2.0.0版本前,在配置了BucketCache後,可以減少LruBlockCache置換的影響。所有數據以及index塊首先被緩存在L1。當L1中發生緩存清除(置換)時,被置換出的數據塊會被移動到L2。在Java中,可以通過HColumnDescriptor.setCacheDataInL1(true)設置cacheDataInL1;在hbase shell中可以設置CACHE_DATA_IN_L1 為true,例如:create ‘t1’, {NamE => ‘t1’, CONFIGURATION => {CACHE_DATA_IN_L1 => ‘true’}}

HBase-2.0.0 版本之後

HBASE-11425改變了HBase的數據讀取路徑,實現了直接從堆外讀取數據。off-heap的延遲可以接近於on-heap的延遲,因為off-heap並不會引起GC操作。

從HBase 2.0.0 開始,L1與L2的概念便被棄用。當BucketCache啟用時,數據塊(DATA blocks)會一直保存於BucketCache;INDEX/BLOOM塊會保存於LRUBlockCache的堆內存。cacheDetaInL1 的配置也被移除。

BucketCache的塊緩存可以被部署為off-heap,文件,或mmaped文件這三種模式(通過hbase.bucketcache.ioengine配置)。設置為offheap會讓BucketCache在堆外內存管理block cache。設置為file:PATH_TO_FILE(EMR裏默認為files:/mnt/hbase/bucketcache),會直接讓BucketCache使用文件緩存(如果卷是SSD之類的高速盤的話,會比較有用)。從2.0.0 開始,使用多個文件路徑也是可以的。若是在需要較大Cache 的場景下,這個配置很有用。在設置時的基本配置為:files:PATH_TO_FILE1, PATH_TO_FILE2, PATH_TO_FILE3。BucketCache可以也可以配置為使用一個mmapped文件。配置ioengine為mmap:PATH_TO_FILE即可。

在hbase 2.0.0之前,也可以設置多級緩存(繞過CombinedBlockCache策略),將BucketCache設置為嚴格的L2 緩存,LruBlockCache為L1緩存。在這種配置中,設置 hbase.bucketcache.combinedcache.enable 為false即可。在這種模式下,當L1緩存內容被清除(置換)時,會將置換出的塊放入L2。當一個塊被緩存時,首先被緩存在L1。當我們去查詢一個緩存塊時,首先在L1查,若是沒找到,則再搜索L2。我們將此部署方法稱為Raw L1+L2。需要註意的是,這個L1+L2模式已經在hbase 2.0.0 以後被移除了。當BucketCache被使用時,它會嚴格的將DATA塊緩存放入BucketCache,而INDEX/META塊則被放入LruBlockCache。

其他BucketCache的配置包括:指定cache被持久化的路徑以在重啟後仍存在、寫cache的線程數量,等等。在檢查它是否開啟時,可以查看日誌內容,會包含cache的設置;它會詳細的記錄BucketCache被部署的信息。也可以通過UI,它可以看到詳細的cache層級以及它們的配置。

BucketCache示例配置

這個例子提供了為一個4GB堆外BucketCache、1GB堆內緩存的配置。配置過程在RegionServer上實施。

設置hbase.bucketcache.ioengine,並設置 hbase.bucketcache.size > 0,開啟CombinedBlockCache。這裏我們假設當前RegionServer配置了5GB的堆內存(HBASE_HEAPSIZE=5g)

  1. 首先,編輯RegionServer的hbase-env.sh 文件,並設置HBASE_OFFHEAPSIZE,需要高於所需的off-heap的大小。在這個案例中需要的off-heap為4GB,所以我們設置此值為5GB。這裏4GB被用於我們的off-heap緩存,剩余的1G被其他用戶使用(因為會有其他用戶也會使用off-heap內存;例如RegionServer中的DFSClient會使用堆外內存,參考下面的Direct Memory Usage in HBase)。HBASE_OFFHEAPSIZE=5G
  2. 然後,在hbase-site.xml 下設置以下配置:

<property>

<name>hbase.bucketcache.ioengine</name>

<value>offheap</value>

</property>

<property>

<name>hfile.block.cache.size</name>

<value>0.2</value>

</property>

<property>

<name>hbase.bucketcache.size</name>

<value>4196</value>

</property>

  1. 重啟集群,若出現任何問題,檢查日誌

上面的配置文件中,我們設置了BucketCache為4G,配置on-heap LruBlockCache為20%的RegionServer的堆內存(0.2 × 5G = 1G)。

HBASE-10641以後,HBase 0.98及以後的版本,引入了可以為BucketCache配置多個bucket以及它們的大小的功能。配置多個bucket sieze,可以設置hbase.bucketcache.bucket.sizes為一系列塊大小設置,從小到大。它的目的是根據你數據訪問模式,優化bucket sizes。下面是一個示例配置,大小從4096到8192:

<property>

<name>hbase.bucketcache.bucket.sizes</name>

<value>4096,8192</value>

</property>

Direct Memory Usage in HBase

默認最大可以使用的direct memory因JVM的不同而不同。傳統下是64M,或者通過直接分配堆內存(-Xmx),或完全沒有限制(如JDK7)。HBase服務器使用direct memory,特別是short-circuit reading(讀數據不經過DataNode,客戶端直接讀文件),RegionServer上的DFSclient會分配direct memory buffers。DFSClient會使用的內存大小並不容易量化;它是由:打開的HFile文件數量 × hbase.dfs.client.read.shortcircuit.buffer.size 決定。hbase.dfs.client.read.shortcircuit.buffer.size在HBase中設置為128k(參考hbae-default.xml默認配置)。如果需要使用off-heap block caching,則需要使用到直接內存(direct memory)。在RPC Server中,也會使用一個ByteBuffer池,從hbase 2.0.0開始,這些緩沖區為off-heap ByteBuffers。在啟動JVM時,確保 -XX:MaxDirectMemorySize 的設置(在hbase-env.sh)考慮到了off-heap BlockCache(hbase.bucketcache.size)、DFSClient的使用量,以及RPC端的ByteBufferPool的最大總和大小。Direct memory 的大小應該比 off-heap BlockCache + max ByteBufferPool 的大小更大。在一般情況下,可以在基於所需的direct memory大小情況下,額外再分配多1-2GB的空間。Direct memory屬於Java進程堆的一部分,與對象堆(由-Xmx分配)分離。MaxDirectMemorySize的大小必須小於物理的RAM大小,並且小於所有可用的RAM大小(由於內存的其他用處,以及系統的限制)。

你可以在UI中的Server Metrics: Memory 欄看到一個RegionServer配置的內存量(on-heap以及off-heap/direct memory)。這部分數據也可以通過JMX獲取。

BlockCache 壓縮

HBASE-11331引入了lazy BlockCache decompression。在開啟此功能後,DATA(以及ENCODED_DATA)數據塊會以它們on-disk的形式緩存到BlockCache。與默認的模式不同點在於:默認情況下,在緩存一個數據塊時,會先解壓縮、解密,然後存入緩存(因為數據塊是從HDFS取)。而lazy BlockCache decompression 直接將數據塊存入緩存。

如果一個RegionServer存儲的數據過多,無法適當的將大部分數據放入緩存,則開啟這個功能(使用SNAPPY壓縮)後,實驗證明:可以提升50%的throughput,30%的平均延遲上升,增加80%垃圾回收,以及2%的整體CPU負載。

對於一個RegionServer,如果它的數據量已經適合cache的大小,或者你的應用對額外的CPU或GC的負載格外敏感,則這個選項不會有太大用處。

默認情況下,這個功能是關閉的,若要開啟,可以在所有的RegionServer中的hbase-site.xml文件裏設置 hbase.block.data.cachecompressed 為 true

References:

http://hbase.apache.org/book.html#arch.overview

HBase Block Cache(塊緩存)