創(chuàng)業(yè)公司做數(shù)據(jù)分析(四)ELK日志系統(tǒng)
作為系列文章的第四篇,本文將重點(diǎn)探討數(shù)據(jù)采集層中的ELK日志系統(tǒng)。日志,指的是后臺服務(wù)中產(chǎn)生的log信息,通常會輸入到不同的文件中,比如Django服務(wù)下,一般會有nginx日志和uWSGI日志。這些日志分散地存儲在不同的機(jī)器上,取決于服務(wù)的部署情況了。如果我們依次登錄每臺機(jī)器去查閱日志,顯然非常繁瑣,效率也很低,而且也沒法進(jìn)行統(tǒng)計(jì)和檢索。因此,我們需要對日志進(jìn)行集中化管理,將所有機(jī)器上的日志信息收集、匯總到一起。完整的日志數(shù)據(jù)具有非常重要的作用:
- 信息查找。通過檢索日志信息,定位相應(yīng)的bug,找出解決方案。
- 服務(wù)診斷。通過對日志信息進(jìn)行統(tǒng)計(jì)、分析,了解服務(wù)器的負(fù)荷和服務(wù)運(yùn)行狀態(tài),找出耗時(shí)請求進(jìn)行優(yōu)化等等。
- 數(shù)據(jù)分析。如果是格式化的log,可以做進(jìn)一步的數(shù)據(jù)分析,統(tǒng)計(jì)、聚合出有意義的信息,比如根據(jù)請求中的商品id,找出***0用戶感興趣商品。
ELK是一套開源的集中式日志數(shù)據(jù)管理的解決方案,由Elasticsearch、Logstash和Kibana三個(gè)系統(tǒng)組成。最初我們建設(shè)ELK日志系統(tǒng)的目的是做數(shù)據(jù)分析,記得***個(gè)需求是期望利用nginx的日志,從API請求的參數(shù)中挖掘出用戶的位置分布信息。后來該系統(tǒng)在追蹤惡意刷量、優(yōu)化耗時(shí)服務(wù)等方面都發(fā)揮了重要作用,而且隨著對Elasticsearch的認(rèn)知加深,我們將其應(yīng)用到了其他方面的數(shù)據(jù)存儲和分析中。 本文的重點(diǎn)是結(jié)合自身實(shí)踐來介紹如何使用ELK系統(tǒng)、使用中的問題以及如何解決,文中涉及的ELK版本是:Elasticsearch 2.3、Logstash 2.3、Kibana 4。
ELK整體方案
ELK中的三個(gè)系統(tǒng)分別扮演不同的角色,組成了一個(gè)整體的解決方案。Logstash是一個(gè)ETL工具,負(fù)責(zé)從每臺機(jī)器抓取日志數(shù)據(jù),對數(shù)據(jù)進(jìn)行格式轉(zhuǎn)換和處理后,輸出到Elasticsearch中存儲。Elasticsearch是一個(gè)分布式搜索引擎和分析引擎,用于數(shù)據(jù)存儲,可提供實(shí)時(shí)的數(shù)據(jù)查詢。Kibana是一個(gè)數(shù)據(jù)可視化服務(wù),根據(jù)用戶的操作從Elasticsearch中查詢數(shù)據(jù),形成相應(yīng)的分析結(jié)果,以圖表的形式展現(xiàn)給用戶。
ELK的安裝很簡單,可以按照“下載->修改配置文件->啟動”方法分別部署三個(gè)系統(tǒng),也可以使用docker來快速部署。具體的安裝方法這里不詳細(xì)介紹,我們來看一個(gè)常見的部署方案,如下圖所示,部署思路是:
- ***,在每臺生成日志文件的機(jī)器上,部署Logstash,作為Shipper的角色,負(fù)責(zé)從日志文件中提取數(shù)據(jù),但是不做任何處理,直接將數(shù)據(jù)輸出到Redis隊(duì)列(list)中;
- 第二,需要一臺機(jī)器部署Logstash,作為Indexer的角色,負(fù)責(zé)從Redis中取出數(shù)據(jù),對數(shù)據(jù)進(jìn)行格式化和相關(guān)處理后,輸出到Elasticsearch中存儲;
- 第三,部署Elasticsearch集群,當(dāng)然取決于你的數(shù)據(jù)量了,數(shù)據(jù)量小的話可以使用單臺服務(wù),如果做集群的話,***是有3個(gè)以上節(jié)點(diǎn),同時(shí)還需要部署相關(guān)的監(jiān)控插件;
- 第四,部署Kibana服務(wù),提供Web服務(wù)。
在前期部署階段,主要工作是Logstash節(jié)點(diǎn)和Elasticsearch集群的部署,而在后期使用階段,主要工作就是Elasticsearch集群的監(jiān)控和使用Kibana來檢索、分析日志數(shù)據(jù)了,當(dāng)然也可以直接編寫程序來消費(fèi)Elasticsearch中的數(shù)據(jù)。
在上面的部署方案中,我們將Logstash分為Shipper和Indexer兩種角色來完成不同的工作,中間通過Redis做數(shù)據(jù)管道,為什么要這樣做?為什么不是直接在每臺機(jī)器上使用Logstash提取數(shù)據(jù)、處理、存入Elasticsearch?
首先,采用這樣的架構(gòu)部署,有三點(diǎn)優(yōu)勢:
- ***,降低對日志所在機(jī)器的影響,這些機(jī)器上一般都部署著反向代理或應(yīng)用服務(wù),本身負(fù)載就很重了,所以盡可能的在這些機(jī)器上少做事;
- 第二,如果有很多臺機(jī)器需要做日志收集,那么讓每臺機(jī)器都向Elasticsearch持續(xù)寫入數(shù)據(jù),必然會對Elasticsearch造成壓力,因此需要對數(shù)據(jù)進(jìn)行緩沖,同時(shí),這樣的緩沖也可以一定程度的保護(hù)數(shù)據(jù)不丟失;
- 第三,將日志數(shù)據(jù)的格式化與處理放到Indexer中統(tǒng)一做,可以在一處修改代碼、部署,避免需要到多臺機(jī)器上去修改配置。
其次,我們需要做的是將數(shù)據(jù)放入一個(gè)消息隊(duì)列中進(jìn)行緩沖,所以Redis只是其中一個(gè)選擇,也可以是RabbitMQ、Kafka等等,在實(shí)際生產(chǎn)中,Redis與Kafka用的比較多。由于Redis集群一般都是通過key來做分片,無法對list類型做集群,在數(shù)據(jù)量大的時(shí)候必然不合適了,而Kafka天生就是分布式的消息隊(duì)列系統(tǒng)。
Logstash
在官方文檔中,Deploying and Scaling Logstash一文詳細(xì)介紹了各種Logstash的部署架構(gòu),下圖是與我們上述方案相吻合的架構(gòu)。Logstash由input、filter和output三部分組成,input負(fù)責(zé)從數(shù)據(jù)源提取數(shù)據(jù),filter負(fù)責(zé)解析、處理數(shù)據(jù),output負(fù)責(zé)輸出數(shù)據(jù),每部分都有提供豐富的插件。Logstash的設(shè)計(jì)思路也非常值得借鑒,以插件的形式來組織功能,通過配置文件來描述需要插件做什么。我們以nginx日志為例,來看看如何使用一些常用插件。
1. 配置nginx日志格式
首先需要將nginx日志格式規(guī)范化,便于做解析處理。在nginx.conf文件中設(shè)置:
2. nginx日志–>>Logstash–>>消息隊(duì)列
這部分是Logstash Shipper的工作,涉及input和output兩種插件。input部分,由于需要提取的是日志文件,一般使用file插件,該插件常用的幾個(gè)參數(shù)是:
- path,指定日志文件路徑。
- type,指定一個(gè)名稱,設(shè)置type后,可以在后面的filter和output中對不同的type做不同的處理,適用于需要消費(fèi)多個(gè)日志文件的場景。
- start_position,指定起始讀取位置,“beginning”表示從文件頭開始,“end”表示從文件尾開始(類似tail -f)。
- sincedb_path,與Logstash的一個(gè)坑有關(guān)。通常Logstash會記錄每個(gè)文件已經(jīng)被讀取到的位置,保存在sincedb中,如果Logstash重啟,那么對于同一個(gè)文件,會繼續(xù)從上次記錄的位置開始讀取。如果想重新從頭讀取文件,需要?jiǎng)h除sincedb文件,sincedb_path則是指定了該文件的路徑。為了方便,我們可以根據(jù)需要將其設(shè)置為“/dev/null”,即不保存位置信息。
output部分,將數(shù)據(jù)輸出到消息隊(duì)列,以redis為例,需要指定redis server和list key名稱。另外,在測試階段,可以使用stdout來查看輸出信息。
3. 消息隊(duì)列–>>Logstash–>>Elasticsearch
這部分是Logstash Indexer的工作,涉及input、filter和output三種插件。在input部分,我們通過redis插件將數(shù)據(jù)從消息隊(duì)列中取出來。在output部分,我們通過elasticsearch插件將數(shù)據(jù)寫入Elasticsearch。
這里,我們重點(diǎn)關(guān)注filter部分,下面列舉幾個(gè)常用的插件,實(shí)際使用中根據(jù)自身需求從官方文檔中查找適合自己業(yè)務(wù)的插件并使用即可,當(dāng)然也可以編寫自己的插件。
grok,是Logstash最重要的一個(gè)插件,用于將非結(jié)構(gòu)化的文本數(shù)據(jù)轉(zhuǎn)化為結(jié)構(gòu)化的數(shù)據(jù)。grok內(nèi)部使用正則語法對文本數(shù)據(jù)進(jìn)行匹配,為了降低使用復(fù)雜度,其提供了一組pattern,我們可以直接調(diào)用pattern而不需要自己寫正則表達(dá)式,參考源碼grok-patterns。
grok解析文本的語法格式是%{SYNTAX:SEMANTIC},SYNTAX是pattern名稱,SEMANTIC是需要生成的字段名稱,使用工具Grok Debugger可以對解析語法進(jìn)行調(diào)試。例如,在下面的配置中,我們先使用grok對輸入的原始nginx日志信息(默認(rèn)以message作為字段名)進(jìn)行解析,并添加新的字段request_path_with_verb(該字段的值是verb和request_path的組合),然后對request_path字段做進(jìn)一步解析。
kv,用于將某個(gè)字段的值進(jìn)行分解,類似于編程語言中的字符串Split。在下面的配置中,我們將request_args字段值按照“&”進(jìn)行分解,分解后的字段名稱以“request_args_”作為前綴,并且丟棄重復(fù)的字段。
geoip,用于根據(jù)IP信息生成地理位置信息,默認(rèn)使用自帶的一份GeoLiteCity database,也可以自己更換為***的數(shù)據(jù)庫,但是需要數(shù)據(jù)格式需要遵循Maxmind的格式(參考GeoLite),似乎目前只能支持legacy database,數(shù)據(jù)類型必須是.dat。下載GeoLiteCity.dat.gz后解壓, 并將文件路徑配置到source中即可。
translate,用于檢測某字段的值是否符合條件,如果符合條件則將其翻譯成新的值,寫入一個(gè)新的字段,匹配pattern可以通過YAML文件來配置。例如,在下面的配置中,我們對request_api字段翻譯成更加易懂的文字描述。
Elasticsearch
Elasticsearch承載了數(shù)據(jù)存儲和查詢的功能,其基礎(chǔ)概念和使用方法可以參考另一篇博文Elasticsearch使用總結(jié),這里主要介紹些實(shí)際生產(chǎn)中的問題和方法:
關(guān)于集群配置,重點(diǎn)關(guān)注三個(gè)參數(shù):
- ***,discovery.zen.ping.unicast.hosts,Elasticsearch默認(rèn)使用Zen Discovery來做節(jié)點(diǎn)發(fā)現(xiàn)機(jī)制,推薦使用unicast來做通信方式,在該配置項(xiàng)中列舉出Master節(jié)點(diǎn)。
- 第二,discovery.zen.minimum_master_nodes,該參數(shù)表示集群中可工作的具有Master節(jié)點(diǎn)資格的最小數(shù)量,默認(rèn)值是1。為了提高集群的可用性,避免腦裂現(xiàn)象(所謂腦裂,就是同一個(gè)集群中的不同節(jié)點(diǎn),對集群的狀態(tài)有不一致的理解。),官方推薦設(shè)置為(N/2)+1,其中N是具有Master資格的節(jié)點(diǎn)的數(shù)量。
- 第三,discovery.zen.ping_timeout,表示節(jié)點(diǎn)在發(fā)現(xiàn)過程中的等待時(shí)間,默認(rèn)值是3秒,可以根據(jù)自身網(wǎng)絡(luò)環(huán)境進(jìn)行調(diào)整,一定程度上提供可用性。
關(guān)于集群節(jié)點(diǎn),***,節(jié)點(diǎn)類型包括:候選Master節(jié)點(diǎn)、數(shù)據(jù)節(jié)點(diǎn)和Client節(jié)點(diǎn)。通過設(shè)置兩個(gè)配置項(xiàng)node.master和node.data為true或false,來決定將一個(gè)節(jié)點(diǎn)分配為什么類型的節(jié)點(diǎn)。第二,盡量將候選Master節(jié)點(diǎn)和Data節(jié)點(diǎn)分離開,通常Data節(jié)點(diǎn)負(fù)載較重,需要考慮單獨(dú)部署。
關(guān)于內(nèi)存,Elasticsearch默認(rèn)設(shè)置的內(nèi)存是1GB,對于任何一個(gè)業(yè)務(wù)部署來說,這個(gè)都太小了。通過指定ES_HEAP_SIZE環(huán)境變量,可以修改其堆內(nèi)存大小,服務(wù)進(jìn)程在啟動時(shí)候會讀取這個(gè)變量,并相應(yīng)的設(shè)置堆的大小。建議設(shè)置系統(tǒng)內(nèi)存的一半給Elasticsearch,但是不要超過32GB。參考官方文檔。
關(guān)于硬盤空間,Elasticsearch默認(rèn)將數(shù)據(jù)存儲在/var/lib/elasticsearch路徑下,隨著數(shù)據(jù)的增長,一定會出現(xiàn)硬盤空間不夠用的情形,此時(shí)就需要給機(jī)器掛載新的硬盤,并將Elasticsearch的路徑配置到新硬盤的路徑下。通過“path.data”配置項(xiàng)來進(jìn)行設(shè)置,比如“path.data: /data1,/var/lib/elasticsearch,/data”。需要注意的是,同一分片下的數(shù)據(jù)只能寫入到一個(gè)路徑下,因此還是需要合理的規(guī)劃和監(jiān)控硬盤的使用。
關(guān)于Index的劃分和分片的個(gè)數(shù),這個(gè)需要根據(jù)數(shù)據(jù)量來做權(quán)衡了,Index可以按時(shí)間劃分,比如每月一個(gè)或者每天一個(gè),在Logstash輸出時(shí)進(jìn)行配置,shard的數(shù)量也需要做好控制。
關(guān)于監(jiān)控,筆者使用過head和marvel兩個(gè)監(jiān)控插件,head免費(fèi),功能相對有限,marvel現(xiàn)在需要收費(fèi)了。另外,不要在數(shù)據(jù)節(jié)點(diǎn)開啟監(jiān)控插件。
Kibana
Kibana提供的是數(shù)據(jù)查詢和顯示的Web服務(wù),有豐富的圖表樣板,能滿足大部分的數(shù)據(jù)可視化需求,這也是很多人選擇ELK的主要原因之一。UI的操作沒有什么特別需要介紹的,經(jīng)常使用就會熟練,這里主要介紹經(jīng)常遇到的三個(gè)問題。
1. 查詢語法
在Kibana的Discover頁面中,可以輸入一個(gè)查詢條件來查詢所需的數(shù)據(jù)。查詢條件的寫法使用的是Elasticsearch的Query String語法,而不是Query DSL,參考官方文檔query-string-syntax,這里列舉其中部分常用的:
- 單字段的全文檢索,比如搜索args字段中包含first的文檔,寫作 args:first;
- 單字段的精確檢索,比如搜索args字段值為first的文檔,寫作 args: “first”;
- 多個(gè)檢索條件的組合,使用 NOT, AND 和 OR 來組合,注意必須是大寫,比如 args:(“first” OR “second”) AND NOT agent: “third”;
- 字段是否存在,_exists_:agent表示要求agent字段存在,_missing_:agent表示要求agent字段不存在;
- 通配符:用 ? 表示單字母,* 表示任意個(gè)字母。
2. 錯(cuò)誤“Discover: Request Timeout after 30000ms”
這個(gè)錯(cuò)誤經(jīng)常發(fā)生在要查詢的數(shù)據(jù)量比較大的情況下,此時(shí)Elasticsearch需要較長時(shí)間才能返回,導(dǎo)致Kibana發(fā)生Timeout報(bào)錯(cuò)。解決這個(gè)問題的方法,就是在Kibana的配置文件中修改elasticsearch.requestTimeout一項(xiàng)的值,然后重啟Kibana服務(wù)即可,注意單位是ms。
3. 疑惑“字符串被分解了”
經(jīng)常在QQ群里看到一些人在問這樣一個(gè)問題:為什么查詢結(jié)果的字段值是正確的,可是做圖表時(shí)卻發(fā)現(xiàn)字段值被分解了,不是想要的結(jié)果?如下圖所示的client_agent_info字段。
得到這樣一個(gè)不正確結(jié)果的原因是使用了Analyzed字段來做圖表分析,默認(rèn)情況下Elasticsearch會對字符串?dāng)?shù)據(jù)進(jìn)行分析,建立倒排索引,所以如果對這么一個(gè)字段進(jìn)行terms聚合,必然會得到上面所示的錯(cuò)誤結(jié)果了。那么應(yīng)該怎么做才對?默認(rèn)情況下,Elasticsearch還會創(chuàng)建一個(gè)相對應(yīng)的沒有被Analyzed的字段,即帶“.raw”后綴的字段,在這樣的字段上做聚合分析即可。
又會有很多人問這樣的問題:為什么我的Elasticsearch沒有自動創(chuàng)建帶“.raw”后綴的字段?然而在Logstash中輸出數(shù)據(jù)時(shí),設(shè)置index名稱前綴為“logstash-”就有了這個(gè)字段。這個(gè)問題的根源是Elasticsearch的dynamic template在搗鬼(可以查看博文Elasticsearch使用總結(jié)中的詳細(xì)介紹),dynamic temlate用于指導(dǎo)Elasticsearch如何為插入的數(shù)據(jù)自動建立Schema映射關(guān)系。
默認(rèn)情況下,Logstash會在Elasticsearch中建立一個(gè)名為“logstash”的模板,所有前綴為“logstash-”的index都會參照這個(gè)模板來建立映射關(guān)系,在該模板中申明了要為每個(gè)字符串?dāng)?shù)據(jù)建立一個(gè)額外的帶“.raw”后綴的字段。可以向Elasticsearch來查詢你的模板,使用API:GET http://localhost:9200/_template。
以上便是對ELK日志系統(tǒng)的總結(jié)介紹,還有一個(gè)重要的功能沒有提到,就是如何將日志數(shù)據(jù)與自身產(chǎn)品業(yè)務(wù)的數(shù)據(jù)融合起來。
舉個(gè)例子,在nginx日志中,通常會包含API請求訪問時(shí)攜帶的用戶Token信息,由于Token是有時(shí)效性的,我們需要及時(shí)將這些Token轉(zhuǎn)換成真實(shí)的用戶信息存儲下來。
這樣的需求通常有兩種實(shí)現(xiàn)方式,一種是自己寫一個(gè)Logstash filter,然后在Logstash處理數(shù)據(jù)時(shí)調(diào)用;另一種是將Logstash Indexer產(chǎn)生的數(shù)據(jù)再次輸出到消息隊(duì)列中,由我們自己的腳本程序從消息隊(duì)列中取出數(shù)據(jù),做相應(yīng)的業(yè)務(wù)處理后,輸出到Elasticsearch中。目前,團(tuán)隊(duì)對ruby技術(shù)棧不是很熟悉,所以我們采用了第二種方案來實(shí)施。
當(dāng)前,我們的數(shù)據(jù)增長相對緩慢,遇到的問題也有限,隨著數(shù)據(jù)量的增加,未來一定會遇到更多的挑戰(zhàn),也可以進(jìn)一步探索ELK。
點(diǎn)擊查看:
創(chuàng)業(yè)公司做數(shù)據(jù)分析(一)開篇
創(chuàng)業(yè)公司做數(shù)據(jù)分析(二)運(yùn)營數(shù)據(jù)系統(tǒng)
創(chuàng)業(yè)公司做數(shù)據(jù)分析(三)用戶行為數(shù)據(jù)采集系統(tǒng)
創(chuàng)業(yè)公司做數(shù)據(jù)分析(五)微信分享追蹤系統(tǒng)
創(chuàng)業(yè)公司做數(shù)據(jù)分析(六)數(shù)據(jù)倉庫的建設(shè)