Flume學習系列(六)---- Logger Sink原始碼解讀與自定

lanyu發表於2021-09-09

前言:接上一篇,我們使用flume自帶的logger有些bug,只能顯示前16個位元組。本文將介紹如何解決這個問題,並分析Logger Sink的原始碼進而得出自定義Sink的一般流程。

一、解決Logger Sink問題

1.1 嘗試使用maxBytesToLog屬性



    之前我們說過,有問題就去官方文件看看,有關Logger Sink的內容如下:

圖片描述

001.png

    可以看到這裡面有個maxBytesToLog的屬性,預設16,描述是Event的body從日誌中輸出的最大位元組數。那我們可以直接設定這個的值試一試。
修改flume的conf目錄下的appenIP.conf,新增logger sink的maxBytesToLog屬性:a1.sinks.k1.maxBytesToLog =1024。然後重新啟動flume。懷著信心,這次一定能成功。

圖片描述

002.jpg


1.2 自定義Logger Sink

    結果還是不行,就很鬱悶,然後準備開始看logger sink的原始碼來解決問題,在看原始碼之前我先把結果和大家說一下,我還是用上一篇的maven工程,新建了一個類MyLoggerSink,然後把LoggerSink的程式碼原封不動貼上過去,只是在關鍵位置加了一些自己的log輸出,用來方便檢視執行狀態,打包到flume的lib目錄下。然後在flume的配置檔案中進行修改,

#把sink換成我們自己的類。
a1.sinks.k1.type = com.zhb.flume.MyLoggerSink
#這個屬性設定成1024位元組
a1.sinks.k1.maxBytesToLog =1024

    啟動flume,進入到flume的bin目錄下,執行./flume-ng agent -c ../conf -f ../conf/appendIP.conf -Dflume.root.logger=INFO,console -n a1,啟動成功,有一些我們自己的狀態資訊,如下:

圖片描述

003.jpg


    可以看到先進入到configure方法中,並且成功從配置檔案中獲得到了maxBytesToLog 的值1024。還有一些有趣的東西。在沒有訊息傳送的情況下,process方法每隔幾秒鐘執行一次,如下:


圖片描述

004.jpg

    簡單看了下原始碼是SinkRunner這個類下的靜態內部類PollingRunner的作用,這個類實現是Runnable介面,裡面不斷呼叫process方法並有Thread.sleep讓執行緒休眠一段時間。(這部分原始碼我也沒有特別研讀,後面有機會再深入一下吧,又立flag。)。

    然後,接下來測試一下吧,新開一個終端,輸入很長的一串資料:echo "AppendIPAddressAppendIPAddressAppendIPAddressAppendIPAddressAppendIPAddressAppendIPAddress" | nc 127.0.0.1 50000  結果就可以了,有知道這是什麼玄學的大佬請留言。

圖片描述

005.png


圖片描述

006.jpg

    我們看下輸出的內容,因為後面我們原始碼分析的時候可能會提到:
前面的類似00000010這個是偏移量,是16進位制的,也就是每16個位元組一組,中間的是資料的ASII碼值,最後是真實資料,第一行沒有偏移量,因為在原始碼中把00000000去掉了,說是什麼資料太少,要偏移沒意義。乖乖,資料多了你又不給我顯示,你想氣死我麼?

二、Logger Sink原始碼解讀

    下面看下Logger Sink的原始碼,我會在程式碼中新增一些註釋

