HBase之CF持久化系列(續2)
阿新 • • 發佈:2018-12-17
正如上篇博文所說,在本節我將為大家帶來StoreFlusher.finalizeWriter。。如果大家沒有看過我的上篇博文《HBase之CF持久化系列(續1)》,那我希望大家還是回去看一下,要不然本節的很多內容大家可能看不懂。閒話不說,讓我們來到正文。
首先來到方法StoreFlusher.finalizeWriter,如下圖所示。
1.這裡的入參writer不知道大家是否還記得,他就是在上一節中構建的StoreFileWriter。這裡呼叫了writer.appendMetadata,將一些資訊放入HFileWriterImpl.fileInfo。這裡雖然不是很複雜,但從整體考慮,我還是會簡單介紹一下。
2.然後呼叫了StoreFileWriter.close。在該方法內部,繼續呼叫了成員變數writer.close方法(HFile.Writer)。該方法完成了檔案目錄結構的組成,作為本節的重點,我將在後面詳細講解。其實,HFileWriterImpl.close方法的呼叫,我在博文《HBase資料持久化之HRegion.flushcache即CF持久化》中已經介紹的比較簡單,但是,在本節我會就該方法詳細分析。
首先,我們來到方法StoreFileWriter.appendMetadata。這裡的writer型別為HFileWriterImpl。
1.在HFileWriterImpl.appendFileInfo方法中呼叫了fileInfo.append,這裡成員變數fileInfo的實際型別為HFile.FileInfo。
2.在HFile.FileInfo.append中呼叫了put方法,將入參作為key、value置於其成員變數map中。該map實際型別為SortedMap。
關於StoreFileWriter.appendMetadata的流程,我就簡單介紹到這裡,接下來我們來了解本節的重點StoreFileWriter.close。
在StoreFileWriter.close方法中主要呼叫了HFileWriterImpl.close。這個方法在博文《HBase資料持久化之HRegion.flushcache即CF持久化》中也簡單介紹過。不過,在本節,我將詳細介紹該方法的每一個知識點。
讓我們來到方法HFileWriterImpl.close,如下圖所示。
1.呼叫了blockEncoder.saveMetadata,這裡的blockEncoder實際型別為NoOpDataBlockEncoder,而在NoOpDataBlockEncoder.saveMetadata方法中實現為空。
2.呼叫了方法finishBlock,該方法完成了成員變數blockWriter中的資料寫入成員變數outputStream中,這裡的outputStream就是在第一節中提到的通過反射機制呼叫DistributedFileSystem.create獲得的檔案輸出流。並且,將獲得的midPoint加入到dataBlockIndexWriter中,以便後期寫入。
3.呼叫writeInlineBlocks方法,遍歷成員變數inlineBlockWriters,將部分InlineBlockWriter中的資訊寫到成員變數outputStream中。由於這裡的成員變數inlineBlockWriters中的資料比較少,而且,在後面的呼叫流程中也比較重要,因此,我在這裡做簡單講解一下。
這裡的成員變數inlineBlockWriters中一共有三個成員。首先是在HFileWriterImpl的構造方法中呼叫的finishInit中,在該方法內dataBlockIndexWriter被加入到inlineBlockWriters中。然後在StoreFileWriter的構造方法中初始化成員變數generalBloomFilterWriter中呼叫了BloomFilterFactory.createGeneralBloomAtWrite,該方法的最後一個入參是成員變數writer,也就是HFileWriterImpl。在該方法內部的最後,呼叫了writer.addInlineBlockWriter,將剛剛構造的CompoundBloomFilterWriter加入到writer的成員變數inlineBlockWriters中。然後,在StoreFileWriter的構造方法中初始化成員變數deleteFamilyBloomFilterWriter中呼叫了BloomFilterFactory.createGeneralBloomAtWrite,同樣,將構造的CompoundBloomFilterWriter加入到writer的成員變數inlineBlockWriters中。
4.構造FixedFileTrailer,為了後面將資訊塞入到FixedFileTrailer中。
5.由於常規的呼叫流程中方法metaNames.isEmpty()的返回值為true,因此,這裡的迴圈我就不介紹了。
6.呼叫dataBlockIndexWriter.writeIndexBlocks將dataBlockIndexWriter中的資訊寫入到成員變數outputStream中。
7.呼叫方法metaBlockIndexWriter.writeSingleLevelIndex,
8.緊接著呼叫blockWriter.writeHeaderAndData,將上面寫入到blockWriter.userDataStream中的資料寫入到成員變數outputStream中。
9.將檔案資訊寫入到blockWriter.userDataStream中,其內部呼叫了方法fileInfo.write。
10.緊接著呼叫blockWriter.writeHeaderAndData,將上面寫入到blockWriter.userDataStream中的資料寫入到成員變數outputStream中。
11.遍歷additionalLoadOnOpenData,並且將其中的資訊寫入到成員變數outputStream中。
12.呼叫finishClose,其內部繼續完善了trailer中的資訊,然後呼叫trailer.serialize將trailer中的資訊寫入到成員變數outputStream中。
從上面的分析中,相信大家都明白了什麼。沒錯,這裡的流程就是首先將資料寫入到blockWriter.userDataStream然後寫入到成員變數outputStream中,這也是我在本節中的關鍵流程。
首先,讓我們來到方法finishBlock。看過前面的大家應該都知道了。前面已經將cell資訊新增到了HFileBlock.Writer.userDataStream或者說HFileBlock.Writer.baosInMemory。接下來就是如何將cell資訊新增到HFileWriterImpl.outputStream中。
1.呼叫blockWriter.writeHeaderAndData將cell資訊寫入到HFileWriterImpl.outputStream中。
2.呼叫getMidpoint獲得用於索引的cell的key
3.呼叫dataBlockIndexWriter.addEntry將索引資訊新增到HFileBlockIndex.BlockIndexWriter的成員變數curInlineChunk中
接下來,讓我們來到blockWriter.writeHeaderAndData,也就是HFileBlock.Writer.writeHeaderAndData,該方法比較簡單,呼叫了方法finishBlockAndWriteHeaderAndData,該方法雖然簡單,但還是值得一提。如下圖所示。
1.呼叫了ensureBlockReady,將之前寫入到HFileBlock.Writer.baosInMemory中的資料以及header寫入到成員變數onDiskBlockBytesWithHeader中
2.呼叫out.write將onDiskBlockBytesWithHeader中的資料寫入到輸出流out中
3.呼叫out.write將onDiskChecksum中的資料寫入到輸出流out中
大家看都這裡可能會有點模糊,沒有關係,耐心看下去,我會在後面畫圖示示檔案中存放的各類資訊。
讓我們來到ensureBlockReady,該方法呼叫了finishBlock,完成了將成員變數baosInMemory中的資料以及header寫入到成員變數onDiskBlockBytesWithHeader
讓我們通過程式碼來詳細分析。這裡,為了將finishBlock方法中的重點標示出來,我只截了該方法的重點片段。如下圖所示。
1.呼叫dataBlockEncodingCtx.compressAndEncrypt,由於我們在預設情況下的為不加密的,因此,這裡的返回為null。
2.將baosInMemory中的資訊封裝到Bytes中,然後賦給變數compressAndEncryptDat
3.重置onDiskBlockBytesWithHeader,避免上一次的寫入對該次寫入造成影響。接著呼叫onDiskBlockBytesWithHeader.write方法將上面剛剛初始化好的compressAndEncryptDat中的值寫到其內部的成員變數buf中。
4.接下來呼叫putHeader方法,將onDiskBlockBytesWithHeader中的前33個位元組,也就是存放header的位置填充(這些位元組一開始是空置的,這一點在前一篇博文中提到過)。
5.為onDiskBlockBytesWithHeader中的所有資料生成校驗和,然後校驗並寫入到onDiskChecksum中。
至於這裡的onDiskBlockBytesWithHeader,如果在第一次呼叫的話,這裡會將成員變數初始化,以後的話,之後呼叫其reset方法以實現重用。至於其使用的位元組數不夠存放的情況,在其write方法中會實現重新分配。
到此為止,finishBlock的完整流程就完畢了。簡單地總結一下,其主要實現了兩個功能:1.將成員變數blockWriter中的資料寫入到成員變數outputStream中。2.將其中的索引新增到成員變數dataBlockIndexWriter中。
接下來,讓我們來到方法writeInlineBlocks。如下圖所示。關於成員變數inlineBlockWriters中的的三個成員我在上面已經介紹過了。其中的三個成員有兩種型別,一種是HFileBlockIndex.BlockIndexWriter,另外一種便是CompoundBloomFilterWriter。
這裡,我們首先來看第一種情況,也就是型別為HFileBlockIndex.BlockIndexWriter。如下圖所示。這裡比較簡單,1.將成員curInlineChunk的值賦給成員變數rootChunk。2.將成員變數curInlineChunk置空。這也是為了將後面的索引塊的寫入。由於這裡預設返回為false,因此,後面的流程我在這裡就不介紹了。其實後面的幾部我在後面也會介紹到。
然後,讓我們來到CompoundBloomFilterWriter。首先來看一下方法CompoundBloomFilterWriter.shouldWriteBlock。這裡只是簡單的呼叫了方法enqueueReadyChunk,然後呼叫方法readyChunks.isEmpty判斷成員變數readyChunks是否為空。
接下來,讓我們來看一下方法enqueueReadyChunk。如下圖所示。
1.構建一個ReadyChunk用於存放存放當前chunk資訊
2.將有關資訊放入到剛剛構建的ReadyChunk中
3.將儲存chunk資訊的ReadyChunk放入到成員變數readyChunks中
4.將部分成員變數置空
在上面我已經介紹過,這裡主要有兩個CompoundBloomFilterWriter(這裡我們就回到了StoreFileWriter類中)。看過上一節的大家應該都知道,在成員變數bloomContext中存放的generalBloomFilterWriter在呼叫StoreFileWriter.append將cell資訊寫入到了chunk中。而在另一箇中並沒有。因此,在寫入generalBloomFilterWriter的情況下,這裡的返回值為true。
接著,呼叫了方法CompoundBloomFilterWriter.writeInlineBlock。
1.將readyChunks佇列中的header值取出,注意,這裡並沒有移除。
2.呼叫BloomFilterChunk.writeBloom方法,將存放在其中的資訊寫入到out流中。
在該方法後面,緊接著呼叫了blockWriter.writeHeaderAndData,將資料寫出到了成員變數outputStream中。
然後呼叫了CompoundBloomFilterWriter.blockWritten。這裡相對複雜,我在接下來講解。
讓我們來到方法CompoundBloomFilterWriter.blockWritten。如下圖所示。這裡將readyChunks佇列中的header移除,並且,將其成員firstKey加入到內部成員變數bloomBlockIndexWriter中。
到此為止。這裡的writeInlineBlocks就講解完成了。
接下來構造了FFT,也就是FixedFileTrailer。
我們繼續將流程往下推進,接下來就來到了方法dataBlockIndexWriter.writeIndexBlocks。如下圖所示。這裡的方法比較長,為了突出重點,我只截取了其中比較重要的地方。
首先,大家需要記得這裡返回值rootLevelIndexPos(由於我這裡只含root level的情況),這裡是即將寫入root level是的位置。
大家是否還記得上面講解的finishBlock中呼叫了dataBlockIndexWriter.addEntry,接著呼叫了方法writeInlineBlocks將其中curInlineChunk的值賦給了rootChunk。這裡將rootChunk中的資料寫入到blockStream,然後再呼叫blockWriter.writeHeaderAndData將其寫入到入參out中。
接著呼叫了metaBlockIndexWriter的一系列方法,這裡的方法呼叫流程與上面大體一致,也並不複雜,我在這裡就不介紹了。
我們接著往下走,來到方法writeFileInfo。
1.呼叫方法finishFileInfo完善成員變數fileInfo中的相關變數
2.呼叫fileInfo.write,將其成員變數map中的資訊使用pb格式序列化後,寫入到輸出流中(這裡的輸出流並不是HFileWriterImpl.outputStream,而是blockWriter.startWriting的返回值)。
緊接著,就呼叫了blockWriter.writeHeaderAndData將剛剛的資訊寫入到輸出流HFileWriterImpl.outputStream中。
然後,開始遍歷additionalLoadOnOpenData,以將其中的值寫入到成員變數outputStream中。
關於additionalLoadOnOpenData,我在之前沒有提到過,這裡簡單介紹一下。
讓我們回到StoreFileWriter.close,如下圖所示。下面的兩個方法我在之前沒有提到過。由於兩個方法比較相似,而且,在我們研究的這個場景中並沒有DeleteFamily。
讓我們來到方法StoreFileWriter.closeGeneralBloomFilter。這裡呼叫了addGeneralBloomFilter,將入參generalBloomFilterWriter新增到上面提到的additionalLoadOnOpenData。
也就是說,遍歷additionalLoadOnOpenData時,呼叫了blockWriter.writeBlock,而在該方法內部,呼叫了bw.writeToBlock,也就是下圖所示的writeToBlock。
上面的bfw.getMetaWriter().write呼叫了CompoundBloomFilterWriter.MetaWriter.write方法,如下圖所示。不知道大家是否還記得,在上面的writeInlineBlocks方法中,最後呼叫了bloomBlockIndexWriter.addEntry。而下圖所示的bloomBlockIndexWriter也正是前面所述的bloomBlockIndexWriter。這裡,再次將資訊輸出到out中。所不同的是,這裡只是其索引值。大家可能對這裡比較迷惑,為什麼都一樣。其實,他們都是StoreFileWriter.generalBloomFilterWriter。一次是在StoreFileWriter構造時呼叫BloomFilterFactory.createGeneralBloomAtWrite,將generalBloomFilterWriter加入到HFileWriterImpl.inlineBlockWriters(雖然這時generalBloomFilterWriter為null),另外一次就是StoreFileWriter.close呼叫時,間接呼叫到closeGeneralBloomFilter,然後將generalBloomFilterWriter加入到HFileWriterImpl.additionalLoadOnOpenData。
然後呼叫了bloomBlockIndexWriter.writeSingleLevelIndex。這個方法裡面的相關呼叫流程我們在上面提到過,這裡就不贅述了。
最後,呼叫了finishClose方法。如下圖所示。
1.呼叫trailer.serialize,將使用PB序列化後的資料寫入到輸出流outputStream中,這裡比較複雜,我在後面會詳細介紹。
2.呼叫outputStream.close,將outputStream中的資料持久化到磁碟上。
讓我們來到FixedFileTrailer.serialize,如下圖所示。
1.呼叫BlockType.TRAILER.write寫入8個位元組的magic值
2.呼叫方法serializeAsPB,將PB序列化的資料輸出到baosDos中
3.編碼主要版本和次要版本並且輸出到baosDos
4.將baos中的內容輸出到流outputStream中
讓我們來詳細檢視方法serializeAsPB。如下圖所示。
1.呼叫toProtobuf().writeDelimitedTo 、baos.writeTo將FFT中的PB後資訊寫入到入參output中
2.計算padding塊並且將其中內容填充為0
關於這裡的公式getTrailerSize() - NOT_PB_SIZE - baos.size()大家可能有點模糊。我在這裡簡單介紹一下。
由於我這裡是3.0版本,因此這裡getTrailerSize的返回值是4096,然後NOT_PB_SIZE的固定值是12個位元組,前面的8個位元組是填充的magic值,後面4個位元組是用來存放上面提到的——編碼主要版本和次要版本。
以及上面呼叫baosDos.writeInt就是將其版本值寫入。
到這裡,本節的內容就講解完成了。本節,我主要沿著上一節的內容,介紹了StoreFlusher.finalizeWriter。下一節,我會從方法HStore.validateStoreFile開始講起。歡迎大家關注。