先上一張圖講一下compaction和split的關系,這樣會比較直覺一些。

compaction把多個memstore flush出來的storefile合并成一個檔案,而split則是把過大的檔案split成兩個。
之前在delete的時候,我們知道它其實并沒有真正删除資料的,那總不能一直不删吧,下面我們就介紹一下它删除資料的過程,它就是compaction。
在講源碼之前,先說一下它的分類和作用。
<b>compaction主要起到如下幾個作用:</b>
1)合并檔案
2)清除删除、過期、多餘版本的資料
3)提高讀寫資料的效率
<b>minor & major compaction的差別:</b>
1)minor操作隻用來做部分檔案的合并操作以及包括minversion=0并且設定ttl的過期版本清理,不做任何删除資料、多版本資料的清理工作。
2)major操作是對region下的hstore下的所有storefile執行合并操作,最終的結果是整理合并出一個檔案。
先說一下怎麼使用吧,下面分别是它們是shell指令,可以在hbase的shell裡面執行。
下面我們開始看入口吧,入口在hbaseadmin,找到compact方法,都知道我們compact可以對表操作或者對region進行操作。
1、先把表或者region相關的region資訊和server資訊全部擷取出來
2、循環周遊這些region資訊,依次請求compact操作
到這裡,用戶端的工作就結束了,我們直接到hregionserver找compactregion這個方法吧。
我們先看major compaction吧,直接去看triggermajorcompaction和requestcompaction方法。
compaction
進入方法裡面就發現了它把forcemajor置為true就完了,看來這個參數是major和minor的開關,接着看requestcompaction。
上面的步驟是執行selectcompaction建立一個compactioncontext,然後送出compactionrunner。
我們接着看compactioncontext的建立過程吧,這裡還需要分是使用者建立的compaction和系統建立的compaction。
我們看看這個select的具體實作吧。
這裡的select方法,從名字上看是壓縮政策的意思,它是由這個參數控制的hbase.hstore.defaultengine.compactionpolicy.class,預設是exploringcompactionpolicy這個類。
接着看exploringcompactionpolicy的selectcompaction方法,發現這個方法是繼承來的,找它的父類ratiobasedcompactionpolicy。
從上面可以看出來,major compaction的選擇檔案幾乎沒什麼限制,隻要排除掉正在compacting的檔案就行了,反而是minor compact有諸多的排除選項,因為預設的compaction是定時執行的,是以它這方面的考慮吧,排除太大的檔案,選擇那些過期的檔案,排除掉bulkload的檔案等等内容。
我們再簡單看看applycompactionpolicy這個方法吧,它是minor的時候用的,它的過程就像下圖一樣。
<b>這個是雙層循環: </b>
從0開始,循環n遍(n=檔案數),就相當于視窗向右滑動,指針為start
----->從currentend=start + minfiles(預設是3)-1,每次增加一個檔案作為考慮,類似擴張的動作, 視窗擴大, 指針為
-------------->從candidateselection檔案裡面取出(start, currentend + 1)開始
-------------->小于最小compact數量檔案,預設是3,continue
-------------->大于最大compact數量檔案,預設是10,continue
-------------->擷取這部分檔案的大小
-------------->如果這部分檔案數量比上次選擇方案的檔案還小,替換為最小檔案方案
-------------->大于memstore flush的大小128m并且符合有一個檔案不滿這個公式(filesize(i) <= ( 檔案總大小- filesize(i) ) * ratio),continue
(注意上面的ratio是幹嘛的,這個和前面提到的非高峰時間的數值有關系,非高峰時段這個數值是5,高峰時間段這個值是1.2, 這說明高峰時段不允許compact過大的檔案)
-------------->開始判斷是不是最優的選擇(下面講的maybestuck是從selectcompaction傳入的,可選擇的檔案超過7個的情況,上面黃色那部分代碼)
1)如果maybestuck并且不是初次,如果 檔案平均大小 > 上次選擇的檔案的平均大小*1.05, 替換上次的選擇檔案方案成為最優解;
2)初次或者不是maybestuck的情況,檔案更多的或者檔案相同、總檔案大小更小的會成為最新的選擇檔案方案;
如果經過比較之後的最優檔案選擇方案不為空,就把它傳回,否則就把最小檔案方案傳回。
下面是之前的ratio的參數值,需要配合之前提到的參數配合使用的。
到這裡先來個小結吧,從上面可以看得出來,這個minor compaction的檔案選擇政策就是選小的來,選最多的小檔案來合并。
之前的代碼我再貼一下,省得大家有點淩亂。
我們去看compactionrunner的run方法吧,它也在目前的類裡面。
先是對region進行compact,如果完成了,判斷一下優先級,優先級小于等于0,請求系統級别的compaction,否則請求split。
我們還是先看hregion的compact方法,compact開始前,它要先上讀鎖,不讓讀了,然後調用hstore中的compact方法。
comact生成新檔案的方法很簡單,給源檔案建立一個storescanner,之前說過storescanner能從多個scanner當中每次都取出最小的kv,然後用storefile.append的方法不停地追加寫入即可,這些過程在前面的章節都介紹過了,這裡不再重複。
簡單的說,就是把這些檔案合并到一個檔案去了,尼瑪,怪不得io那麼大。
剩下的就是清理工作了,這裡面有意思的就是它會記錄一筆日志到writecompactionwalrecord當中,在之間日志恢複那一章的時候,貼出來的代碼裡面有,隻是沒有詳細的講。因為走到這裡它已經完成了compaction的過程,隻是沒有把舊的檔案移入歸檔檔案當中,它挂掉重新開機的時候進行恢複幹的事情,就是替換檔案。
compact完了,要判斷一下這個,真是天才啊。
比較方法是這個,blockingfilecount的預設值是7,如果compact之後storefiles的檔案數量大于7的話,就很有可能再觸發一下,那麼major compaction觸發的可能性低,minor觸發的可能性非常大。
不過沒關系,實在選不出檔案來,它會退出的。咱們可以将它這個參數hbase.hstore.blockingstorefiles設定得大一些,弄出來一個比較大的數字。
好,我們接着看requestsplit。
先檢查一下是否可以進行split,如果可以,把中間的key傳回來。
那條件是啥?在這裡,if的條件是成立的,條件判斷在increasingtoupperboundregionsplitpolicy的shouldsplit方法當中。
<b>周遊region裡面所有的store</b>
1、store當中不能有reference檔案。
2、store.size > math.min(getdesiredmaxfilesize(), this.flushsize * (tableregionscount * (long)tableregionscount)) 就傳回ture,可以split。
getdesiredmaxfilesize()預設是10g,由這個參數來确定hbase.hregion.max.filesize, 當沒超過10g的時候它就會根據128mb * (該表在這個rs上的region數量)平方。
midkey怎麼找呢?找出最大的hstore,然後通過它來找這個分裂點,最大的檔案的中間點。
但是如果是另外一種情況,我們通過用戶端來分裂region,我們強制指定的分裂點,這種情況是按照我們設定的分裂點來進行分裂。
分裂點有了,我們接着看,我們發現它又送出了一個splitrequest線程,看run方法。
1、先獲得一個tablelock,給這個表上鎖
2、執行splittransaction的prepare方法,然後execute
3、結束了釋放tablelock
prepare方法當中,主要做了這麼件事,new了兩個新的region出來:
我們接着看execute方法,這個是重頭戲。
<b>總共分三步:</b>
1、建立子region
2、上線子region
3、更改zk當中的狀态
我們先看createdaughters
在splitstorefiles這塊的,它給每個檔案都開一個線程去進行split。
這裡其實是給每個檔案都建立了reference檔案,無論它的檔案當中包不包括splitrow。
把引用檔案生成在每個子region對應的目錄,以便下一步直接重指令目錄即可。
重命名目錄之後,就是修改meta表了,splitregion的方法是通過put來進行操作的,它修改parent的regioninfo這一列更新為最新的資訊,另外又增加了splita和splitb兩列,hri_a和hri_b則通過另外兩個put插入到meta表當中。
這個過程當中如果出現任何問題,就需要根據journal記錄的過程資訊進行復原操作。
到這裡split的過程就基本結束了,鑒于compaction和split的對io方面的巨大影響,是以在任何資料裡面都是推薦屏蔽自動執行,寫腳本在晚上自動進行這些操作。