Flume-NG原始碼閱讀之FileChannel

玖瘋發表於2014-05-18

  FileChannel是flume一個非常重要的channel元件,非常常用。這個channel非常複雜,涉及的檔案更多涉及三個包:org.apache.flume.channel.file、org.apache.flume.channel.file.encryption(加密)、org.apache.flume.channel.file.proto共計40個原始碼檔案。

  一、configure(Context context)方法:

  1、首先獲取配置檔案中的checkpointDir和dataDirs屬性,這是存放檢查點和資料的目錄,預設使用$user.home/.flume/file-channel/checkpoint和$user.home/.flume/file-channel/data來;checkpointDir是一個目錄,而dataDirs可以是多個以“,”分割;且這兩個目錄最好不要來回修改,因為裡面儲存著資料呢;

  2、獲取容量capacity,並做一些檢查比如是否<0,是否是動態載入(有無變化?),預設1000000;這指的是checkpoint檔案存放event資訊的最大容量。

  3、keepAlive超時時間,就是如果channel中沒有資料最長等待時間,預設3s;

  4、transactionCapacity事務的最大容量,預設1000;

  5、checkpointInterval檢查點寫入間隔,預設30000ms;

  6、maxFileSize,data檔案大小的上限,使用者設定的和1623195647 之間較小的那個;

  7、最少需要多少空間minimumRequiredSpace,max((使用者配置的,500M),1M);

  8、useLogReplayV1,預設false;

  9、useFastReplay,預設false;

  10、encryptionActiveKey,加密金鑰別名預設為null;

  11、encryptionContext加密配置資訊;

  12、encryptionCipherProvider加密密碼提供者,預設值為null

  13、encryptionKeyProviderName,加密金鑰提供者,預設值為null;

  14、queueRemaining,佇列是否有剩餘空間訊號量,初始化容量為capacity;

  15、設定Log log的檢查間隔checkpointInterval和maxFileSize最大檔案大小。

  16、是否新建一個計數器channelCounter。

  二、start()方法。

  1、通過Log.Builder()構建了一個builder物件,並設定了相應的引數,然後log = builder.build(),Log的構造方法會對checkpointDir及logDirs嘗試獲取鎖操作,所以如果存在多個file channel則checkpointDir及logDirs最好配置在多個磁碟下或者多個目錄下,否則只能一個獲得初始化;Log用來將封裝好的FlumeEvent寫入磁碟並將指向這些event的指標存入一個記憶體佇列queue。會建立一個執行緒工作內容就是每隔checkpointInterval毫秒,預設30s寫一次檢查點log.writeCheckpoint(),會將checpoint、inflightTakes、inflightPuts都重新整理至磁碟,會先後將inflightPuts、inflightTakes、checkpoint.meta重建,更新checkpoint檔案並重新整理至磁碟,這些檔案都在checkpointDir目錄下;更新log-ID.meta檔案;同時肩負起刪除log檔案及其對應的meta檔案的責任。

  2、log.replay(),一旦一個Log物件被建立,則需要呼叫replay()方法使用queue最新的檢查點來調整磁碟上的write ahead log。會獲取最大的fileID;然後讀取log檔案根據record的型別進行相應的操作,進行恢復;遍歷所有的data目錄,然後roll(index)建立LogFile.Writer(空的);然後將queue重新整理到相關檔案。

  3、 open = true,表示channel開啟;

  4、depth = getDepth(),FlumeEventQueue的大小,然後需要判斷一下queueRemaining是否有足夠的剩餘量queueRemaining.tryAcquire(depth);

  5、如果open==true,計數器開始工作。

  三、createTransaction()方法主要是構造一個FileBackedTransaction物件用來直接操作channel,並返回。

  四、stop()停止channel,清理資料。

  五、靜態類FileBackedTransaction extends BasicTransactionSemantics。類似可參考memory channel,必須要實現doTake()、doCommit()、doRollback()、doPut()四個方法。put和take不能同時操作。

  1、doPut(Event event)方法,該方法source會通過transaction.put()方法呼叫。檢查LinkedBlockingDeque<FlumeEventPointer> putList是否有剩餘空間(putList.remainingCapacity() == 0);檢查queue是否有剩餘空間,如果沒有則等待keepAlive秒(!queueRemaining.tryAcquire(keepAlive, TimeUnit.SECONDS)),獲取一個許可;獲取log的共享鎖;FlumeEventPointer ptr = log.put(transactionID, event)將event寫入資料檔案(是RandomAccessFile),transactionID指的是每次事務的編號,且都是在上一次基礎之上加一而來,每個event都要呼叫put(Event)方法,該方法會獲取要寫入的資料檔案的目錄(配置檔案中可以配置多個data檔案目錄,這裡會依據transactionID輪訓式的向所有的目錄寫資料),由於start()方法中有log.replay()方法,該方法會遍歷所有的data目錄並roll(index)建立LogFile.Writer,logFiles.get(logFileIndex).getUsableSpace()不會為0,檢查是否剩餘空間夠,然後獲取transactionID對應檔案的寫LogFile.Writer(其實是其子類LogFileV3.Writer),如果沒有則呼叫方法 roll(logFileIndex, buffer)建立一個LogFileV3.Writer,放入logFiles(這個維持著每個data目錄對應著一個正在活動的可以用於寫的檔案)可能會根據buffer的大小滾動檔案因為單個檔案有最大限制,LogFileV3.Writer的構造方法會在這個data目錄建立一個meta檔案,寫入一些基本資料,FlumeEventPointer ptr = logFiles.get(logFileIndex).put(buffer)會把event封裝成的ByteBuffer,通過LogFile.Writer.write(buffer)方法寫入磁碟檔案(buffer會再次被封裝,最終封裝成的格式順序是:OP_RECORD、buffer.limit()、buffer。buffer的內容是(RecordType(這是1)、TransactionID、LogWriteOrderID、event.getHeaders、event.getBody())),返回Pair.of(getLogFileID(), offset);ptr由兩部分組成:fileID和在這個檔案中的偏移量offset。fileID就是Log.nextFileID,不管有幾個data目錄,始終根據這個變數設定檔案編號(檔名的後面的編號);putList.offer(ptr)加入到putList之中;queue.addWithoutCommit(ptr, transactionID)將這個方法在每個put操作中必須要被呼叫,確保檢查點之後事務的提交,它會呼叫inflightPuts.addEvent(transactionID, e.toLong())此時e.toLong()=fileID+offset,addEvent方法會執行inflightEvents.put(transactionID, pointer)、inflightFileIDs.put(transactionID,FlumeEventPointer.fromLong(pointer).getFileID())、syncRequired = true,inflightEvents中存放的就是<transactionID,fileID+offset>而inflightFileIDs存放的是<transactionID,fileID>;success = true表示put成功;log.unlockShared()解除共享鎖。put事件並未重新整理至磁碟檔案,因為並沒有commit,commit操作會導致重新整理至磁碟的操作。queue中累計的數量不能超過capacity,超過就會等待一定時間後異常。

  2、doTake()方法,該方法sink會通過transaction.take()方法呼叫。檢查LinkedBlockingDeque<FlumeEventPointer> takeList時刻有剩餘空間;然後獲取共享鎖;ptr = queue.removeHead(transactionID)取出頭部的資料,ptr的內容<fileID, offset>,頭部是邏輯上的0位置,但是物理上的頭位置會一邊take一邊變化,是從checkpoint中取出的資料,期間會inflightTakes.addEvent(transactionID, value)將資料快取在inflightTakes之中;然後放入takeList.offer(ptr);log.take(transactionID, ptr)會封裝資料(buffer會再次被封裝,最終封裝成的格式順序 是:OP_RECORD、buffer.limit()、buffer。buffer的內容是(RecordType(這是1)、 TransactionID、LogWriteOrderID、fileID、offset))然後寫入快取等待commit重新整理至磁碟;event = log.get(ptr)是從data檔案中取出資料event;最後釋放共享鎖。take操作queue.removeHead(transactionID)會從overwriteMap或者記憶體對映elementsBuffer中取出對應的head位置資料。

  3、doCommit()方法,該方法source和sink會通過transaction.commit()方法呼叫。首先獲取takeList和putList的大小;putList和takeList不能同時都>0,其中有一個得是==0;如果putList>0,獲取共享鎖,log.commitPut(transactionID)會呼叫Log.commit(long transactionID, short type)方法把commit操作封裝成一個ByteBuffer buffer(最終封裝成的格式順序是:OP_RECORD、 buffer.limit()、buffer。buffer的內容是(RecordType(這是4)、TransactionID、 LogWriteOrderID、type))寫入資料檔案,並重新整理至磁碟檔案,此重新整理也會將這次的put或者take中的所有事件寫入磁碟檔案;然後是將所有putList中的資料放入queue中queue.addTail(putList.removeFirst())當新增第一個時會從checkpointfile的最後一個位置開始先寫入overwriteMap(邏輯位置轉為物理位置)中,後續會再從0開始迴圈寫入overwriteMap,take操作也會從最後一個位置取會先檢查overwriteMap中有無對應的資料,沒有就再檢查checkpoint的記憶體對映elementsBuffer中有無(控制take位置的是head位置,控制put位置的是size),每次更新檢查點時都會把overwriteMap寫入記憶體對映elementsBuffer中並重新整理至磁碟檔案checkpoint;queue.completeTransaction(transactionID)會執行清除操作即如果inflightPuts和inflightTakes執行其一,如果inflightPuts包含transactionID則清空inflightPuts,否則清空inflightTakes;然後解鎖。如果takes>0,則獲取共享鎖,log.commitTake(transactionID)會進行封裝寫入data檔案,commit中的型別標記除了自己的表示還有要提交型別的標記這裡是TAKE,上面是PUT;queue.completeTransaction(transactionID)和上面的一樣;解除共享鎖;queueRemaining.release(takes)釋放。最後將putList和takeList清空。

  4、doRollback()方法該方法source和sink會通過transaction.rollback()方法呼叫。首先會獲取takeList和putList的大小;然後獲取共享鎖;如果takes>0,