//繼承AbstractSink以能夠處理Event,實現Configurable 則能夠在配置檔案中配置屬性public class LoggerSink extends AbstractSink implements Configurable {  private static final Logger logger = LoggerFactory
      .getLogger(LoggerSink.class);  // Default Max bytes to dump
  public static final int DEFAULT_MAX_BYTE_DUMP = 16;  // Max number of bytes to be dumped
  private int maxBytesToLog = DEFAULT_MAX_BYTE_DUMP;  public static final String MAX_BYTES_DUMP_KEY = "maxBytesToLog";  @Override
  public void configure(Context context) {    //從配置檔案中讀取maxBytesToLog屬性的值
    String strMaxBytes = context.getString(MAX_BYTES_DUMP_KEY);    if (!Strings.isNullOrEmpty(strMaxBytes)) {      try {
        maxBytesToLog = Integer.parseInt(strMaxBytes);
      } catch (NumberFormatException e) {
        logger.warn(String.format(            "Unable to convert %s to integer, using default value(%d) for maxByteToDump",
            strMaxBytes, DEFAULT_MAX_BYTE_DUMP));
        maxBytesToLog = DEFAULT_MAX_BYTE_DUMP;
      }
    }
  } 
  //這個函式是用來讓sink消費從關聯的channel中得到的資料,SinkRunner不斷呼叫這個方法
  @Override
  public Status process() throws EventDeliveryException {
    Status result = Status.READY;
    Channel channel = getChannel();
    Transaction transaction = channel.getTransaction();
    Event event = null;    try {
      transaction.begin();      //從channel中獲得下一個event
      event = channel.take();      if (event != null) {         //列印日誌,dumpEvent方法是我們下一個要看的。
        if (logger.isInfoEnabled()) {
          logger.info("Event: " + EventHelper.dumpEvent(event, maxBytesToLog));
        }
      } else {        // No event found, request back-off semantics from the sink runner
        result = Status.BACKOFF;
      }
      transaction.commit();
    } catch (Exception ex) {
      transaction.rollback();      throw new EventDeliveryException("Failed to log event: " + event, ex);
    } finally {
      transaction.close();
    }    return result;
  }
}

    下面看dumpEvent(Event event, int maxBytes)這個方法:

public static String dumpEvent(Event event, int maxBytes) {    //其實就是完成字串的拼接
    StringBuilder buffer = new StringBuilder();    if (event == null || event.getBody() == null) {
      buffer.append("null");
    } else if (event.getBody().length == 0) {      // do nothing... in this case, HexDump.dump() will throw an exception
    } else {      byte[] body = event.getBody();      byte[] data = Arrays.copyOf(body, Math.min(body.length, maxBytes));
      ByteArrayOutputStream out = new ByteArrayOutputStream();      try {        //這條語句把data轉為為這種形式:16進位制偏移量 16進位制ASII碼 真實值
        HexDump.dump(data, 0, out, 0);
        String hexDump = new String(out.toByteArray());        // remove offset since it's not relevant for such a small dataset
        //這裡把第一次的偏移量去掉。也就是00000000去掉
        if (hexDump.startsWith(HEXDUMP_OFFSET)) {
          hexDump = hexDump.substring(HEXDUMP_OFFSET.length());
        }
        buffer.append(hexDump);
      } catch (Exception e) {        if (LOGGER.isInfoEnabled()) {
          LOGGER.info("Exception while dumping event", e);
        }
        buffer.append("...Exception while dumping: ").append(e.getMessage());
      }
      String result = buffer.toString();      //去掉換行符
      if (result.endsWith(EOL) && buffer.length() > EOL.length()) {
        buffer.delete(buffer.length() - EOL.length(), buffer.length()).toString();
      }
    }    //返回這種形式的日誌內容。
    return "{ headers:" + event.getHeaders() + " body:" + buffer + " }";
  }

三、自定義Sink初探

    透過閱讀原始碼,我們可以大致總結出自定義Sink的流程:
① 搭建flume開發環境(我用maven然後引入flume的依賴)
② 新建一個類繼承AbstractSink類,如果需要實現可在配置檔案中配置,那麼還需實現Configurable介面。
③新增自己需要的屬性並重寫process方法和configure方法(如果實現了Configurable介面)

四、總結

    本文 “解決”了logger sink的一些問題(尷尬ing),並且分析了一下logger sink的原始碼,從原始碼中總結了一下自定義Sink的方法。下一篇準備實現自定義Sink到Mysql資料庫,敬請期待。



作者:小北覓
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2035/viewspace-2815324/,如需轉載,請註明出處,否則將追究法律責任。

相關文章