創業公司如何做數據分析(四)ELK日誌係統
作為係列文章的第四篇,本文將重點探討數據采集層中的ELK日誌係統。日誌,指的是後台服務中產生的log信息,通常會輸入到不同的文件中,比如Django服務下,一般會有nginx日誌和uWSGI日誌。這些日誌分散地存儲在不同的機器上,取決於服務的部署情況了。如果我們依次登錄每台機器去查閱日誌,顯然非常繁瑣,效率也很低,而且也沒法進行統計和檢索。因此,我們需要對日誌進行集中化管理,將所有機器上的日誌信息收集、匯總到一起。完整的日誌數據具有非常重要的作用:
-
通過檢索日誌信息,定位相應的bug,找出解決方案。
-
通過對日誌信息進行統計、分析,了解服務器的負荷和服務運行狀態,找出耗時請求進行優化等等。
-
如果是格式化的log,可以做進一步的數據分析,統計、聚合出有意義的信息,比如根據請求中的商品id,找出TOP10用戶感興趣商品。
ELK是一套開源的集中式日誌數據管理的解決方案,由Elasticsearch、Logstash和Kibana三個係統組成。最初我們建設ELK日誌係統的目的是做數據分析,記得第一個需求是期望利用nginx的日誌,從API請求的參數中挖掘出用戶的位置分布信息。後來該係統在追蹤惡意刷量、優化耗時服務等方麵都發揮了重要作用,而且隨著對Elasticsearch的認知加深,我們將其應用到了其他方麵的數據存儲和分析中。
ELK整體方案
ELK中的三個係統分別扮演不同的角色,組成了一個整體的解決方案。Logstash是一個ETL工具,負責從每台機器抓取日誌數據,對數據進行格式轉換和處理後,輸出到Elasticsearch中存儲。Elasticsearch是一個分布式搜索引擎和分析引擎,用於數據存儲,可提供實時的數據查詢。Kibana是一個數據可視化服務,根據用戶的操作從Elasticsearch中查詢數據,形成相應的分析結果,以圖表的形式展現給用戶。
ELK的安裝很簡單,可以按照“下載->修改配置文件->啟動”方法分別部署三個係統,也可以使用docker來快速部署。具體的安裝方法這裏不詳細介紹,我們來看一個常見的部署方案,如下圖所示,部署思路是:
- 第一,在每台生成日誌文件的機器上,部署Logstash,作為Shipper的角色,負責從日誌文件中提取數據,但是不做任何處理,直接將數據輸出到Redis隊列(list)中;
- 第二,需要一台機器部署Logstash,作為Indexer的角色,負責從Redis中取出數據,對數據進行格式化和相關處理後,輸出到Elasticsearch中存儲;
- 第三,部署Elasticsearch集群,當然取決於你的數據量了,數據量小的話可以使用單台服務,如果做集群的話,最好是有3個以上節點,同時還需要部署相關的監控插件;
- 第四,部署Kibana服務,提供Web服務。

在前期部署階段,主要工作是Logstash節點和Elasticsearch集群的部署,而在後期使用階段,主要工作就是Elasticsearch集群的監控和使用Kibana來檢索、分析日誌數據了,當然也可以直接編寫程序來消費Elasticsearch中的數據。
在上麵的部署方案中,我們將Logstash分為Shipper和Indexer兩種角色來完成不同的工作,中間通過Redis做數據管道,為什麼要這樣做?為什麼不是直接在每台機器上使用Logstash提取數據、處理、存入Elasticsearch?
首先,采用這樣的架構部署,有三點優勢:
- 第一,降低對日誌所在機器的影響,這些機器上一般都部署著反向代理或應用服務,本身負載就很重了,所以盡可能的在這些機器上少做事;
- 第二,如果有很多台機器需要做日誌收集,那麼讓每台機器都向Elasticsearch持續寫入數據,必然會對Elasticsearch造成壓力,因此需要對數據進行緩衝,同時,這樣的緩衝也可以一定程度的保護數據不丟失;
- 第三,將日誌數據的格式化與處理放到Indexer中統一做,可以在一處修改代碼、部署,避免需要到多台機器上去修改配置。
其次,我們需要做的是將數據放入一個消息隊列中進行緩衝,所以Redis隻是其中一個選擇,也可以是RabbitMQ、Kafka等等,在實際生產中,Redis與Kafka用的比較多。由於Redis集群一般都是通過key來做分片,無法對list類型做集群,在數據量大的時候必然不合適了,而Kafka天生就是分布式的消息隊列係統。
Logstash
在官方文檔中,Deploying and Scaling Logstash一文詳細介紹了各種Logstash的部署架構,下圖是與我們上述方案相吻合的架構。Logstash由input、filter和output三部分組成,input負責從數據源提取數據,filter負責解析、處理數據,output負責輸出數據,每部分都有提供豐富的插件。Logstash的設計思路也非常值得借鑒,以插件的形式來組織功能,通過配置文件來描述需要插件做什麼。我們以nginx日誌為例,來看看如何使用一些常用插件。

