最近工作中正在用ELK搭建一套數(shù)據(jù)平臺(tái),通過Logstash(以下簡(jiǎn)稱LS)讀取文件的內(nèi)容,然后同步給ES。文件的內(nèi)容是用Python腳本生成的,文件名一直不變,即每次Python腳本是往同一個(gè)文件里覆蓋的去寫,demo如下:
python demo t.py
import json
lst = list()
for i in range(3):
lst.append(dict(a=i+1, b=4.3, c='world', date=1554972722911))
fi = '/Applications/logstash-5.6.3/test1.json'
with open(fi, 'w') as f:
f.write(json.dumps(lst))
Logstash demo t.conf
input {
file {
path => "/Applications/logstash-5.6.3/test1.json"
start_position => "beginning"
codec => "json"
}
}
filter {
date {
match => [ "date", "MMM dd yyyy HH:mm:ss", "UNIX_MS" ]
timezone => "Asia/Shanghai"
target => "date"
}
}
output {
elasticsearch {
hosts => ["xx.xx.xx.xx:8080"]
index => "t_l-%{+YYYY.MM.dd}"
document_type => "tt"
manage_template => false
template_name => "t_temp"
}
}
結(jié)果超級(jí)詭異的現(xiàn)象出現(xiàn)了!!LS并不讀取文件的內(nèi)容,只有手動(dòng)vim去修改一下文件,LS才會(huì)讀數(shù)據(jù)。
最開始以為是file input插件的參數(shù)那里設(shè)置的不對(duì),然后在官方的社區(qū)里搜到了這個(gè)問題https://discuss.elastic.co/t/file-plugin-doesnt-read-file/49052/3,提問者的問題跟我一模一樣,下面有人回答是ignore_older參數(shù)搗的鬼,然后提問那哥們把ignore_older參數(shù)值改了后,問題解決了。我以為這就找到答案了,就去看ignore_older參數(shù)的定義,摘抄如下:

