寫程序很難之logstash之file input插件實現分析
前言
寫程序有時候真的有點難,要考慮各種情況。
應用在運行中,會不斷生成日誌文件。假如要實現一個日誌收集的工具,不考慮其它的分析功能,隻考慮收集,有哪些方麵要考慮的?
首先看下一般的log框架是如何輸出日誌的:
可能是這樣的:a.log.1, a.log.2, a.log.3, a.log.4, a.log.5 循環輸出;
可能是這樣的: a.2014-5-5.log, a.2014-5-6.log, a.2014-5-7.log,每天生成一個日誌文件;
可能是這樣的:log.out,每次重啟都會生成一個新的log.out,覆蓋舊的文件。
那麼,我們有哪些方麵要實現和注意的?
- 提供正則或者globs方式的通配符。
- 要能判斷文件是不是新建的。
- 如何判斷文件有沒有更新?
- 如何保存文件的讀取進度?
- 如果我們在讀取文件的過程中,文件被刪除了會怎樣?
- 如果我們在讀取文件過程中,進程掛了,讀取進度有沒有及時保存?
- 在保存文件進度時,如果掛了,重啟能不能正確恢複文件進度?
- 能不能保證讀取的內容不重複?
- 如果日誌文件很快生成,又很快刪除了,是否能保證不漏掉?
- 如果日誌文件是軟鏈接(soft link),能不能正確處理?
- 文件係統的inode會被回收利用,能不能處理這個?
- 有沒有控製讀進內存的數據的大小,防止占用過多的內存?
logstash的實現
下麵解釋下logstash是如何實現和處理上麵的問題的:
可以配置path參數(Array),其中支持globs風格的匹配,如:
path => [ "/var/log/messages", "/var/log/*.log" ]
可以配置exclude參數(Array),排除掉不需要的文件,如:
exclude => "*.gz"
利用inode來識別新文件
logstash把進度保存到所謂的sincedb裏,實際上即這樣的一個文本文件,默認是放在home目錄下的,如:
.sincedb_e794081d6134aace51b759aea8cc3be2
.sincedb_f7a0c8a0def03e0c572511ceea0b9f63
後麵是日誌文件,即path的hash值。這樣就區分了不同的文件名的日誌文件的進度保存問題。
sincedb文件裏是類似這樣的內容:
6511055 0 2051 118617881
5495516 0 2051 155859036
6348913 0 2051 148511449
上麵的4列分別是:
inode, major number, minor number, pos。
其中major number和minor number是設備相關的數字,參考:https://unix.stackexchange.com/questions/73988/linux-major-and-minor-device-numbers
inode是文件係統給文件分配的是一個號碼,參考:https://zh.wikipedia.org/wiki/Inode
因此logstash區分了設備,文件名,文件的不同版本。
這裏引出了一個新問題,用inode來判斷文件的不同版本,是否夠準確了?因為inode是會回收再使用的。
比如依次執行下麵的命令,可以發現,兩個文件的inode是一樣的:
touch test stat test rm test touch test stat test
但是因為logstash是沒有close掉文件,所以是一直持有inode,所以新的同名的日誌文件會有一個新的inode。
也正是因為這樣,如果logstash監視的日誌文件如果被刪除了,還是可以繼續把刪除的文件的內容處理完。
利用inode這點特性,有時可以做一些補救工作,比如不小心把mysql的文件刪掉了,還是可以把數據dump出來,因為mysql進程還持有數據文件的inode。
另外,logstash默認是每隔1秒就嚐試讀取文件有沒有新內容,默認是15秒就掃描,檢查有沒有新文件。對應stat_interval和discover_interval參數。
還有一些小細節:
比如每次最多隻讀取出16394字節的數據,防止占用過多的內存,每5秒判斷下是否需要保存新的pos。
如果日誌文件被刪除了,也會刪除sincedb文件。
利用rename原子性地保存pos
當讀取到新文件內容時,pos會增加,在保存新的pos到sincedb時,logstash采用了臨時文件的辦法:
先建立一個臨時文件,寫入新內容,再調用操作係統提供的remane函數,原子性地替換原來的sincedb文件。
這種實際上是比較常用的技巧了,redis也是這樣子做的。
能否保證不重複,不丟失數據?
很遺憾,這是不能的,除非是分布式事務,否則,總有可能丟失或者重複發送數據。任何日誌收集軟件或者消息隊列軟件都是如此。
實現的代碼
具體的實現代碼就不貼了,因為比較易讀,其中logstash使用了filewatch這個庫,可以用gem來安裝。相關的代碼在線查看:
https://github.com/elasticsearch/logstash/blob/v1.4.1/lib/logstash/inputs/file.rb
https://github.com/jordansissel/ruby-filewatch/tree/master/lib/filewatch
和fluentd的in_tail插件比較
fluentd也是一個很流行的日誌收集工具。
簡單再看了下fluentd的in_tail插件,發現裏麵還有自己當年提交的一個防止內存占用過大的建議:)
https://github.com/fluent/fluentd/blob/master/lib/fluent/plugin/in_tail.rb
fluentd的in_tail插件的原理和logstash的file input是差不多的,都是用inode來區分文件是否更新。
但是fluentd隻保存了inode和pos,沒有logstash那樣把設備都考慮進去了。
另外fluentd保存pos時,都是以文件追加的方式來保存的,沒有像logstash那樣是用rename文件來保存到新文件裏。顯然logstash的實現更加合理。
扯遠一點,logstash部署要比fluentd方便,盡管兩者都是用ruby寫的,不同的是logstash默認是jruby,隻要有JVM就可以跑,fluentd則要安裝ruby環境,比較麻煩。
其它的一些東東:
logstash大有一統江湖之勢,這句話忘記在哪裏看到的了。在github上的logstash的start有2000多個。
logstash + elasticsearch + Kibana的日誌收集,搜索,展現的一條龍服務非常流行。
參考:
https://unix.stackexchange.com/questions/73988/linux-major-and-minor-device-numbers
https://zh.wikipedia.org/wiki/Inode
https://github.com/elasticsearch/logstash/blob/v1.4.1/lib/logstash/inputs/file.rb
最後更新:2017-04-03 12:56:41