一、背景
目前我們部門(mén)的日志查詢只能通過(guò)開(kāi)發(fā)人員登錄對(duì)應(yīng)的機(jī)器執(zhí)行日志分析趾徽,效率不夠高效续滋。對(duì)于現(xiàn)網(wǎng)問(wèn)題,由于運(yùn)維不理解后臺(tái)模塊的日志含義孵奶,因此只能由開(kāi)發(fā)去現(xiàn)網(wǎng)機(jī)器查詢?nèi)罩酒W茫粚?duì)于聯(lián)調(diào)環(huán)境,接入方調(diào)用接口出了問(wèn)題只能通過(guò)我們部門(mén)后臺(tái)開(kāi)發(fā)人員協(xié)助解決。這種強(qiáng)依賴指定開(kāi)發(fā)人員的情況朗恳,不利于問(wèn)題的快速解決湿颅。因此我們迫切的需要一個(gè)日志中心來(lái)處理和查詢所有的日志,并且由于需要在聯(lián)調(diào)時(shí)能夠讓接入方自己定位問(wèn)題粥诫,我們對(duì)實(shí)時(shí)性也有一定的要求油航。但是由于公司沒(méi)有提供這種系統(tǒng),所以我們決定自己做一個(gè)日志模塊來(lái)處理上述問(wèn)題怀浆。
這篇文章主要是寫(xiě)一下日志模塊客戶端的實(shí)現(xiàn)思路谊囚,對(duì)于日志存儲(chǔ)查詢服務(wù)由于不是我開(kāi)發(fā),所以只做簡(jiǎn)單介紹执赡。
二镰踏、在模塊內(nèi)部實(shí)現(xiàn)
最開(kāi)始的實(shí)現(xiàn)方式是修改業(yè)務(wù)邏輯代碼,在需要上報(bào)日志的地方增加日志上報(bào)邏輯沙合,但是由于php語(yǔ)言不支持裝飾器和注解這樣的語(yǔ)法奠伪,因此這樣的實(shí)現(xiàn)對(duì)于業(yè)務(wù)代碼的入侵度極高,同時(shí)需要大量修改業(yè)務(wù)代碼已有的處理流程首懈,也存在著很大的風(fēng)險(xiǎn)绊率。同時(shí)由于增加了日志上報(bào)邏輯,因此多了一次網(wǎng)絡(luò)調(diào)用猜拾,如果日志服務(wù)存在故障即舌,那么網(wǎng)絡(luò)調(diào)用超時(shí)會(huì)影響業(yè)務(wù)邏輯,這是很不合理的實(shí)現(xiàn)方案挎袜,因此我這樣寫(xiě)了兩天代碼就寫(xiě)不下去了。
三肥惭、優(yōu)化
后來(lái)看了下CI框架的文檔盯仪,發(fā)現(xiàn)CI也支持鉤子語(yǔ)法,因此可以通過(guò)設(shè)置一個(gè)全局的鉤子來(lái)實(shí)現(xiàn)對(duì)關(guān)鍵日志的監(jiān)控蜜葱,所謂鉤子全景,其實(shí)就是框架主流程埋幾個(gè)點(diǎn),這樣你就可以在框架執(zhí)行流程中增加自己的邏輯來(lái)影響框架執(zhí)行流程牵囤,我當(dāng)時(shí)掛鉤點(diǎn)是控制器初始化完成爸黄,但調(diào)用構(gòu)造器之前。這樣的實(shí)現(xiàn)看起來(lái)很簡(jiǎn)單揭鳞,也減少了代碼入侵炕贵,但是僅僅是減少代碼入侵而已,對(duì)于所有的流程還是會(huì)經(jīng)過(guò)日志判斷野崇,運(yùn)行流程上面還是和最開(kāi)始的實(shí)現(xiàn)方式一樣称开,是全局的。另外一點(diǎn)很不好的地方就是需要設(shè)置幾個(gè)全局變量來(lái)存儲(chǔ)一些鉤子獲取不到的數(shù)據(jù),如網(wǎng)絡(luò)調(diào)用鳖轰,這樣對(duì)于后來(lái)的維護(hù)者很難理解為什么這里會(huì)有一個(gè)全局變量賦值清酥,然后就吐槽一番這個(gè)代碼順便刪除,然后我們的日志監(jiān)控就呵呵了蕴侣。因此這樣的實(shí)現(xiàn)方案也是不太好的焰轻。所以我把代碼回滾,放棄了這種做法昆雀。辱志。
四、通過(guò)本地日志agent腳本實(shí)現(xiàn)
接下來(lái)就是本文的重點(diǎn)啦~
最后決定通過(guò)本地寫(xiě)個(gè)腳本來(lái)實(shí)時(shí)分析日志并且上傳到日志模塊來(lái)實(shí)現(xiàn)日志的監(jiān)控忆肾。這個(gè)方案是我認(rèn)為最合理的方案荸频,同時(shí)也是很多企業(yè)的做法。實(shí)現(xiàn)方案確定好了以后客冈,接下來(lái)的就是評(píng)估技術(shù)方案了旭从。
python腳本循環(huán)讀取日志存儲(chǔ)目錄實(shí)現(xiàn)監(jiān)控
由于之前有位大神給我講過(guò)他之前是怎么做一個(gè)日志監(jiān)控的,他當(dāng)時(shí)告訴我是本地寫(xiě)了一個(gè)腳本來(lái)實(shí)現(xiàn)场仲,所以我第一想法就是寫(xiě)個(gè)python腳本和悦。但是寫(xiě)了一段時(shí)間發(fā)現(xiàn)越寫(xiě)越?jīng)]譜,主要是因?yàn)楝F(xiàn)網(wǎng)的日志一天的量是以T為單位渠缕,而我的邏輯里面包含了很多的文件IO(因?yàn)橐獙?shí)時(shí)監(jiān)控鸽素,所以要一直讀取文件夾監(jiān)控內(nèi)部的變化)。所以這樣做的話亦鳞,很可能會(huì)出現(xiàn)分析跟不上產(chǎn)生的節(jié)奏馍忽,這樣實(shí)時(shí)性很差且會(huì)產(chǎn)生一定的系統(tǒng)負(fù)載,因此這種實(shí)現(xiàn)被放棄了燕差。
后來(lái)想了想遭笋,突然記起之前在學(xué)校做得一個(gè)項(xiàng)目,項(xiàng)目里面有個(gè)知識(shí)點(diǎn)是關(guān)于linux文件系統(tǒng)監(jiān)控的徒探,即linux文件系統(tǒng)的inotify機(jī)制瓦呼,關(guān)于該機(jī)制的介紹我就不做過(guò)多篇幅的描述了,參見(jiàn)這篇wikiinotify测暗。
所以我完全可以去網(wǎng)上下個(gè)python實(shí)現(xiàn)的文件系統(tǒng)notify庫(kù)央串,然后就可以很方便的監(jiān)控到文件系統(tǒng)的變化了。但是突然想起后面還有那么多的字符分析碗啄,好像用python的話性能不能滿足我們對(duì)實(shí)時(shí)性要求高的需求质和,因此我決定使用shell命令來(lái)做這個(gè)腳本。那些設(shè)計(jì)優(yōu)美且性能高效的文本分析命令完全可以很方便的實(shí)現(xiàn)我對(duì)于日志分析的要求挫掏。而且這樣我的全部工作就是組裝命令侦另,維護(hù)日志分析的主邏輯了。
shell通過(guò)notify_tools實(shí)現(xiàn)日志分析
shell實(shí)現(xiàn)有幾個(gè)技術(shù)問(wèn)題需要解決,第一是文本處理命令的選擇褒傅;第二是notify_tools是否真能滿足要求弃锐;第三則是性能測(cè)試了
文本命令的選擇
# 普通字符過(guò)濾性能比較
[root@mcs/data/log/trade.logical]# time grep ".*atom" log-2016-07-16.log>/dev/null
real 0m0.194s
user 0m0.188s
sys 0m0.004s
[root@mcs /data/log/trade.logical]# time sed "/.*atom/p" log-2016-07-16.log>/dev/null
real 0m0.201s
user 0m0.192s
sys 0m0.008s
[root@mcs/data/log/trade.logical]# time awk "/.*atom/" log-2016-07-16.log>/dev/null
real 0m1.502s
user 0m1.484s
sys 0m0.016s
可以看出來(lái)這三者的性能排序?yàn)間rep>sed>awk,awk基本不考慮使用了殿托,最然它給我們提供了可編程的空間霹菊,但是太慢了。至于最快的grep支竹,由于它在命令上支持不夠豐富旋廷,所以也不考慮使用。因此選取性能和功能相對(duì)而言優(yōu)于其他兩者的sed命令礼搁。
當(dāng)然饶碘,這只是一個(gè)簡(jiǎn)單的測(cè)試,由于我缺乏對(duì)這三個(gè)命令高級(jí)選項(xiàng)的認(rèn)識(shí)馒吴,因此這三個(gè)命令在加了高級(jí)選項(xiàng)以后的性能排序可能有所不同扎运。但是對(duì)目前的需求而言,執(zhí)行這樣的測(cè)試然后選擇sed是沒(méi)有問(wèn)題的饮戳。
notify_tools是否滿足要求
這個(gè)就簡(jiǎn)單了豪治,通過(guò)執(zhí)行man inotifywait看了下文檔,發(fā)現(xiàn)這個(gè)工具是滿足我們的要求的
性能測(cè)試
待補(bǔ)充....
shell腳本實(shí)現(xiàn)方案
有幾點(diǎn)需要首先明確扯罐,這個(gè)日志分析腳本由于是在本機(jī)運(yùn)行负拟,所以必然會(huì)部署多份,雖然我們部門(mén)只有幾臺(tái)服務(wù)器歹河,但也勉強(qiáng)算個(gè)分布式了==掩浙。所以腳本的運(yùn)行應(yīng)該足夠簡(jiǎn)單,所以決定通過(guò)配置文件來(lái)控制腳本的運(yùn)行秸歧。
另外一點(diǎn)就是如何保證日志文件都是被處理完了涣脚,不會(huì)出現(xiàn)漏處理或者處理速度跟不上的問(wèn)題。這個(gè)的實(shí)現(xiàn)我是通過(guò)維護(hù)一個(gè)處理偏移量的文件來(lái)記錄腳本處理的文件位置信息寥茫,方便腳本中斷后能夠從上次的處理位置繼續(xù)處理。
由于一次產(chǎn)生的日志量很大矾麻,所以不能夠一行一行的處理纱耻,這樣或許能夠跟上日志的產(chǎn)生速度,但是不太合理险耀,我采用的方案如下:
- 循環(huán)的執(zhí)行監(jiān)聽(tīng)命令弄喘,當(dāng)收到文件變化的通知后便立刻進(jìn)行處理
- 如果通知的變化文件和偏移量中記錄的文件一致,則計(jì)算出當(dāng)前文件總行數(shù)(主要是為了避免一直變化的行數(shù)造成處理混亂)甩牺,從上次記錄的偏移量處理到當(dāng)前文件的總行數(shù)
- 如果通知的變化文件和偏移量中記錄的文件不一致蘑志,這個(gè)時(shí)候說(shuō)明發(fā)生了新建日志文件的動(dòng)作,則一次性處理完偏移量文件中記錄的文件的剩下的所有內(nèi)容,并且開(kāi)始處理新創(chuàng)建的日志文件
- 更新偏移量和指向文件
這樣的實(shí)現(xiàn)有個(gè)特點(diǎn)就是急但,在日至量增加特別特別快的情況下(萬(wàn)行每秒)澎媒,處理腳本可能會(huì)出現(xiàn)延后,且日志增量如果不降下去波桩,處理會(huì)越來(lái)越延后戒努,但是當(dāng)新建文件時(shí),腳本會(huì)一次性將所有延后處理的日志一次性全部處理了镐躲,通過(guò)動(dòng)態(tài)獲取處理量來(lái)避免大量日志產(chǎn)生對(duì)于實(shí)時(shí)性的降低储玫。
當(dāng)然缺點(diǎn)也是很明顯的,如果日志量增加的速率一直增加萤皂,那么日志處理肯定是會(huì)有延后的撒穷,同時(shí)如果是通過(guò)調(diào)用接口上報(bào)日志的話,日志仍然會(huì)有幾秒甚至幾十秒左右的延后裆熙。但是對(duì)于我們的系統(tǒng)端礼,目前這樣實(shí)現(xiàn)是夠用了,業(yè)務(wù)量上去還可以通過(guò)優(yōu)化腳本和優(yōu)化日志服務(wù)的方式來(lái)提高日志的處理速度弛车。對(duì)于聯(lián)調(diào)環(huán)境的日志量齐媒,這樣的實(shí)現(xiàn)完全可以勝任。
接下來(lái)的事情就是讓時(shí)間去驗(yàn)證這樣實(shí)現(xiàn)的優(yōu)缺點(diǎn)纷跛。
后續(xù)改進(jìn)方案
- 目前這個(gè)腳本的耗時(shí)主要在日志的網(wǎng)絡(luò)傳輸上(即傳到日志中心的這個(gè)過(guò)程)喻括,采用的直接插入數(shù)據(jù)庫(kù)或者調(diào)用接口。
- 日志中心廢棄數(shù)據(jù)庫(kù)的存儲(chǔ)方式贫奠,采用更適合文本檢索的文件存儲(chǔ)方式來(lái)分析存儲(chǔ)日志唬血。
- 本地不再分析日志,只負(fù)責(zé)將日志提取出來(lái)發(fā)送給日至中心唤崭,分析由日至中心處理完成拷恨,但是降低了實(shí)時(shí)性。
- 傳輸協(xié)議上面可以用thrift協(xié)議谢肾,而不是現(xiàn)在的http協(xié)議或者直插數(shù)據(jù)庫(kù)腕侄。
- 規(guī)范化系統(tǒng)后臺(tái)日志格式,提高程序的整潔和日志信息的可讀性芦疏。
最后貼一段腳本的部分代碼來(lái)湊湊篇幅
#!/bin/sh
# 偏移量文件格式:偏移量 指向文件
configFile=$1
declare -a configArr
while IFS='' read -r line || [[ -n "$line" ]]; do
IFS='=' read -r key value <<< "$line"
configArr[$key]=$value
done < "$configFile"
echo -e "parse config file succ \n\nstart watch log file[log path:${configArr['logCategoryPath']}]\n"
while watchInfo=`inotifywait -q --format '%e %f' -e modify,create ${configArr['logCategoryPath']}`;do
watchInfo=($watchInfo)
lines=`wc -l ${watchInfo[1]}`
offsetInfo=`cat ${configArr['offsetFilePath']}`
# 如果偏移量文件不存在冕杠,則創(chuàng)建偏移量文件
if [ $? ]; then
# 如果偏移量記錄文件為空,則初始化偏移量酸茴,從第0行讀取變更的文件
if [ $offsetInfo -eq "" ]; then
process 1 ${lines} $watchInfo[1]
# 如果偏移量文件不為空分预,則取出偏移量和指向的文件
else
offsetInfo=($offsetInfo)
if [ ${offsetInfo[1]} -eq ${watchInfo[1]} ]; then
# 處理上一個(gè)日志文件
process ${offset} $ $offsetFile
# 處理從第0行開(kāi)始處理新創(chuàng)建的文件
process 1 ${lines} $watchInfo[1]
else
# 繼續(xù)處理偏移量文件中記錄的文件
process ${offset} ${lines} $watchInfo[1]
fi
fi
# 處理完成,更新偏移量和指向的文件
cat "$lines ${watchInfo[1]}" > ${configArr['offsetFilePath']}
else
touch ${configArr['offsetFilePath']}
fi
done