ignore_older的作用就是設(shè)置一個(gè)時(shí)長(zhǎng)來讓LS忽略文件。比如這個(gè)參數(shù)設(shè)置為1小時(shí),那Logstash就會(huì)忽略到當(dāng)前為止已經(jīng)超過1小時(shí)沒有任何修改的文件,即只會(huì)讀取1小時(shí)內(nèi)有修改的文件。這個(gè)參數(shù)主要是用來屏蔽時(shí)間久遠(yuǎn)的log文件。
當(dāng)前官方最新插件說這個(gè)參數(shù)默認(rèn)是關(guān)閉的,即不起作用,上面的答案說這個(gè)參數(shù)默認(rèn)是1天(插件版本不一致)。
然后我發(fā)現(xiàn)這個(gè)參數(shù)跟我的問題沒啥關(guān)系,因?yàn)槲椰F(xiàn)在的demo是立馬更新文件,即時(shí)間絕對(duì)在1天內(nèi),但LS還是忽略這個(gè)文件了。
無奈從頭開始詳讀input file插件文檔,在Tracking_of_current_position_in_watched_files中了解到,file插件是通過一個(gè)叫sincedb的文件來保存Logstash已經(jīng)讀取的文件,以及上一次讀取文件時(shí)的最后一個(gè)位置(方便下一次接著該位置繼續(xù)往下讀)。所以我該從sincedb入手查問題,在data目錄下找到sincedb文件:
? file pwd
/Applications/logstash-5.6.3/data/plugins/inputs/file
? file ll
total 8
drwxr-xr-x 3 ivanli admin 102 4 19 16:39 ./
drwxr-xr-x 3 ivanli admin 102 4 18 15:12 ../
-rw-r--r-- 1 ivanli admin 17 4 19 16:39 .sincedb_34488dda9a79102a6b4436bfb0f592d2
在https://discuss.elastic.co/t/logstash-sincedb-files/95297/2中詳細(xì)說明了.conf文件中的path參數(shù)中的每一個(gè)路徑對(duì)應(yīng)1個(gè)sincedb文件,如果文件路徑有通配符,如“/Applications/logstash-5.6.3/test.json”,則Logstash會(huì)把讀取test.json文件的相關(guān)信息統(tǒng)一放在一個(gè)sincedb文件中,而不是每一個(gè)文件一個(gè)sincedb文件。
另外這個(gè)鏈接里也說了LS讀取文件的規(guī)則,這個(gè)稍后再說。先看下sincedb文件的內(nèi)容:
? file cat .sincedb_34488dda9a79102a6b4436bfb0f592d2
13326659 1 2 172
文件里一行數(shù)據(jù)包含4個(gè)數(shù),分別代表inode number、major device number、minor device number和current byte offset。
其中inode,major device,minor device是操作系統(tǒng)的文件系統(tǒng)的概念。current byte offset表示上一次文件讀取的位置。
Logstash不讀取文件的問題就出在inode上。簡(jiǎn)單理解,inode記錄一個(gè)文件的屬性,如文件的字節(jié)數(shù)、userId、groupId,讀寫執(zhí)行權(quán)限等。一個(gè)文件對(duì)應(yīng)一個(gè)inode。
上面的鏈接中說了LS讀取文件和inode的關(guān)系,引用如下:
Be aware of the INODE reuse problem. Background: the sincedb tracks read content position by INODE, because during file rotation (different techniques have different effects), the name of the log file may change but its INODE does not. Eventually as files are created and deleted inevitably an INODE will be reused and if by chance that INODE has been seen before via a log file name that once satisfied the glob pattern, LS will think it has read the file before. Two things can happen here 1) the new file is smaller than the last-read point then LS will detect this and start from the beginning (assumes rotation) and 2) the new file is bigger than the last-read point then LS will read from the last-read point on. This random unfortunate situation is difficult to foresee and to code for and leads to very confused OPS people.
即當(dāng)文件滾動(dòng)(rotation,參考:logrotate機(jī)制和原理)的時(shí)候,文件的inode是不會(huì)變化的,這時(shí):
- 文件的內(nèi)容小于上一次文件讀取的位置時(shí),LS會(huì)從頭(beginning)開始讀文件內(nèi)容;
- 文件的內(nèi)容大于上一次文件讀取的位置時(shí),LS會(huì)從上一次文件讀取的位置開始讀文件內(nèi)容。
所以,之前我本地測(cè)試Py demo的時(shí)候,每次往同一個(gè)文件里覆蓋寫的內(nèi)容是一模一樣的,文件大小沒有變化,所以LS不會(huì)去讀文件的內(nèi)容。只要每次文件更新的內(nèi)容造成文件大小有變化,LS就可以讀數(shù)據(jù)了,但這也不符合我的需求。畢竟每次我寫的都是全新的內(nèi)容,而且新寫的內(nèi)容可能和上一次的內(nèi)容是一模一樣的(業(yè)務(wù)上的要求),這樣LS就不讀數(shù)據(jù)了。另外,即使內(nèi)容不完全一樣,若新的內(nèi)容大于offset的話,LS讀取出來的內(nèi)容是不全的(因?yàn)閺纳弦淮蔚膐ffset開始讀,前面的內(nèi)容忽略了)。
又在這個(gè)鏈接中https://discuss.elastic.co/t/logstash-is-not-reading-the-log-file-thats-being-updated-automatically-continuously/92686/7看到文件重讀的機(jī)制:
The file input rereads a file if
its inode number changes, or
if the file shrinks (indicating that it was rotated via copy/truncate).
The file input does not support other means of detecting changes in a file.
一是文件的inode變化了,即新生成一個(gè)文件;二是如之前所說的文件rotate。
所以可以通過每次都往一個(gè)新文件里寫內(nèi)容(新的inode),在.conf中使用通配符:
input {
file {
path => "/Applications/logstash-5.6.3/test*.json"
start_position => "beginning"
codec => "json"
}
}
這樣,sincedb會(huì)新增一行記錄新inode的內(nèi)容,新文件的內(nèi)容也可以全部被Logstash讀取了。
這也解釋了為什么之前需要我手動(dòng)去編輯一下文件,就可以被Logstash讀取了,因?yàn)槊看尉庉嫼螅募膇node就已經(jīng)變化了。
可以試一下:
? logstash-5.6.3 stat test1.json
16777218 13326659 -rw-r--r-- 1 ivanli admin 0 572 "Apr 19 17:48:39 2019" "Apr 19 17:48:34 2019" "Apr 19 17:48:34 2019" "Apr 19 16:39:35 2019" 4096 8 0 test1.json
在mac上,源文件的inode號(hào)是13326659,然后vim編輯下文件(隨便改1個(gè)數(shù)),再保存:
? logstash-5.6.3 vim test1.json
? logstash-5.6.3 stat test1.json
16777218 13334123 -rw-r--r-- 1 ivanli admin 0 572 "Apr 19 18:16:47 2019" "Apr 19 18:16:46 2019" "Apr 19 18:16:46 2019" "Apr 19 18:16:46 2019" 4096 8 0 test1.json
發(fā)現(xiàn)文件的inode號(hào)變成13334123了,對(duì)于LS來說是一個(gè)新文件,內(nèi)容全部被讀。 至于vim為啥會(huì)改變文件的inode號(hào):
So the inode is unchanged. In Vim, as cjm has already stated, the choice is controlled by the backup , backupcopy and writebackup options. By default, Vim renames the old file, then writes a new file with the original name, if it thinks it can re-create the original file's attributes.
即vim修改文件的背后機(jī)制是重命名該文件,然后新建一個(gè)文件和源文件名一樣,再把原文件的內(nèi)容寫入新建文件里。
雖然因?yàn)楸镜販y(cè)試每次寫的都是同樣的內(nèi)容導(dǎo)致在解決問題的道路上繞了一大圈,但也通過這一大圈了解了LS讀取文件的機(jī)制,總比稀里糊涂的用要好很多吧,而且在這個(gè)過程中了解了inode,還有rotate,收獲也不小~