並且puts==0,將putList中的所有有資料queue.addHead(takeList.removeLast()),addHead操作和宣告的addTail操作相似,只不過是要在呼叫add(int index, long value)

方法時index是0,會插入到第一個位置;清空putList、takeList;queue.completeTransaction(transactionID)上面已經講過;log.rollback(transactionID)會呼叫Log.rollback(long transactionID, short type)方法把commit操作封裝成一個ByteBuffer buffer(最 終封裝成的格式順序是:OP_RECORD、 buffer.limit()、buffer。buffer的內容是(RecordType(這是3)、TransactionID、 LogWriteOrderID))寫入快取中;釋放共享鎖;queueRemaining.release(puts)釋放許可。(這一段的格式亂了,這編輯器,我屮艸芔茻!!無語了。。。不自動換行了。。手動換的行。)

  1中的put操作將寫入log檔案的指標新增進了快取putList中;2中的take操作從快取中的取出指標,然如takeList中,然後寫入log檔案,從log檔案中獲取資料還原為event;3中的commit操作無論是對put的還是對take的都會講commit資訊寫入log檔案,都會清理queue中的快取(inflightPuts和inflightTakes),如果對put還要將putList中的所有資料新增進queue的隊尾,實際上是overwriteMap中,如果是對take則要釋放queueRemaining的takes個許可量,還要清空putList、takeList;4中的rollback操作會將takeList中所有資料放入queue的頭部,再清空putList、takeList,再清空queue中的快取(inflightPuts和inflightTakes),將rollback資訊寫入log,要釋放queueRemaining的puts個許可量。

 

  ps:

  1、data/log-ID,這種型別的檔案存放的是put、take、commit、rollback的操作記錄及資料。

  2、checkpoint/checkpoint存放的是event在那個data檔案logFileID,的什麼位置offset等資訊。

  2、checkpoint/inflightTakes存放的是事務take的快取資料,每隔段時間就重建檔案。內容:1、16位元組是校驗碼;2、transactionID1+eventsCount1+eventPointer11+eventPointer12+...;3、transactionID2+eventsCount2+eventPointer21+eventPointer22+...

  3、checkpoint/inflightPuts存放的是事務對應的put快取資料,每隔段時間就重建檔案。內容:1、16位元組是校驗碼;2、transactionID1+eventsCount1+eventPointer11+eventPointer12+...;3、transactionID2+eventsCount2+eventPointer21+eventPointer22+...

  4、checkpoint/checkpoint.meta主要儲存的是logfileID及對應event的數量等資訊。

  5、data/log-ID.meta,主要記錄log-ID下一個寫入位置以及logWriteOrderID等資訊。

    6、每個data目錄裡data檔案保持一般不超過2個,會刪除檔案編號比當前正在使用的檔案編號小的資料檔案。

  7、putList和takeList是快取儲存的是相應的FlumeEventPointer,但是inflightTakes和inflightPuts其實也是快取儲存的也是相應的資訊,只不過比兩者多存一些資訊罷了,功能重合度很高,為什麼會這樣呢?我想是一個只能在記憶體,一個可以永久儲存(當然是不斷重建的),後者可以用來進行flume再啟動的恢復。

  file channel太過複雜了,比配置的檔案的載入複雜更多,涉及的知識非常多,還不能一下子就消耗了。。。。大體的已經瞭解了,剩下的都是細節!!後續會再慢慢咀嚼!!爭取吃透file。

相關文章