Filebeat 介紹
概要
Filebeat 是使用 Golang 實現(xiàn)的輕量型日志采集器悔雹,也是 Elasticsearch stack 里面的一員屠阻。本質(zhì)上是一個 agent 讨永,可以安裝在各個節(jié)點(diǎn)上,根據(jù)配置讀取對應(yīng)位置的日志抚垃,并上報到相應(yīng)的地方去腰吟。
Filebeat 的可靠性很強(qiáng),可以保證日志 At least once 的上報脑沿,同時也考慮了日志搜集中的各類問題藕畔,例如日志斷點(diǎn)續(xù)讀、文件名更改庄拇、日志 Truncated 等注服。
Filebeat 并不依賴于 ElasticSearch,可以單獨(dú)存在措近。我們可以單獨(dú)使用Filebeat進(jìn)行日志的上報和搜集溶弟。filebeat 內(nèi)置了常用的 Output 組件, 例如 kafka、ElasticSearch瞭郑、redis 等辜御,出于調(diào)試考慮,也可以輸出到 console 和 file 屈张。我們可以利用現(xiàn)有的 Output 組件擒权,將日志進(jìn)行上報袱巨。
當(dāng)然,我們也可以自定義 Output 組件碳抄,讓 Filebeat 將日志轉(zhuǎn)發(fā)到我們想要的地方愉老。
filebeat 其實是 elastic/beats 的一員,除了 filebeat 外剖效,還有 HeartBeat嫉入、PacketBeat。這些 beat 的實現(xiàn)都是基于 libbeat 框架璧尸。
整體工作原理
Filebeat 由兩個主要組件組成:harvester 和 prospector咒林。
采集器 harvester 的主要職責(zé)是讀取單個文件的內(nèi)容。讀取每個文件爷光,并將內(nèi)容發(fā)送到 the output垫竞。 每個文件啟動一個 harvester,harvester 負(fù)責(zé)打開和關(guān)閉文件蛀序,這意味著在運(yùn)行時文件描述符保持打開狀態(tài)件甥。如果文件在讀取時被刪除或重命名,F(xiàn)ilebeat 將繼續(xù)讀取文件哼拔。
查找器 prospector 的主要職責(zé)是管理 harvester 并找到所有要讀取的文件來源。如果輸入類型為日志瓣颅,則查找器將查找路徑匹配的所有文件倦逐,并為每個文件啟動一個 harvester。每個 prospector 都在自己的 Go 協(xié)程中運(yùn)行宫补。
注:Filebeat prospector只能讀取本地文件檬姥, 沒有功能可以連接到遠(yuǎn)程主機(jī)來讀取存儲的文件或日志。
由以上兩個組件一起工作來讀取文件(tail file)并將事件數(shù)據(jù)發(fā)送到您指定的輸出粉怕。
下圖是 Filebeat 官方提供的架構(gòu)圖:
其工作流程如下:當(dāng)啟動 Filebeat 程序時健民,它會啟動一個或多個查找器去檢測指定的日志目錄或文件。對于查找器 prospector 所在的每個日志文件贫贝,F(xiàn)Ilebeat 會啟動收集進(jìn)程 harvester秉犹。 每個 harvester 都會為新內(nèi)容讀取單個日志文件,并將新日志數(shù)據(jù)發(fā)送到后臺處理程序稚晚,后臺處理程序會集合這些事件崇堵,最后發(fā)送集合的數(shù)據(jù)到 output 指定的目的地。
除了圖中提到的各個組件客燕,整個 filebeat 主要包含以下重要組件:
- Crawler:負(fù)責(zé)管理和啟動各個 Input
- Input:負(fù)責(zé)管理和解析輸入源的信息鸳劳,以及為每個文件啟動 Harvester∫泊辏可由配置文件指定輸入源信息赏廓。
- Harvester: Harvester 負(fù)責(zé)讀取一個文件的信息涵紊。
- Pipeline: 負(fù)責(zé)管理緩存、Harvester 的信息寫入以及 Output 的消費(fèi)等幔摸,是 Filebeat 最核心的組件摸柄。
- Output: 輸出源,可由配置文件指定輸出源信息抚太。
- Registrar:管理記錄每個文件處理狀態(tài)塘幅,包括偏移量、文件名等信息尿贫。當(dāng) Filebeat 啟動時电媳,會從 Registrar 恢復(fù)文件處理狀態(tài)。
filebeat 的整個生命周期庆亡,幾個組件共同協(xié)作匾乓,完成了日志從采集到上報的整個過程。
安裝與使用
Filebeat 基于 Go 語言開發(fā)無其他依賴又谋,它最大的特點(diǎn)是性能穩(wěn)定拼缝、配置簡單、占用系統(tǒng)資源很少彰亥,安裝使用也非常簡單咧七,可訪問 Elastic-Beats 官網(wǎng)獲取各版本 Filebeat。因為 Filebeat 各版本之間的差異較大任斋,這里推薦7以上的新版继阻,首先進(jìn)行下載解壓:
tar -zxvf filebeat-7.tar.gz
mv filebeat-7 filebeat
cd filebeat
FileBeat啟停指令:
- 調(diào)試模式下采用:終端啟動(退出終端或 ctrl+c 會退出運(yùn)行)
./filebeat -e -c filebeat.yml
- 線上環(huán)境配合 error 級別使用:以后臺守護(hù)進(jìn)程啟動啟動 filebeats
nohup ./filebeat -e -c filebeat.yml &
- 零輸出啟動(不推薦):將所有標(biāo)準(zhǔn)輸出及標(biāo)準(zhǔn)錯誤輸出到
/dev/null
空設(shè)備,即沒有任何輸出信息废酷。
nohup ./filebeat -e -c filebeat.yml >/dev/null 2>&1 &
- 停止運(yùn)行 FileBeat 進(jìn)程
ps -ef | grep filebeat
Kill -9 線程號
FileBeat配置文件
FileBeat 的配置文件定義了在讀取文件的位置瘟檩,輸出流的位置以及相應(yīng)的性能參數(shù),本實例是以 Kafka 消息中間件作為緩沖澈蟆,所有的日志收集器都向 Kafka 輸送日志流墨辛,相應(yīng)的配置項如下,并附配置說明:
$ vim fileat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /wls/applogs/rtlog/app.log
fields:
log_topic: appName
multiline:
# pattern for error log, if start with space or cause by
pattern: '^[[:space:]]+(at|\.{3})\b|^Caused by:'
negate: false
match: after
output.kafka:
enabled: true
hosts: ["kafka-1:9092","kafka-2:9092"]
topic: applog
version: "0.10.2.0"
compression: gzip
processors:
- drop_fields:
fields: ["beat", "input", "source", "offset"]
logging.level: error
name: app-server-ip
-
paths:定義了日志文件路徑趴俘,可以采用模糊匹配模式睹簇,如
*.log
- fields:topic 對應(yīng)的消息字段或自定義增加的字段。
- output.kafka:filebeat 支持多種輸出哮幢,支持向 kafka带膀,logstash,elasticsearch 輸出數(shù)據(jù)橙垢,此處設(shè)置數(shù)據(jù)輸出到 kafka垛叨。
- enabled:這個啟動這個模塊。
- topic:指定要發(fā)送數(shù)據(jù)給 kafka 集群的哪個 topic,若指定的 topic 不存在嗽元,則會自動創(chuàng)建此 topic敛纲。
- version:指定 kafka 的版本。
- drop_fields:舍棄字段剂癌,filebeat 會 json 日志信息淤翔,適當(dāng)舍棄無用字段節(jié)省空間資源。
- name:收集日志中對應(yīng)主機(jī)的名字佩谷,建議 name 這里設(shè)置為 IP旁壮,便于區(qū)分多臺主機(jī)的日志信息。
以上參數(shù)信息谐檀,需要用戶個性化修改的主要是:paths抡谐,hosts,topic桐猬,version 和 name麦撵。
異常堆棧的多行合并問題
在收集日志過程中還常常涉及到對于應(yīng)用中異常堆棧日志的處理,此時有兩種方案溃肪,一種是在采集是歸并免胃,一種是 Logstash 過濾時歸并,更建議在客戶端 agent 上直接實現(xiàn)堆棧的合并惫撰,把合并操作的壓力在輸入源頭上進(jìn)行控制羔沙,filebeat 合并行的思路有兩種,正向和逆向處理厨钻。由于 filebeat 在合并行的時候需要設(shè)置 negate 和 match 來決定合并動作撬碟,意義混淆,簡直是一種糟糕的設(shè)計莉撇,直接附上配置源碼和說明便于理解。
第一種:符合條件才合并惶傻,容易有漏網(wǎng)之魚
說明:將以空格開頭的所有行合并到上一行棍郎;并把以Caused by開頭的也追加到上一行
multiline:
pattern: '^[[:space:]]+(at|\.{3})\b|^Caused by:'
negate: false
match: after
negate 參數(shù)為 false,表示“否定參數(shù)=false”银室。multiline 多行參數(shù)負(fù)負(fù)得正涂佃,表示符合 pattern、match 條件的行會融入多行之中蜈敢、成為一條完整日志的中間部分辜荠。如果match=after,則以b開頭的和前面一行將合并成一條完整日志抓狭;如果 match=before伯病,則以 b 開頭的和后面一行將合并成一條完整日志。
第二種:不符合條件通通合并否过,需事先約定
說明:約定一行完整的日志開頭必須是以“[”開始午笛,不符合則歸并惭蟋。
multiline:
pattern: '^\['
negate: true
match: after
negate 參數(shù)為 true,表示“否定參數(shù)=true”药磺。multiline 多行參數(shù)為負(fù)告组,表示符合 match 條件的行是多行的開頭,是一條完整日志的開始或結(jié)尾癌佩。如果 match=after木缝,則以 b 開頭的行是一條完整日志的開始,它和后面多個不以 b 開頭的行組成一條完整日志围辙;如果 match=before我碟,則以 b 開頭的行是一條完整日志的結(jié)束,和前面多個不以 b 開頭的合并成一條完整日志酌畜。
最后怎囚,如果對 FileBeat 占用資源的要求比較苛刻,有如下幾個參數(shù)可以配置:
- 采用文件緩沖限制內(nèi)存使用:
queue.spool:
file:
path: "tmp/spool.dat" #緩沖區(qū)路徑
size: 512MiB #緩沖區(qū)大小
page_size: 16KiB #文件頁面大小桥胞,采用16kb默認(rèn)值
write:
buffer_size: 10MiB #寫緩沖大小
flush.timeout: 5s #寫緩沖最舊事件的最長等待時間
flush.events: 1024 #緩沖事件數(shù)量恳守,滿足則刷新。
- 文件資源優(yōu)化贩虾,filebeat 是貪婪式的采集催烘,只要有日志就會堅持采集完日志,否則就會永久持有文件句柄不“放手”缎罢,可設(shè)置文件資源配置參數(shù)優(yōu)化:
close_inactive: 1m
#沒有新日志多長時間關(guān)閉文件句柄伊群,默認(rèn)5分鐘可改短一些
clean_inactive: 72h
#多久清理一次registry文件,默認(rèn)值為0策精,運(yùn)行時間長可能會導(dǎo)致該文件變大帶來性能問題舰始。
- CPU最大數(shù)量使用限制:
max_procs: 4
其他原理擴(kuò)充
日志采集流程
Filebeat 不僅支持普通文本日志的作為輸入源,還內(nèi)置支持了 redis 的慢查詢?nèi)罩狙释唷tdin丸卷、tcp 和 udp 等作為輸入源。
本文只分析下普通文本日志的處理方式询刹,對于普通文本日志谜嫉,可以按照以下配置方式,指定 log 的輸入源信息凹联。
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/*.log
其中 Input 也可以指定多個, 每個 Input 下的 Log 也可以指定多個沐兰。
filebeat 啟動時會開啟 Crawler,對于配置中的每條 Input蔽挠,Crawler 都會啟動一個 Input 進(jìn)行處理住闯,代碼如下所示:
func (c *Crawler) Start(...){
...
for _, inputConfig := range c.inputConfigs {
err := c.startInput(pipeline, inputConfig, r.GetStates())
if err != nil {
return err
}
}
...
}
由于指定的 paths 可以配置多個,而且可以是 Glob 類型,因此 Filebeat 將會匹配到多個配置文件寞秃。
Input 對于每個匹配到的文件斟叼,都會開啟一個 Harvester 進(jìn)行逐行讀取,每個 Harvester 都工作在自己的的 goroutine 中春寿。
Harvester 的工作流程非常簡單朗涩,就是逐行讀取文件,并更新該文件暫時在 Input 中的文件偏移量(注意绑改,并不是 Registrar 中的偏移量)谢床,讀取完成則結(jié)束流程。
同時厘线,我們需要考慮到识腿,日志型的數(shù)據(jù)其實是在不斷增長和變化的:
- 會有新的日志在不斷產(chǎn)生
- 可能一個日志文件對應(yīng)的 Harvester 退出后,又再次有了內(nèi)容更新造壮。
為了解決這兩個情況渡讼,filebeat 采用了 Input 定時掃描的方式。代碼如下耳璧,可以看出成箫,Input 掃描的頻率是由用戶指定的 scan_frequency
配置來決定的 (默認(rèn) 10s 掃描一次)。
func (p *Runner) Run() {
p.input.Run()
if p.Once {
return
}
for {
select {
case <-p.done:
logp.Info("input ticker stopped")
return
case <-time.After(p.config.ScanFrequency): // 定時掃描
logp.Debug("input", "Run input")
p.input.Run()
}
}
}
此外旨枯,如果用戶啟動時指定了 --once
選項蹬昌,則掃描只會進(jìn)行一次,就退出了攀隔。
日志定時掃描及異常處理
我們之前講到 Registrar 會記錄每個文件的狀態(tài)皂贩,當(dāng) Filebeat 啟動時,會從 Registrar 恢復(fù)文件處理狀態(tài)昆汹。
其實在 filebeat 運(yùn)行過程中明刷,Input 組件也記錄了文件狀態(tài)。不一樣的是满粗,Registrar 是持久化存儲遮精,而 Input 中的文件狀態(tài)僅表示當(dāng)前文件的讀取偏移量,且修改時不會同步到磁盤中败潦。
每次,F(xiàn)ilebeat 剛啟動時准脂,Input 都會載入 Registrar 中記錄的文件狀態(tài)劫扒,作為初始狀態(tài)。Input 中的狀態(tài)有兩個非常重要:
- offset: 代表文件當(dāng)前讀取的 offset狸膏,從 Registrar 中初始化沟饥。Harvest 讀取文件后,會同時修改 offset。
- finished: 代表該文件對應(yīng)的 Harvester 是否已經(jīng)結(jié)束贤旷,Harvester 開始時置為 false广料,結(jié)束時置為 False。
對于每次定時掃描到的文件幼驶,概括來說艾杏,會有三種大的情況:
- Input 找不到該文件狀態(tài)的記錄, 說明是新增文件,則開啟一個 Harvester盅藻,從頭開始解析該文件
- 如果可以找到文件狀態(tài)购桑,且 finished 等于 false。這個說明已經(jīng)有了一個 Harvester 在處理了氏淑,這種情況直接忽略就好了勃蜘。
- 如果可以找到文件狀態(tài),且 finished 等于 true假残。說明之前有 Harvester 處理過缭贡,但已經(jīng)處理結(jié)束了。
對于這種第三種情況辉懒,我們需要考慮到一些異常情況阳惹,F(xiàn)ilebeat 是這么處理的:
- 如果 offset 大于當(dāng)前文件大小:說明文件被 Truncate 過耗帕,此時按做一個新文件處理穆端,直接從頭開始解析該文件
- 如果 offset 小于當(dāng)前文件大小,說明文件內(nèi)容有新增仿便,則從上次 offset 處繼續(xù)讀即可体啰。
對于第二種情況,F(xiàn)ilebeat 似乎有一個邏輯上的問題: 如果文件被 Truncate 過嗽仪,后來又新增了數(shù)據(jù)荒勇,且文件大小也比之前 offset 大,那么 Filebeat 是檢查不出來這個問題的闻坚。
除此之外沽翔,一個比較有意思的點(diǎn)是,F(xiàn)ilebeat 甚至可以處理文件名修改的問題窿凤。即使一個日志的文件名被修改過仅偎,F(xiàn)ilebeat 重啟后,也能找到該文件雳殊,從上次讀過的地方繼續(xù)讀橘沥。
這是因為 Filebeat 除了在 Registrar 存儲了文件名,還存儲了文件的唯一標(biāo)識夯秃。對于 Linux 來說座咆,這個文件的唯一標(biāo)識就是該文件的 inode ID + device ID痢艺。
至此,我們可以清楚的知道介陶,F(xiàn)ilebeat 是如何采集日志文件堤舒,同時做到監(jiān)聽日志文件的更新和修改。而日志采集過程哺呜,Harvest 會將數(shù)據(jù)寫到 Pipeline 中舌缤。我們接下來看下數(shù)據(jù)是如何寫入到 Pipeline 中的。
Pipeline 的寫入
Haveseter 會將數(shù)據(jù)寫入緩存中弦牡,而另一方面 Output 會從緩存將數(shù)據(jù)讀走友驮。整個生產(chǎn)消費(fèi)的過程都是由 Pipeline 進(jìn)行調(diào)度的,而整個調(diào)度過程也非常復(fù)雜驾锰。
此外卸留,F(xiàn)ilebeat 的緩存目前分為 memqueue 和 spool。memqueue 顧名思義就是內(nèi)存緩存椭豫,spool 則是將數(shù)據(jù)緩存到磁盤中耻瑟。本文將基于 memqueue 講解整個調(diào)度過程。
我們首先看下 Haveseter 是如何將數(shù)據(jù)寫入緩存中的赏酥,如下圖所示:
Harvester 通過 pipeline 提供的 pipelineClient 將數(shù)據(jù)寫入到 pipeline 中喳整,Haveseter 會將讀到的數(shù)據(jù)會包裝成一個 Event 結(jié)構(gòu)體,再遞交給 pipeline裸扶。
在 Filebeat 的實現(xiàn)中框都,pipelineClient 并不直接操作緩存,而是將 event 先寫入一個 events channel 中呵晨。
同時魏保,有一個 eventloop 組件,會監(jiān)聽 events channel 的事件到來摸屠,等 event 到達(dá)時谓罗,eventloop 會將其放入緩存中。
當(dāng)緩存滿的時候季二,eventloop 直接移除對該 channel 的監(jiān)聽檩咱。
每次 event ACK 或者取消后,緩存不再滿了胯舷,則 eventloop 會重新監(jiān)聽 events channel刻蚯。
以上是 Pipeline 的寫入過程,此時 event 已被寫入到了緩存中桑嘶。
但是 Output 是如何從緩存中拿到 event 數(shù)據(jù)的炊汹?
Pipeline 的消費(fèi)過程
整個消費(fèi)的過程非常復(fù)雜,數(shù)據(jù)會在多個 channel 之間傳遞流轉(zhuǎn)不翩,如下圖所示:
首先再介紹兩個角色:
- consumer: pipeline 在創(chuàng)建的時候兵扬,會同時創(chuàng)建一個 consumer。consumer 負(fù)責(zé)從緩存中取數(shù)據(jù)
- client worker:負(fù)責(zé)接收 consumer 傳來的數(shù)據(jù)口蝠,并調(diào)用 Output 的 Publish 函數(shù)進(jìn)行上報器钟。
與 producer 類似,consumer 也不直接操作緩存妙蔗,而是會向 get channel 中寫入消費(fèi)請求傲霸。
consumer 本身是個后臺 loop 的過程,這個消費(fèi)請求會不斷進(jìn)行眉反。
eventloop 監(jiān)聽 get channel, 拿到之后會從緩存中取數(shù)據(jù)昙啄。并將數(shù)據(jù)寫入到 resp channel 中。
consumer 從 resp channel 中拿到 event 數(shù)據(jù)后寸五,又會將其寫入到 workQueue梳凛。
workQueue 也是個 channel。client worker 會監(jiān)聽該 channel 上的數(shù)據(jù)到來梳杏,將數(shù)據(jù)交給 Output client 進(jìn)行 Publish 上報韧拒。
而且,Output 收到的是 Batch Events十性,即會一次收到一批 Events叛溢。BatchSize 由各個 Output 自行決定。
至此劲适,消息已經(jīng)遞交給了 Output 組件楷掉。
Ack 機(jī)制
filebeat 之所以可以保證日志可以 at least once 的上報,就是基于其 Ack 機(jī)制霞势。
簡單來說烹植,Ack 機(jī)制就是,當(dāng) Output Publish 成功之后會調(diào)用 ACK支示,最終 Registrar 會收到 ACK刊橘,并修改偏移量。
而且, Registrar 只會在 Output 調(diào)用 batch 的相關(guān)信號時颂鸿,才改變文件偏移量促绵。其中 Batch 對外提供了這些信號:
type Batch interface {
Events() []Event
// signals
ACK()
Drop()
Retry()
RetryEvents(events []Event)
Cancelled()
CancelledEvents(events []Event)
}
Output 在 Publish 之后,無論失敗嘴纺,必須調(diào)用這些函數(shù)中的其中一個败晴。
以下是 Output Publish 成功后調(diào)用 Ack 的流程:
可以看到其中起核心作用的組件是 Ackloop。AckLoop 中有一個 ackChanList栽渴,其中每一個 ackChan尖坤,對應(yīng)于轉(zhuǎn)發(fā)給 Output 的一個 Batch。
每次新建一個 Batch闲擦,同時會建立一個 ackChan慢味,該 ackChan 會被 append 到 ackChanList 中场梆。
而 AckLoop 每次只監(jiān)聽處于 ackChanList 最頭部的 ackChan。
當(dāng) Batch 被 Output 調(diào)用 Ack 后纯路,AckLoop 會收到對應(yīng) ackChan 上的事件或油,并將其最終轉(zhuǎn)發(fā)給 Registrar。同時驰唬,ackChanList 將會 pop 頭部的 ackChan顶岸,繼續(xù)監(jiān)聽接下來的 Ack 事件。
總結(jié)
了解了 Filebeat 的實現(xiàn)原理叫编,我們才有會明白 Filebeat 配置中各個參數(shù)對程序的最終影響辖佣。同時,由于 FileBeat 是 At least once 的上報搓逾,但并不保證 Exactly once, 因此一條數(shù)據(jù)可能會被上報多次卷谈,所以接收端需要自行進(jìn)行去重過濾。