首先需要將nginx日誌格式規範化,便於做解析處理。在nginx.conf文件中設置:

2. nginx日誌–>>Logstash–>>消息隊列
這部分是Logstash Shipper的工作,涉及input和output兩種插件。input部分,由於需要提取的是日誌文件,一般使用file插件,該插件常用的幾個參數是:
- path,指定日誌文件路徑。
- type,指定一個名稱,設置type後,可以在後麵的filter和output中對不同的type做不同的處理,適用於需要消費多個日誌文件的場景。
- start_position,指定起始讀取位置,“beginning”表示從文件頭開始,“end”表示從文件尾開始(類似tail -f)。
- sincedb_path,與Logstash的一個坑有關。通常Logstash會記錄每個文件已經被讀取到的位置,保存在sincedb中,如果Logstash重啟,那麼對於同一個文件,會繼續從上次記錄的位置開始讀取。如果想重新從頭讀取文件,需要刪除sincedb文件,sincedb_path則是指定了該文件的路徑。為了方便,我們可以根據需要將其設置為“/dev/null”,即不保存位置信息。output部分,將數據輸出到消息隊列,以redis為例,需要指定redis server和list key名稱。另外,在測試階段,可以使用stdout來查看輸出信息。

這部分是Logstash Indexer的工作,涉及input、filter和output三種插件。在input部分,我們通過redis插件將數據從消息隊列中取出來。在output部分,我們通過elasticsearch插件將數據寫入Elasticsearch。

這裏,我們重點關注filter部分,下麵列舉幾個常用的插件,實際使用中根據自身需求從官方文檔中查找適合自己業務的插件並使用即可,當然也可以編寫自己的插件。
grok,是Logstash最重要的一個插件,用於將非結構化的文本數據轉化為結構化的數據。grok內部使用正則語法對文本數據進行匹配,為了降低使用複雜度,其提供了一組pattern,我們可以直接調用pattern而不需要自己寫正則表達式,參考源碼grok-patterns。
grok解析文本的語法格式是%{SYNTAX:SEMANTIC},SYNTAX是pattern名稱,SEMANTIC是需要生成的字段名稱,使用工具Grok Debugger可以對解析語法進行調試。例如,在下麵的配置中,我們先使用grok對輸入的原始nginx日誌信息(默認以message作為字段名)進行解析,並添加新的字段request_path_with_verb(該字段的值是verb和request_path的組合),然後對request_path字段做進一步解析。
kv,用於將某個字段的值進行分解,類似於編程語言中的字符串Split。在下麵的配置中,我們將request_args字段值按照“&”進行分解,分解後的字段名稱以“request_args_”作為前綴,並且丟棄重複的字段。
geoip,用於根據IP信息生成地理位置信息,默認使用自帶的一份GeoLiteCity database,也可以自己更換為最新的數據庫,但是需要數據格式需要遵循Maxmind的格式(參考GeoLite),似乎目前隻能支持legacy database,數據類型必須是.dat。下載GeoLiteCity.dat.gz後解壓, 並將文件路徑配置到source中即可。
translate,用於檢測某字段的值是否符合條件,如果符合條件則將其翻譯成新的值,寫入一個新的字段,匹配pattern可以通過YAML文件來配置。例如,在下麵的配置中,我們對request_api字段翻譯成更加易懂的文字描述。


