【Java】【Fulme】Flume-NG原始碼閱讀之SpoolDirectorySource

來源:互聯網
上載者:User

標籤:

        org.apache.flume.source.SpoolDirectorySource是flume的一個經常使用的source,這個源支援從磁碟中某目錄擷取檔案資料。不同於其它非同步源,這個源可以避免重新啟動或者發送失敗後資料丟失。flume可以監控目錄,當出現新檔案時會讀取該檔案並擷取資料。當一個給定的檔案被所有讀入到通道中時,該檔案會被重新命名以標誌已經完畢。同一時候,該源須要一個清理進程來定期移除完畢的檔案。

  通道可選地將一個完畢路徑的原始檔案插入到每一個事件的hearder域中。在讀取檔案時,source快取檔案資料到記憶體中。同一時候,須要確定設定了bufferMaxLineLength選項,以確保該資料遠大於輸入資料中資料最長的某一行。

     注意!!!channel僅僅接收spooling directory中唯一命名的檔案。假設檔案名稱反覆或檔案在讀取過程中被改動,則會有讀取失敗返回異常資訊。這樣的情境下,同名的檔案拷貝到這個檔案夾時建議帶唯一標示,比方時間戳記。

     一、configure(Context context)方法。代碼例如以下:

public void configure(Context context) {    spoolDirectory = context.getString(SPOOL_DIRECTORY);    Preconditions.checkState(spoolDirectory != null,        "Configuration must specify a spooling directory");    completedSuffix = context.getString(SPOOLED_FILE_SUFFIX,        DEFAULT_SPOOLED_FILE_SUFFIX);    deletePolicy = context.getString(DELETE_POLICY, DEFAULT_DELETE_POLICY);    fileHeader = context.getBoolean(FILENAME_HEADER,        DEFAULT_FILE_HEADER);    fileHeaderKey = context.getString(FILENAME_HEADER_KEY,        DEFAULT_FILENAME_HEADER_KEY);    batchSize = context.getInteger(BATCH_SIZE,        DEFAULT_BATCH_SIZE);    inputCharset = context.getString(INPUT_CHARSET, DEFAULT_INPUT_CHARSET);    ignorePattern = context.getString(IGNORE_PAT, DEFAULT_IGNORE_PAT);    trackerDirPath = context.getString(TRACKER_DIR, DEFAULT_TRACKER_DIR);    deserializerType = context.getString(DESERIALIZER, DEFAULT_DESERIALIZER);    deserializerContext = new Context(context.getSubProperties(DESERIALIZER +        "."));    // "Hack" to support backwards compatibility with previous generation of    // spooling directory source, which did not support deserializers    Integer bufferMaxLineLength = context.getInteger(BUFFER_MAX_LINE_LENGTH);    if (bufferMaxLineLength != null && deserializerType != null &&        deserializerType.equals(DEFAULT_DESERIALIZER)) {      deserializerContext.put(LineDeserializer.MAXLINE_KEY,          bufferMaxLineLength.toString());    }  }

1、spoolDirectory是監控檔案夾,不可為空,沒有預設值。這個source不具有監控子檔案夾的功能,也就是不能遞迴監控。假設須要,這須要自己去實現,http://blog.csdn.net/yangbutao/article/details/8835563 這裡有遞迴檢測的實現;

  2、completedSuffix是檔案讀取完畢後給完畢檔案加入的標記尾碼,預設是".COMPLETED";

  3、deletePolicy這是是否刪除讀取完成的檔案,預設是"never",就是不刪除,眼下僅僅支援"never"和“IMMEDIATE”;

  4、fileHeader是否在event的Header中加入檔案名稱,boolean類型

  5、fileHeaderKey這是event的Header中的key,value是檔案名稱

  6、batchSize這個是一次處理的記錄數,預設是100;

  7、inputCharset編碼方式,預設是"UTF-8";

  8、ignorePattern忽略合格檔案名稱

  9、trackerDirPath被處理檔案中繼資料的隱藏檔夾,預設".flumespool"

  10、deserializerType將檔案裡的資料序列化成event的方式,預設是“LINE”---org.apache.flume.serialization.LineDeserializer

  11、deserializerContext這個主要用在Deserializer中設定編碼方式outputCharset和檔案每行最大長度maxLineLength。

  

  二、start()方法。代碼例如以下:

public void start() {    logger.info("SpoolDirectorySource source starting with directory: {}",        spoolDirectory);    ScheduledExecutorService executor =        Executors.newSingleThreadScheduledExecutor();    counterGroup = new CounterGroup();    File directory = new File(spoolDirectory);    try {      reader = new ReliableSpoolingFileEventReader.Builder()          .spoolDirectory(directory)          .completedSuffix(completedSuffix)          .ignorePattern(ignorePattern)          .trackerDirPath(trackerDirPath)          .annotateFileName(fileHeader)          .fileNameHeader(fileHeaderKey)          .deserializerType(deserializerType)          .deserializerContext(deserializerContext)          .deletePolicy(deletePolicy)          .inputCharset(inputCharset)          .build();    } catch (IOException ioe) {      throw new FlumeException("Error instantiating spooling event parser",          ioe);    }    Runnable runner = new SpoolDirectoryRunnable(reader, counterGroup);    executor.scheduleWithFixedDelay(        runner, 0, POLL_DELAY_MS, TimeUnit.MILLISECONDS);    super.start();    logger.debug("SpoolDirectorySource source started");  }

 1、構建了一個org.apache.flume.client.avro.ReliableSpoolingFileEventReader的對象reader;

  2、啟動了一個每隔POLL_DELAY_MS(預設500,單位ms)運行一次SpoolDirectoryRunnable的進程;

  三、讀取並發送event進程。代碼例如以下:

private class SpoolDirectoryRunnable implements Runnable {    private ReliableSpoolingFileEventReader reader;    private CounterGroup counterGroup;    public SpoolDirectoryRunnable(ReliableSpoolingFileEventReader reader,        CounterGroup counterGroup) {      this.reader = reader;      this.counterGroup = counterGroup;    }    @Override    public void run() {      try {        while (true) {          List<Event> events = reader.readEvents(batchSize);  //讀取batchSize個記錄          if (events.isEmpty()) {            break;          }          counterGroup.addAndGet("spooler.events.read", (long) events.size());          getChannelProcessor().processEventBatch(events);  //將events批量發送到channel          reader.commit();        }      } catch (Throwable t) {        logger.error("Uncaught exception in Runnable", t);        if (t instanceof Error) {          throw (Error) t;        }      }    }  }

  該進程實現了批量讀取reader所指向的檔案的資料,並發送到channel。

四、org.apache.flume.client.avro.ReliableSpoolingFileEventReader的構造方法首先是先嘗試對spoolDirectory是否有建立檔案、讀、寫、刪除等許可權;然後在構造"$spoolDirectory/.flumespool/.flumespool-main.meta"中繼資料檔案

五、上面SpoolDirectoryRunnable.run方法中的List<Event> events = reader.readEvents(batchSize),是org.apache.flume.client.avro.ReliableSpoolingFileEventReader.readEvents(batchSize):

 public List<Event> readEvents(int numEvents) throws IOException {    if (!committed) {      if (!currentFile.isPresent()) {//為空白,假設Optional包括非null的引用(引用存在),返回true        throw new IllegalStateException("File should not roll when " +            "commit is outstanding.");      }      logger.info("Last read was never committed - resetting mark position.");      currentFile.get().getDeserializer().reset();    } else {//已經committed成功      // Check if new files have arrived since last call      //Returns true if this holder contains a (non-null) instance      if (!currentFile.isPresent()) {//為空白,擷取下一個檔案,初次調用        currentFile = getNextFile();      }      // Return empty list if no new files      if (!currentFile.isPresent()) {//為空白,已經沒有可讀的檔案了        return Collections.emptyList();      }    //其他的說明是currentFile眼下還在讀    }    EventDeserializer des = currentFile.get().getDeserializer();    List<Event> events = des.readEvents(numEvents);//加入event的body    /* It's possible that the last read took us just up to a file boundary.     * If so, try to roll to the next file, if there is one. */    if (events.isEmpty()) {      retireCurrentFile();  //改名字      currentFile = getNextFile();//換下一個檔案      if (!currentFile.isPresent()) {        return Collections.emptyList();      }      events = currentFile.get().getDeserializer().readEvents(numEvents);//繼續讀,加入event的body    }    if (annotateFileName) {      String filename = currentFile.get().getFile().getAbsolutePath();      for (Event event : events) {        event.getHeaders().put(fileNameHeader, filename);//加入header      }    }    committed = false;    lastFileRead = currentFile;    return events;  }

1,committed初始化時是true,所以第一次執行就是通過getNextFile()擷取當前要去讀的檔案。假設是空就返回空值了。

2,使用deserializer(預設是org.apache.flume.serialization.LineDeserializer)的readEvents(numEvents)去批量讀資料封裝成event。

3,如擷取的批量events為空白,說明這個檔案讀完了,須要對這個讀完的檔案做個“刪除”(retireCurrentFile()方法,在這也會刪除中繼資料檔案),就是依據deletePolicy(刪除還是加入去讀完成尾碼completedSuffix);可是這個本方法是有返回值的就是events,所以須要擷取下一個檔案,即再次執行getNextFile(),並events = currentFile.get().getDeserializer().readEvents(numEvents)

4,是否要對這些events的Header中加入檔案名稱

5,committed = false;    lastFileRead = currentFile; 並返回events。

這種方法還有幾點須要解釋:

其一、就是committed參數,此參數關係到這一批量的event是否已經正確處理完成。能夠看到上面的5中所講,每調用一次ReliableSpoolingFileEventReader.readEvents(batchSize)均會在最後將committed設定為false,可是在SpoolDirectoryRunnable.run()方法中也能夠看出在調用readEvents方法後還會調用ReliableSpoolingFileEventReader.commit()方法,代碼例如以下:

/** Commit the last lines which were read. */  @Override  public void commit() throws IOException {    if (!committed && currentFile.isPresent()) {      currentFile.get().getDeserializer().mark();      committed = true;    }  }

這種方法說明滿足兩個條件就能夠:一、向trackerFile寫入讀到的記錄位置,mark()方法會將syncPosition寫入trackerFile,而ResettableFileInputStream中的position用來暫存位置添加的,待到何時會syncPosition=position,這樣是為了防止出現異常時用於恢複丟失的資料;二、將committed  = true。兩個條件:一個是committed=false,這個運行完readEvents最後會置為false;二、currentFile“非空”,代表有正在讀的檔案。假設committed在readEvents中開始時為false,說明:一、event提交到channel時出現了問題,沒有運行reader.commit;二、currentFile已經“為空白”,說明沒有能夠讀的檔案。這兩點也體如今readEvents開始部分,committed=false時,假設沒有可讀檔案就會拋出異常File should not roll when commit is outstanding.";假設是在提交到channel時出問題會通過currentFile.get().getDeserializer().reset()又一次撤回到上次正確提交channel的位置,這樣能夠使得不遺失資料。

其二、就是getNextFile()方法。這種方法會首先過濾檢測檔案夾的子檔案夾(也就是不能遞迴)、隱藏檔案(以"."開頭的檔案)、已經讀完的檔案(有completedSuffix尾碼的)、符合ignorePattern的檔案;然後將過濾後的檔案按時間的先後順序排序,再建立一個新的相應的中繼資料檔案;構造一個讀取檔案的輸入資料流ResettableFileInputStream,並將此輸入資料流作為參數傳遞給deserializer,終於返回一個Optional.of(new FileInfo(nextFile, deserializer));

其三、就是LineDeserializer)的readEvents(numEvents)方法。這種方法會多次(numEvents)調用LineDeserializer(預設)的readLine()擷取一行資料封裝成event。readLine()會通過org.apache.flume.serialization.ResettableFileInputStream.readChar()不斷的去擷取資料,讀完正行後推斷每行的長度是否超過規定值maxLineLength。readChar()方法除了不斷讀取一個字元外,還會記下字元的位置,等待將位置寫入中繼資料檔案裡(通過deserializer.mark()寫入)



【Java】【Fulme】Flume-NG原始碼閱讀之SpoolDirectorySource

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.