HBase-1.2.4LruBlockCache實現分析(一)

一、簡介

? ? ??BlockCache是HBase中的一個重要特性,相比于寫數據時緩存為Memstore,讀數據時的緩存則為BlockCache。
? ? ? LruBlockCache是HBase中BlockCache的默認實現,它采用嚴格的LRU算法來淘汰Block。

二、緩存級別

? ? ? 目前有三種緩存級別,定義在BlockPriority中,如下:

public enum BlockPriority {/*** Accessed a single time (used for scan-resistance)*/SINGLE,/*** Accessed multiple times*/MULTI,/*** Block from in-memory store*/MEMORY
}

? ? ? 1、SINGLE:主要用于scan等,避免大量的這種一次的訪問導致緩存替換;

? ? ? 2、MULTI:多次緩存;

? ? ? 3、MEMORY:常駐緩存的,比如meta信息等。

三、緩存實現分析

? ? ??LruBlockCache緩存的實現在方法cacheBlock()中,實現邏輯如下:

? ? ? 1、首先需要判斷需要緩存的數據大小是否超過最大塊大小,按照2%的頻率記錄warn類型log并返回;

? ? ? 2、從緩存map中根據cacheKey嘗試獲取已緩存數據塊cb;

? ? ? 3、如果已經緩存過,比對下內容,如果內容不一樣,拋出異常,否則記錄warn類型log并返回;

? ? ? 4、獲取當前緩存大小currentSize,獲取可以接受的緩存大小currentAcceptableSize,計算硬性限制大小hardLimitSize;

? ? ? 5、如果當前大小超過硬性限制,當回收沒在執行時,執行回收并返回,否則直接返回;

? ? ? 6、利用cacheKey、數據buf等構造Lru緩存數據塊實例cb;

? ? ? 7、將cb放置入map緩存中;

? ? ? 8、元素個數原子性增1;

? ? ? 9、如果新大小超過當前可以接受的大小,且未執行回收過程中,執行內存回收。

? ? ? 詳細代碼如下,可自行閱讀分析:

  // BlockCache implementation/*** Cache the block with the specified name and buffer.* <p>* It is assumed this will NOT be called on an already cached block. In rare cases (HBASE-8547)* this can happen, for which we compare the buffer contents.* @param cacheKey block's cache key* @param buf block buffer* @param inMemory if block is in-memory* @param cacheDataInL1*/@Overridepublic void cacheBlock(BlockCacheKey cacheKey, Cacheable buf, boolean inMemory,final boolean cacheDataInL1) {// 首先需要判斷需要緩存的數據大小是否超過最大塊大小if (buf.heapSize() > maxBlockSize) {// If there are a lot of blocks that are too// big this can make the logs way too noisy.// So we log 2%if (stats.failInsert() % 50 == 0) {LOG.warn("Trying to cache too large a block "+ cacheKey.getHfileName() + " @ "+ cacheKey.getOffset()+ " is " + buf.heapSize()+ " which is larger than " + maxBlockSize);}return;}// 從緩存map中根據cacheKey嘗試獲取已緩存數據塊LruCachedBlock cb = map.get(cacheKey);if (cb != null) {// 如果已經緩存過// compare the contents, if they are not equal, we are in big troubleif (compare(buf, cb.getBuffer()) != 0) {// 比對緩存內容,如果不相等,拋出異常,否則記錄warn日志throw new RuntimeException("Cached block contents differ, which should not have happened."+ "cacheKey:" + cacheKey);}String msg = "Cached an already cached block: " + cacheKey + " cb:" + cb.getCacheKey();msg += ". This is harmless and can happen in rare cases (see HBASE-8547)";LOG.warn(msg);return;}// 獲取當前緩存大小long currentSize = size.get();// 獲取可以接受的緩存大小long currentAcceptableSize = acceptableSize();// 計算硬性限制大小long hardLimitSize = (long) (hardCapacityLimitFactor * currentAcceptableSize);if (currentSize >= hardLimitSize) {// 如果當前大小超過硬性限制,當回收沒在執行時,執行回收并返回stats.failInsert();if (LOG.isTraceEnabled()) {LOG.trace("LruBlockCache current size " + StringUtils.byteDesc(currentSize)+ " has exceeded acceptable size " + StringUtils.byteDesc(currentAcceptableSize) + "  too many."+ " the hard limit size is " + StringUtils.byteDesc(hardLimitSize) + ", failed to put cacheKey:"+ cacheKey + " into LruBlockCache.");}if (!evictionInProgress) {// 當回收沒在執行時,執行回收并返回runEviction();}return;}// 利用cacheKey、數據buf等構造Lru緩存數據塊實例cb = new LruCachedBlock(cacheKey, buf, count.incrementAndGet(), inMemory);long newSize = updateSizeMetrics(cb, false);// 放置入map緩存中map.put(cacheKey, cb);// 元素個數原子性增1long val = elements.incrementAndGet();if (LOG.isTraceEnabled()) {long size = map.size();assertCounterSanity(size, val);}// 如果新大小超過當前可以接受的大小,且未執行回收過程中if (newSize > currentAcceptableSize && !evictionInProgress) {runEviction();// 執行內存回收}}