Elasticsearch
Elasticsearch承載了數據存儲和查詢的功能,其基礎概念和使用方法可以參考另一篇博文Elasticsearch使用總結,這裏主要介紹些實際生產中的問題和方法:
關於集群配置,重點關注三個參數:
- 第一,discovery.zen.ping.unicast.hosts,Elasticsearch默認使用Zen Discovery來做節點發現機製,推薦使用unicast來做通信方式,在該配置項中列舉出Master節點。
- 第二,discovery.zen.minimum_master_nodes,該參數表示集群中可工作的具有Master節點資格的最小數量,默認值是1。為了提高集群的可用性,避免腦裂現象(所謂腦裂,就是同一個集群中的不同節點,對集群的狀態有不一致的理解。),官方推薦設置為(N/2)+1,其中N是具有Master資格的節點的數量。
- 第三,discovery.zen.ping_timeout,表示節點在發現過程中的等待時間,默認值是3秒,可以根據自身網絡環境進行調整,一定程度上提供可用性。
創業公司做數據分析(四)ELK日誌係統
關於集群節點,第一,節點類型包括:候選Master節點、數據節點和Client節點。通過設置兩個配置項node.master和node.data為true或false,來決定將一個節點分配為什麼類型的節點。第二,盡量將候選Master節點和Data節點分離開,通常Data節點負載較重,需要考慮單獨部署。
關於內存,Elasticsearch默認設置的內存是1GB,對於任何一個業務部署來說,這個都太小了。通過指定ES_HEAP_SIZE環境變量,可以修改其堆內存大小,服務進程在啟動時候會讀取這個變量,並相應的設置堆的大小。建議設置係統內存的一半給Elasticsearch,但是不要超過32GB。參考官方文檔。
關於硬盤空間,Elasticsearch默認將數據存儲在/var/lib/elasticsearch路徑下,隨著數據的增長,一定會出現硬盤空間不夠用的情形,此時就需要給機器掛載新的硬盤,並將Elasticsearch的路徑配置到新硬盤的路徑下。通過“path.data”配置項來進行設置,比如“path.data: /data1,/var/lib/elasticsearch,/data”。需要注意的是,同一分片下的數據隻能寫入到一個路徑下,因此還是需要合理的規劃和監控硬盤的使用。
關於Index的劃分和分片的個數,這個需要根據數據量來做權衡了,Index可以按時間劃分,比如每月一個或者每天一個,在Logstash輸出時進行配置,shard的數量也需要做好控製。
關於監控,筆者使用過head和marvel兩個監控插件,head免費,功能相對有限,marvel現在需要收費了。另外,不要在數據節點開啟監控插件。
Kibana
Kibana提供的是數據查詢和顯示的Web服務,有豐富的圖表樣板,能滿足大部分的數據可視化需求,這也是很多人選擇ELK的主要原因之一。UI的操作沒有什麼特別需要介紹的,經常使用就會熟練,這裏主要介紹經常遇到的三個問題。
1. 查詢語法
在Kibana的Discover頁麵中,可以輸入一個查詢條件來查詢所需的數據。查詢條件的寫法使用的是Elasticsearch的Query String語法,而不是Query DSL,參考官方文檔query-string-syntax,這裏列舉其中部分常用的:
單字段的全文檢索,比如搜索args字段中包含first的文檔,寫作 args:first;
單字段的精確檢索,比如搜索args字段值為first的文檔,寫作 args: “first”;
多個檢索條件的組合,使用 NOT, AND 和 OR 來組合,注意必須是大寫,比如 args:(“first” OR “second”) AND NOT agent: “third”;
字段是否存在,_exists_:agent表示要求agent字段存在,_missing_:agent表示要求agent字段不存在;
通配符:用 ? 表示單字母,* 表示任意個字母。
2. 錯誤“Discover: Request Timeout after 30000ms”
這個錯誤經常發生在要查詢的數據量比較大的情況下,此時Elasticsearch需要較長時間才能返回,導致Kibana發生Timeout報錯。解決這個問題的方法,就是在Kibana的配置文件中修改elasticsearch.requestTimeout一項的值,然後重啟Kibana服務即可,注意單位是ms。
3. 疑惑“字符串被分解了”
經常在QQ群裏看到一些人在問這樣一個問題:為什麼查詢結果的字段值是正確的,可是做圖表時卻發現字段值被分解了,不是想要的結果?如下圖所示的client_agent_info字段。
創業公司做數據分析(四)ELK日誌係統
得到這樣一個不正確結果的原因是使用了Analyzed字段來做圖表分析,默認情況下Elasticsearch會對字符串數據進行分析,建立倒排索引,所以如果對這麼一個字段進行terms聚合,必然會得到上麵所示的錯誤結果了。那麼應該怎麼做才對?默認情況下,Elasticsearch還會創建一個相對應的沒有被Analyzed的字段,即帶“.raw”後綴的字段,在這樣的字段上做聚合分析即可。
又會有很多人問這樣的問題:為什麼我的Elasticsearch沒有自動創建帶“.raw”後綴的字段?然而在Logstash中輸出數據時,設置index名稱前綴為“logstash-”就有了這個字段。這個問題的根源是Elasticsearch的dynamic template在搗鬼(可以查看博文Elasticsearch使用總結中的詳細介紹),dynamic temlate用於指導Elasticsearch如何為插入的數據自動建立Schema映射關係。
默認情況下,Logstash會在Elasticsearch中建立一個名為“logstash”的模板,所有前綴為“logstash-”的index都會參照這個模板來建立映射關係,在該模板中申明了要為每個字符串數據建立一個額外的帶“.raw”後綴的字段。可以向Elasticsearch來查詢你的模板,使用API:GET https://localhost:9200/_template。
以上便是對ELK日誌係統的總結介紹,還有一個重要的功能沒有提到,就是如何將日誌數據與自身產品業務的數據融合起來。
舉個例子,在nginx日誌中,通常會包含API請求訪問時攜帶的用戶Token信息,由於Token是有時效性的,我們需要及時將這些Token轉換成真實的用戶信息存儲下來。
這樣的需求通常有兩種實現方式,一種是自己寫一個Logstash filter,然後在Logstash處理數據時調用;另一種是將Logstash Indexer產生的數據再次輸出到消息隊列中,由我們自己的腳本程序從消息隊列中取出數據,做相應的業務處理後,輸出到Elasticsearch中。目前,團隊對ruby技術棧不是很熟悉,所以我們采用了第二種方案來實施。
當前,我們的數據增長相對緩慢,遇到的問題也有限,隨著數據量的增加,未來一定會遇到更多的挑戰,也可以進一步探索ELK。
最後更新:2017-04-14 12:00:24