1.核心元件

總入口是CacheConfig,這個類根據配置資訊,來傳回不同的具體cache元件;
預設會傳回LruBlockCache,所有類型的block都會存入;
啟用了BucketCache時,會傳回CombinedBlockCache,此類中根據block類型,data block存入BucketCache,其它存入LruBlockCache;
2.LruBlockCache
LruBlockCache内部較為簡單,主要就是一個map,如上圖所示,由hfilename+offset來唯一辨別一個block;
LruBlockCache所能夠使用的記憶體為堆的一定比例,通過hfile.block.cache.size設定,預設是0.4;
so,maxSize = heapSize * hfile.block.cache.size,以下參數都根據maxSize計算;
acceptSize:
使用量達到一定比例時會觸發驅逐,該門檻值通過hbase.lru.blockcache.acceptable.factor設定,預設是0.99;
minSize:
驅逐後最少剩餘比例,該門檻值通過hbase.lru.blockcache.min.factor設定,預設是0.95;
hardLimit:
使用量達到一定比例時則拒絕寫入,該門檻值通過hbase.lru.blockcache.hard.capacity.limit.factor設定,預設是1.2,這意味允許一定的超出;
關于驅逐:
- block分為3種類型,由BlockPriority字段區分,取值為single、mutli、inMem,空間配置設定預設為0.25:0.5:0.25;
- 系統表以及其它指定了InMem的表所含block會标記為inMem,其它block初次存入時标記為single,再次通路時會修改為multi;
- 存放時隻要還有空間即可放入,空間配置設定比例隻是在驅逐發生時進行計算使用;
- 驅逐時,會用minSize乘以各類型的比例,得到各類型最少要保留的minSize;
- 根據目前的算法,驅逐後的size,應該是略大于minSize的一個值,僞代碼如下;
expectFreeSize = usedSize - minSize;//預期釋放總大小
freedSize = 0;//目前已釋放總大小
n=3;//類型數量
for type in ('single','multi','inMem'):
overFlow = type.usedSize - type.minSize
toBeFree = min(overFlow,(expectFreeSize - freedSize)/n)
free(toBeFree)
freedSize += toBeFree
n--;
3.BucketCache
LruBlockCache的優點是實作簡單,缺點是block的存入和釋放伴随着記憶體的申請和釋放,會帶來記憶體碎片和gc過多的問題;
BucketCache采用了類似池的思路,預先申請記憶體并劃分為一個個的bucket,這些bucket會一直存在并重複使用;
總體的讀寫流程如下圖所示:
Block緩存寫入流程:
- 将block寫入RAMCache,然後系統會根據blockkey進行hash,根據hash結果将block配置設定到一組blockingQueue中;
- HBase會同時啟動多個WriteThead,分别關聯一個blockingQueue,并發的執行異步寫入;
- 每個WriteThead讀取到block資料後,調用bucketAllocator為這些block配置設定記憶體空間;
- BucketAllocator會選擇與block大小對應的bucket進行存放,并且傳回對應的實體位址偏移量offset;
- WriteThead将block以及配置設定好的實體位址偏移量傳給IOEngine子產品,執行具體的記憶體寫入操作;
- 寫入成功後,将類似這樣的映射關系寫入BackingMap中,友善後續查找時根據blockkey可以直接定位;
Block緩存讀取流程:
- 首先從RAMCache中查找,對于還沒有來得及寫入到bucket的緩存block,一定存儲在RAMCache中;
- 如果在RAMCache中沒有找到,再在BackingMap中根據blockKey找到對應entry;
- 根據entry中的offset可以直接從記憶體中查找對應的block資料;
其中最核心的元件是BucketAllocator和IoEngine,前者負責block的邏輯位址配置設定,後者負責block的實際實體存放,内部結構如下:
hbase中blocksize是可以靈活設定的,bucketCache預設了一組支援的大小,從4K~512k不等;
一個Bucket隻能存放一種size的block,一種size對應一個BucketSizeInfo進行管理;
初始化時,每種size先配置設定1個bucket,剩餘的都配置設定給最大的那個size,如黑色箭頭所示;
配置設定過程中目前size如果空間不夠,會挪用其它size的空閑bucket,如棕色箭頭所示,這意味着有可能某個Bucket一開始存放了32k的block
,後面釋放後空閑,被挪用後變成存放64k的block;
ioEngine有多種實作,可支援onheap、offheap、disk等;
- 2種情況下會觸發,1是已使用超過95%(acceptableFactor),2是某個size的block配置設定不了(總量雖然沒達到門檻值,但不存在完全空閑的bucket供挪用);
- 驅逐後的最少剩餘比例為85%(minFactor),周遊各個bucketSizeInfo,把超過85%的部分加起來,再乘以一個系數0.1(extraFreeFactor),就是要釋放的大小;
- 具體計算方法複用了LruBlockCache的代碼,也是按照single、multi、inMem及其比例進行計算和釋放;
- 實際清理動作是修改一些狀态資料,比如Bucket對象的freeList、freeCount,以及backMapping的鍵值對等,并不需要對底層的byteBuffer做什麼操作;
- 對于refCount大于0的block,會先将其markedForEvict置為true,待各個使用方讀取完成後調用returnBlock進行釋放;
參考資料
http://hbasefly.com/2016/04/26/hbase-blockcache-2/?xuxezc=17idz1