Flume學習系列(六)---- Logger Sink原始碼解讀與自定
前言:接上一篇,我們使用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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- flume自定義 ES SINk外掛,AVRO格式資料寫入ESVR
- vuex 原始碼:原始碼系列解讀總結Vue原始碼
- Flume-NG原始碼閱讀之FileChannel原始碼
- Flume-NG原始碼閱讀之AvroSink原始碼VRROS
- EOS原始碼學習系列原始碼
- Flume-ng HDFS sink原理解析
- 精讀《原始碼學習》原始碼
- redux v3.7.2原始碼解讀與學習之 applyMiddlewareRedux原始碼APP
- Flink 從 0 到 1 學習 —— 如何自定義 Data Sink ?
- Flume學習——Flume的架構架構
- Dubbo2.7.3版本原始碼學習系列六: Dubbo服務匯出原始碼解析原始碼
- redux v3.7.2原始碼詳細解讀與學習之composeRedux原始碼
- jQuery 原始碼學習 (六) 選擇器jQuery原始碼
- 【菜鳥讀原始碼】halo✍原始碼學習 (一)原始碼
- Flume-NG原始碼閱讀之Interceptor(原創)原始碼
- 【解讀 ahooks 原始碼系列】DOM篇(一)Hook原始碼
- MMKV原始碼解讀與理解原始碼
- Flume學習——BasicChannelSemantics
- Flume學習——BasicTransactionSemantics
- [從原始碼學設計] Flume 之 memory channel原始碼
- thinkphp5.1原始碼閱讀與學習(一、路由解析)PHP原始碼路由
- Flume-NG原始碼閱讀之SinkGroups和SinkRunner原始碼
- Flink kafka source & sink 原始碼解析Kafka原始碼
- element-ui - 原始碼學習 - 自定義事件UI原始碼事件
- 學習JUC原始碼(2)——自定義同步元件原始碼元件
- D3原始碼解讀系列之Chord原始碼
- D3原始碼解讀系列之Dispatches原始碼
- D3原始碼解讀系列之Force原始碼
- D3原始碼解讀系列之Hierarchies原始碼
- D3原始碼解讀系列之Path原始碼
- D3原始碼解讀系列之Requests原始碼
- mobx原始碼解讀—— autorun 與 observable原始碼
- Redux 原始碼解讀 —— 從原始碼開始學 ReduxRedux原始碼
- YYImage原始碼剖析與學習原始碼
- Retrofit 原始碼學習與使用原始碼
- Flume基礎學習
- AFNetworking 3.0 原始碼解讀(六)之 AFHTTPSessionManager原始碼HTTPSession
- Redux 學習筆記 – 原始碼閱讀Redux筆記原始碼