四、淘汰緩存實現分析

? ? ? 淘汰緩存的實現方式有兩種:

? ? ? 1、第一種是在主線程中執行緩存淘汰;

? ? ? 2、第二種是在一個專門的淘汰線程中通過持有對外部類LruBlockCache的弱引用WeakReference來執行緩存淘汰。

? ? ? 應用那種方式,取決于構造函數中的boolean參數evictionThread,如下:

    if(evictionThread) {this.evictionThread = new EvictionThread(this);this.evictionThread.start(); // FindBugs SC_START_IN_CTOR} else {this.evictionThread = null;}
? ? ? 而在執行淘汰緩存的runEviction()方法中,有如下判斷:

  /*** Multi-threaded call to run the eviction process.* 多線程調用以執行回收過程*/private void runEviction() {if(evictionThread == null) {// 如果未指定回收線程evict();} else {// 如果執行了回收線程evictionThread.evict();}}
? ? ? ? 而EvictionThread的evict()實現如下:

    @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="NN_NAKED_NOTIFY",justification="This is what we want")public void evict() {synchronized(this) {this.notifyAll();}}
? ? ? ? 通過synchronized獲取EvictionThread線程的對象鎖,然后主線程通過回收線程對象的notifyAll喚醒EvictionThread線程,那么這個線程是何時wait的呢?答案就在其run()方法中,notifyAll()之后,線程run()方法得以繼續執行:

    @Overridepublic void run() {enteringRun = true;while (this.go) {synchronized(this) {try {this.wait(1000 * 10/*Don't wait for ever*/);} catch(InterruptedException e) {LOG.warn("Interrupted eviction thread ", e);Thread.currentThread().interrupt();}}LruBlockCache cache = this.cache.get();if (cache == null) break;cache.evict();}}
? ? ? ? 線程會wait10s,放棄對象鎖,在notifyAll()后,繼續執行后面的淘汰流程,即:

  /*** Eviction method.*/void evict() {// Ensure only one eviction at a time// 通過可重入互斥鎖ReentrantLock確保同一時刻只有一個回收在執行if(!evictionLock.tryLock()) return;try {// 標志位,是否正在進行回收過程evictionInProgress = true;// 當前緩存大小long currentSize = this.size.get();// 計算應該釋放的緩沖大小bytesToFreelong bytesToFree = currentSize - minSize();if (LOG.isTraceEnabled()) {LOG.trace("Block cache LRU eviction started; Attempting to free " +StringUtils.byteDesc(bytesToFree) + " of total=" +StringUtils.byteDesc(currentSize));}// 如果需要回收的大小小于等于0,直接返回if(bytesToFree <= 0) return;// Instantiate priority buckets// 實例化優先級隊列:single、multi、memoryBlockBucket bucketSingle = new BlockBucket("single", bytesToFree, blockSize,singleSize());BlockBucket bucketMulti = new BlockBucket("multi", bytesToFree, blockSize,multiSize());BlockBucket bucketMemory = new BlockBucket("memory", bytesToFree, blockSize,memorySize());// Scan entire map putting into appropriate buckets// 掃描緩存,分別加入上述三個優先級隊列for(LruCachedBlock cachedBlock : map.values()) {switch(cachedBlock.getPriority()) {case SINGLE: {bucketSingle.add(cachedBlock);break;}case MULTI: {bucketMulti.add(cachedBlock);break;}case MEMORY: {bucketMemory.add(cachedBlock);break;}}}long bytesFreed = 0;if (forceInMemory || memoryFactor > 0.999f) {// 如果memoryFactor或者InMemory緩存超過99.9%,long s = bucketSingle.totalSize();long m = bucketMulti.totalSize();if (bytesToFree > (s + m)) {// 如果需要回收的緩存超過則全部回收Single、Multi中的緩存大小和,則全部回收Single、Multi中的緩存,剩余的則從InMemory中回收// this means we need to evict blocks in memory bucket to make room,// so the single and multi buckets will be emptiedbytesFreed = bucketSingle.free(s);bytesFreed += bucketMulti.free(m);if (LOG.isTraceEnabled()) {LOG.trace("freed " + StringUtils.byteDesc(bytesFreed) +" from single and multi buckets");}// 剩余的則從InMemory中回收bytesFreed += bucketMemory.free(bytesToFree - bytesFreed);if (LOG.isTraceEnabled()) {LOG.trace("freed " + StringUtils.byteDesc(bytesFreed) +" total from all three buckets ");}} else {// 否則,不需要從InMemory中回收,按照如下策略回收Single、Multi中的緩存:嘗試讓single-bucket和multi-bucket的比例為1:2// this means no need to evict block in memory bucket,// and we try best to make the ratio between single-bucket and// multi-bucket is 1:2long bytesRemain = s + m - bytesToFree;if (3 * s <= bytesRemain) {// single-bucket足夠小,從multi-bucket中回收// single-bucket is small enough that no eviction happens for it// hence all eviction goes from multi-bucketbytesFreed = bucketMulti.free(bytesToFree);} else if (3 * m <= 2 * bytesRemain) {// multi-bucket足夠下,從single-bucket中回收// multi-bucket is small enough that no eviction happens for it// hence all eviction goes from single-bucketbytesFreed = bucketSingle.free(bytesToFree);} else {// single-bucket和multi-bucket中都回收,且盡量滿足回收后比例為1:2// both buckets need to evict some blocksbytesFreed = bucketSingle.free(s - bytesRemain / 3);if (bytesFreed < bytesToFree) {bytesFreed += bucketMulti.free(bytesToFree - bytesFreed);}}}} else {// 否則,從三個隊列中循環回收PriorityQueue<BlockBucket> bucketQueue =new PriorityQueue<BlockBucket>(3);bucketQueue.add(bucketSingle);bucketQueue.add(bucketMulti);bucketQueue.add(bucketMemory);int remainingBuckets = 3;BlockBucket bucket;while((bucket = bucketQueue.poll()) != null) {long overflow = bucket.overflow();if(overflow > 0) {long bucketBytesToFree = Math.min(overflow,(bytesToFree - bytesFreed) / remainingBuckets);bytesFreed += bucket.free(bucketBytesToFree);}remainingBuckets--;}}if (LOG.isTraceEnabled()) {long single = bucketSingle.totalSize();long multi = bucketMulti.totalSize();long memory = bucketMemory.totalSize();LOG.trace("Block cache LRU eviction completed; " +"freed=" + StringUtils.byteDesc(bytesFreed) + ", " +"total=" + StringUtils.byteDesc(this.size.get()) + ", " +"single=" + StringUtils.byteDesc(single) + ", " +"multi=" + StringUtils.byteDesc(multi) + ", " +"memory=" + StringUtils.byteDesc(memory));}} finally {// 重置標志位,釋放鎖等stats.evict();evictionInProgress = false;evictionLock.unlock();}}

? ? ? ? 邏輯比較清晰,如下:

? ? ? ? 1、通過可重入互斥鎖ReentrantLock確保同一時刻只有一個回收在執行;

? ? ? ? 2、設置標志位evictionInProgress,是否正在進行回收過程為true;

? ? ? ? 3、獲取當前緩存大小currentSize;

? ? ? ? 4、計算應該釋放的緩沖大小bytesToFree:currentSize - minSize();

? ? ? ? 5、如果需要回收的大小小于等于0,直接返回;

? ? ? ? 6、實例化優先級隊列:single、multi、memory;

? ? ? ? 7、掃描緩存,分別加入上述三個優先級隊列;

? ? ? ? 8、如果forceInMemory或者InMemory緩存超過99.9%:

? ? ? ? ? ? ? 8.1、如果需要回收的緩存超過則全部回收Single、Multi中的緩存大小和,則全部回收Single、Multi中的緩存,剩余的則從InMemory中回收(this means we need to evict blocks in memory bucket to make room,so the single and multi buckets will be emptied):

? ? ? ? ? ? ? 8.2、否則,不需要從InMemory中回收,按照如下策略回收Single、Multi中的緩存:嘗試讓single-bucket和multi-bucket的比例為1:2:

? ? ? ? ? ? ? ? ? ? ? ? 8.2.1、?single-bucket足夠小,從multi-bucket中回收;

? ? ? ? ? ? ? ? ? ? ? ? 8.2.2、?multi-bucket足夠小,從single-bucket中回收;

? ? ? ? ? ? ? ? ? ? ? ? 8.2.3、single-bucket和multi-bucket中都回收,且盡量滿足回收后比例為1:2;

? ? ? ? 9、否則,從三個隊列中循環回收;

? ? ? ? 10、最后,重置標志位,釋放鎖等。

四、實例化

? ? ? ? 參見《HBase-1.2.4 Allow block cache to be external分析》最后。







本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/397270.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/397270.shtml
英文地址,請注明出處:http://en.pswp.cn/news/397270.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c .net ajax,Asp.net mvc 2中使用Ajax的三種方式

在Asp.net MVC中&#xff0c;我們能非常方便的使用Ajax。這篇文章將介紹三種Ajax使用的方式&#xff0c;分別為原始的Ajax調用、Jquery、Ajax Helper。分別采用這三種方式結合asp.net mvc去實現一個史上最簡單的留言板。首先看一下原始的Ajax的調用的:定義CommentController&am…

爆款AR游戲如何打造?網易楊鵬以《悠夢》為例詳解前沿技術

本文來自網易云社區。 7月31日&#xff0c;2018云創大會游戲論壇在杭州國際博覽中心103B圓滿舉行。本場游戲論壇聚焦探討了可能對游戲行業發展有重大推動的新技術、新實踐&#xff0c;如AR、區塊鏈、安全、大數據等。 網易AR游戲生態合作負責人楊鵬表示&#xff0c;傳統游戲模式…

景深決定照相機什么特性_照相機光圈與景深的關系

展開全部「光圈」&#xff0c;光圈是一個用來控制光線透過鏡頭&#xff0c;進入機身636f70793231313335323631343130323136353331333264663664內感光面的光量的裝置&#xff0c;它通常是在鏡頭內。表達光圈大小我們是用f值。光圈f值鏡頭的焦距/鏡頭口徑的直徑從以上的公式可知要…

潤乾V4導出TXT時自定義分隔符

&#xfeff;&#xfeff;◆ 背景說明 報表中&#xff0c;導出text時&#xff0c;默認沒有分隔符&#xff1b;應用中對導出Text&#xff0c;希望能自定義分隔符。在tag中定義了 textDataSeparator屬性&#xff0c;讓用戶在導出Text時自定義分隔符&#xff0c;從而確保滿足應用…

Spark學習體會

在去年圖計算工作中&#xff0c;和公司里實習的博士生嘗試過Spark后&#xff0c;發現Spark比Hadoop在計算速度上后很大的提高。Spark的計算使用Scala語言編寫代碼&#xff0c;其中圖計算用到了GraphX。對Spark技術的學習已經非常重要。 最近半年多時間里&#xff0c;經常看…

fastadmin自定義按鈕不是ajax,Fastadmin 自定義按鈕實現審核功能

功能描述新增自定義審核按鈕&#xff0c;點擊審核按鈕后&#xff0c;按鈕變為取消審核按鈕&#xff0c;同理點擊取消審核按鈕后&#xff0c;按鈕變為審核按鈕實現功能如下圖微信圖片_20200827112914.png上代碼{field: operate, title: __(Operate), table: table, events: Tabl…

函數的命名空間以及作用域

轉載于:https://www.cnblogs.com/mpfei/p/9451208.html

python獲取路由器數據包pppoe_PPPoE協議***4:如何得到PPPoE服務器的mac地址

在局域網中&#xff0c;怎樣得到PPPoE服務器的mac地址是一件頭疼的事情&#xff0c;特別是在windows環境下&#xff1b;得到PPPoE服務器mac地址的實現方法有兩種&#xff1a;1.在windows下&#xff0c;我們運行wireshark軟件&#xff0c;可以得到所有進出網卡的數據包格式和內容…

使用vs自帶的性能診斷工具

visual studio是個強大的集成開發環境&#xff0c;內置了程序性能診斷工具。下面通過兩段代碼進行介紹。 static void Main( string[] args){Test1();Test2();Console.ReadKey();}protected static void Test1(){Stopwatch sp new Stopwatch();sp.Start();string str "&…

Avg_row_length是怎么計算的?

通過一下命令我們可以獲取表的使用情況&#xff1a; rootmysql 05:49:33>show table status like tbname\G 結果&#xff1a; *************************** 1. row ***************************Name: tbnameEngine: InnoDBVersion: 10Row_format: CompactRows: 3425Avg_row_…

1.用代碼演示String類中的以下方法的用法 (2018.08.09作業)

1 public class Test_001 {2 3 public static void main(String[] args) {4 String a "德瑪西亞!";5 String b "";6 String c "aBcDefG";7 String d " 123321 ";8 System.out.println…

【Java基礎】List迭代并修改時出現的ConcurrentModificationException問題

現在有一個需求&#xff0c;要遍歷一個List&#xff0c;假設List里面存儲的是String對象&#xff0c;然后該需求事判斷里面如果有某個對象&#xff0c;則添加一個新的對象進去。自然&#xff0c;我們得出下面的代碼&#xff1a; import java.util.ArrayList; import java.util.…

tp5框架原理詳解_TP5框架安全機制實例分析

本文實例講述了TP5框架安全機制。分享給大家供大家參考&#xff0c;具體如下&#xff1a;防止sql注入1、查詢條件盡量使用數組方式&#xff0c;具體如下&#xff1a;$wheres array();$wheres[account] $account;$wheres[password] $password;$User->where($wheres)->f…

碧藍航線8.20服務器維護,碧藍航線半人馬來襲 8.20更新公告

半人馬來襲&#xff01;碧藍航線將于8月20日9:00~11:00對安卓、iOS全港區進行為期2小時的改造建設&#xff0c;維護后將開啟限時活動「盛夏的半人馬座」&#xff0c;一起來看看吧。一、內容新增1.開啟限時活動「盛夏的半人馬座」&#xff0c;活動時間8月20日維護后~8月30日&…

MySQL安裝與設置

下載zip&#xff0c;配置 1&#xff0c;系統變量添加&#xff1a;...\mysql-5.7.10-winx64,環境變量添加&#xff1a;%MYSQL_HOME%\bin 2&#xff0c;修改MySQL.ini basedir&#xff08;同系統變量路徑&#xff09; datadir&#xff08;系統變量路徑\data&#xff09; port 33…

后端把Long類型的數據傳給前端,前端可能會出現精度丟失的情況,以及解決方案...

后端把Long類型的數據傳給前端&#xff0c;前端可能會出現精度丟失的情況。例如&#xff1a;201511200001725439這樣一個Long類型的整數&#xff0c;傳給前端后會變成201511200001725440。 解決方法&#xff1a; 方法一&#xff1a;在后臺將這個Long類型的字段轉換成String類型…

傳奇服務端各文件用途說明

MirServer(服務器目錄)├DBServer(數據庫服務端)│ ├Connection│ ├FDB(人物數據庫&#xff0c;數據庫格式為傳奇自定義格式)│ ├Log(角色選擇服務端日志)│ ├!AddrTable.txt(IP地址配置)│ ├!IdList.txt(交費賬號列表&#xff0c;!Setup.exe中ServiceModeTRUE時起作用)│…

認證服務器協議,基于口令的客戶端/服務器認證協議

摘要&#xff1a;Identity authentication is the precondition for secure communication between the client and the server. Kim and Chung presented a mutual authentication scheme for client/server scene. The authors realized the mutual authentication with the …

印章仿制工具_仿制圖章工具怎么用

在日常生活中&#xff0c;有時候我們需要帳單表格上的文字&#xff0c;用PS的防制圖章工具&#xff0c;可以十分方便快捷的處理出來。我想最恨學霸的就是學渣了吧&#xff0c;因為他們每次考試成績都是科科滿分。是家長嘴里別人家的孩子。那么今天就教學渣一個神技能&#xff0…

java日期的運用(DateUtils工具類)

public static void main(String[] args) { Date now new Date(); SimpleDateFormat sd new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("------當前時間--------&#xff1a;" sd.format(now)); //年: 加、減操作 System.out.…