很多人剛剛接觸ELK都不知道如何使用它們來做分析,經常會碰到下面的問題:
- 安裝完ELK不知從哪下手
- 拿到數據樣本不知道怎么分解數據
- 導入到elasticsearch中奇怪為什么搜不出來
- 搜到結果后,不知道它還能干什么
本篇就以一個完整的流程介紹下,數據從 讀取-->分析-->檢索-->應用 的全流程處理。在閱讀本篇之前,需要先安裝ELK,可以參考之前整理安裝文檔:ELK5.0部署教程
在利用ELK做數據分析時,大致為下面的流程:
- 1 基於logstash分解字段
- 2 基於字段創建Mapping
- 3 查看分詞結果
- 4 檢索
- 5 聚合
- 6 高亮
可能會根據第4步重復第2步的工作,調整分詞等規則。
為了便於理解,先說一下本文的業務背景:
我需要統計一個url對應的pv和uv,這個url需要支持全文檢索。每天同一個url都會產生一條數據。最后會按照特定的日期范圍對數據進行聚合。
下面就開始數據分析之路吧~
基於logstash分解字段
在使用logstash前,需要對它有一定的了解。logstash的組件其實很簡單,主要包括input、filter、output、codec四個部分。
- input 用於讀取內容,常用的有stdin(直接從控制台輸入)、file(讀取文件)等,另外還提供了對接redis、kafka等的插件
- filter 用於對輸入的文本進行處理,常用的有grok(基於正則表達式提取字段)、kv(解析鍵值對形式的數據)、csv、xml等,另外還提供了了一個ruby插件,這個插件如果會用的話,幾乎是萬能的。
- output 用於把fitler得到的內容輸出到指定的接收端,常用的自然是elasticsearch(對接ES)、file(輸出到文件)、stdout(直接輸出到控制台)
- codec 它用於格式化對應的內容,可以再Input和output插件中使用,比如在output的stdout中使用rubydebug以json的形式輸出到控制台
理解上面的內容后,再看看logstash的使用方法。
首先需要定義一個配置文件,配置文件中配置了對應的input,filter,output等,至少是一個input,output。
如我的配置文件:
input {
file {
path => "C:\Users\Documents\workspace\elk\page.csv"
start_position => "beginning"
}
}
filter {
grok {
match => {
"message" => "%{NOTSPACE:url}\s*%{NOTSPACE:date}\s*%{NOTSPACE:pvs}\s*%{NOTSPACE:uvs}\s*%{NOTSPACE:ips}\s*%{NOTSPACE:mems}\s*%{NOTSPACE:new_guests}\s*%{NOTSPACE:quits}\s*%{NOTSPACE:outs}\s*%{NOTSPACE:stay_time}"
}
}
}
output {
stdout{codec => dots}
elasticsearch {
document_type => "test"
index => "page"
hosts => ["1.1.1.1:9200"]
}
}
上面的配置最不容易理解的就是Grok,其實它就是個正則表達式而已,你可以把它理解成是一段正則表達式的占位。至於grok都有哪些關鍵字,這些關鍵字對應的正則都是什么,可以直接參考logstash的源碼,目錄的位置為:
logstash-5.2.2\vendor\bundle\jruby\1.9\gems\logstash-patterns-core-4.0.2\patterns
如果提供的話,可以直接在grokdebug上面進行測試:

另外一個技巧就是,如果開啟stdout並且codec為rubydebug,會把數據輸出到控制台,因此使用.代替,即可省略輸出,又能檢測到現在是否有數據正在處理。而且每個.是一個字符,如果把它輸出到文件,也可以直接通過文件的大小,判斷處理了多少條。
這樣,數據的預處理做完了.....
基於字段創建Mapping
雖然說Es是一個文檔數據庫,但是它也是有模式的概念的。文檔中的每個字段仍然需要定義字段的類型,使用者經常會遇到明明是數字,在kibana卻做不了加法;或者明明是IP,kibana里面卻不認識。這都是因為Mapping有問題導致的。
在Elasticsearch中其實是有動態映射這個概念的,在字段第一次出現時,ES會自動檢測你的字段是否屬於數字或者日期或者IP,如果滿足它預定義的格式,就按照特殊格式存儲。一旦格式設置過了,之后的數據都會按照這種格式存儲。舉個例子,第一條數據進入ES時,字段檢測為數值型;第二條進來的時候,卻是一個字符串,結果可能插不進去,也可能插進去讀不出來(不同版本處理的方式不同)。
因此,我們需要事先就設定一下字段的Mapping,這樣之后使用的時候才不會困惑。
另外,Mapping里面不僅僅有字段的類型,還有這個字段的分詞方式,比如使用標准standard分詞器,還是中文分詞器,或者是自定義的分詞器,這個也是很關鍵的一個概念,稍后再講。
創建Mapping有兩種方式:
第一種,直接創建索引並創建映射
創建索引時,可以直接指定它的配置和Mapping:
PUT index_name
{
"settings" : {
"number_of_shards" : 1
},
"mappings" : {
"type_name" : {
"properties" : {
"field_name" : { "type" : "text" }
}
}
}
}
第二種,先創建索引,再創建映射
# 先創建索引
PUT index_name
{}
# 然后創建Mapping
PUT /index_name/_mapping/type_name
{
"properties": {
"ip": {
"type": "ip"
}
}
}
# 最后查詢創建的Mapping
GET /index_name/_mapping/type_name
比如我們上面的URL場景,可以這么建立索引:
PUT url/_mapping/test
{
"properties": {
"url": {
"type": "string",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"date": {
"type": "date"
},
"pvs": {
"type": "integer"
},
"uvs": {
"type": "integer"
}
}
}
PS,在上面的例子中,url需要有兩個用途,一個是作為聚合的字段;另一個是需要做全文檢索。在ES中全文檢索的字段是不能用來做聚合的,因此使用嵌套字段的方式,新增一個url.keyword字段,這個字段設置成keyword類型,不采用任何分詞(這是5.0的新特性,如果使用以前版本,可以直接設置string對應的index屬性即可);然后本身的url字段則采用默認的標准分詞器進行分詞。
這樣,以后在搜索的時候可以直接以query string的方式檢索url,聚合的時候則可以直接使用url.keyword
查看分詞結果
如果字段為https://www.elastic.co/guide/en/elasticsearch/reference/5.2,使用standard標准分詞器,輸入elastic卻收不到任何結果,是不是有點懷疑人生。
我們做個小例子,首先創建一個空的索引:
PUT test1/test1/1
{
"text":"https://www.elastic.co/guide/en/elasticsearch/reference/5.2"
}
然后查詢這個字段被分解成了什么鬼?
GET /test1/test1/1/_termvectors?fields=text
得到的內容如下:
{
"_index": "test1",
"_type": "test1",
"_id": "1",
"_version": 1,
"found": true,
"took": 1,
"term_vectors": {
"text": {
"field_statistics": {
"sum_doc_freq": 7,
"doc_count": 1,
"sum_ttf": 7
},
"terms": {
"5.2": {
"term_freq": 1,
"tokens": [
{
"position": 6,
"start_offset": 56,
"end_offset": 59
}
]
},
"elasticsearch": {
"term_freq": 1,
"tokens": [
{
"position": 4,
"start_offset": 32,
"end_offset": 45
}
]
},
"en": {
"term_freq": 1,
"tokens": [
{
"position": 3,
"start_offset": 29,
"end_offset": 31
}
]
},
"guide": {
"term_freq": 1,
"tokens": [
{
"position": 2,
"start_offset": 23,
"end_offset": 28
}
]
},
"https": {
"term_freq": 1,
"tokens": [
{
"position": 0,
"start_offset": 0,
"end_offset": 5
}
]
},
"reference": {
"term_freq": 1,
"tokens": [
{
"position": 5,
"start_offset": 46,
"end_offset": 55
}
]
},
"www.elastic.co": {
"term_freq": 1,
"tokens": [
{
"position": 1,
"start_offset": 8,
"end_offset": 22
}
]
}
}
}
}
}
看到了吧,沒有elastic這個詞,自然是搜不出來的。如果你不理解這句話,回頭看看倒排索引的原理吧!或者看看我的這篇文章:分詞器的作用
那么你可能很郁悶,我就是要搜elastic怎么辦!沒關系,換個分詞器就行了~比如elasticsearch為我們提供的simple分詞器,就可以簡單的按照符號進行切分:
POST _analyze
{
"analyzer": "simple",
"text": "https://www.elastic.co/guide/en/elasticsearch/reference/5.2"
}
得到的結果為:
{
"tokens": [
{
"token": "https",
"start_offset": 0,
"end_offset": 5,
"type": "word",
"position": 0
},
{
"token": "www",
"start_offset": 8,
"end_offset": 11,
"type": "word",
"position": 1
},
{
"token": "elastic",
"start_offset": 12,
"end_offset": 19,
"type": "word",
"position": 2
},
{
"token": "co",
"start_offset": 20,
"end_offset": 22,
"type": "word",
"position": 3
},
{
"token": "guide",
"start_offset": 23,
"end_offset": 28,
"type": "word",
"position": 4
},
{
"token": "en",
"start_offset": 29,
"end_offset": 31,
"type": "word",
"position": 5
},
{
"token": "elasticsearch",
"start_offset": 32,
"end_offset": 45,
"type": "word",
"position": 6
},
{
"token": "reference",
"start_offset": 46,
"end_offset": 55,
"type": "word",
"position": 7
}
]
}
這樣你就可以搜索elastic了,但是前提是需要在Mapping里面為該字段指定使用simple分詞器,方法為:
PUT url/_mapping/test
{
"properties": {
"url": {
"type": "string",
"analyzer": "simple",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"date": {
"type": "date"
},
"pvs": {
"type": "integer"
},
"uvs": {
"type": "integer"
}
}
修改Mapping前,需要先刪除索引,然后重建索引。刪除索引的命令為:
DELETE url
不想刪除索引,只想改變Mapping?想得美....你當ES是孫悟空會72變?不過,你可以創建一個新的索引,然后把舊索引的數據導入到新索引就行了,這也不失為一種辦法。如果想這么搞,可以參考reindex api,如果版本是5.0之前,那么你倒霉了!自己搞定吧!
檢索
ES里面檢索是一個最基礎的功能了,很多人其實這個都是一知半解。由於內容太多,我就結合Kibana講講其中的一小部分吧。
很多人安裝完kibana之后,登陸后不知道該干啥。如果你的elasticsearch里面已經有數據了,那么此時你需要在Kiban新建對應的索引。

如果你的es的索引是name-2017-03-19,name-2017-03-20這種名字+時間后綴的,那么可以勾選1位置的選項,它會自動聚合這些索引。這樣在這一個索引中就可以查詢多個索引的數據了,其實他是利用了索引的模式匹配的特性。如果你的索引僅僅是一個簡單的名字,那么可以不勾選1位置的選項,直接輸入名字,即可。

然后進入Kibana的首頁,在輸入框里面就可以任意輸入關鍵字進行查詢了。
查詢的詞,需要是上面_termvectors分析出來的詞,差一個字母都不行!!!!!
這個搜索框其實就是elasticsearch中的query string,因此所有的lucene查詢語法都是支持的!
如果想要了解更多的查詢語法,也可以參考我之前整理的文章,Lucene查詢語法
另外,這個輸入框,其實也可以輸入ES的DSL查詢語法,只不過寫法過於蛋疼,就不推薦了。
自定義查詢語法
如果不使用kibana,想在自己的程序里面訪問es操作,也可以直接以rest api的方式查詢。
比如查詢某個索引的全部內容,默認返回10個:
GET /page/test/_search?pretty
再比如,增加一個特殊點的查詢:
GET /page/test/_search?pretty
{
"query": {
"query_string" : {
"default_field" : "url",
"query" : "顏色"
}
},
"size": 10,
}
聚合
在es中一個很重要的亮點,就是支持很多的聚合語法,如果沒有它,我想很多人會直接使用lucene吧。在ES中的聚合,大體上可以為兩類聚合方法,metric和bucket。metic可以理解成avg、sum、count、max、min,bucket可以理解為group by 。有了這兩種聚合方法,就可以對ES中的數據做很多處理了。
比如在kibana中,做一個最簡單的餅圖:

其實它在后台發送的請求,就是這個樣子的:
{
"size": 0,
"query": {
"query_string": {
"query": "顏色",
"analyze_wildcard": true
}
},
"_source": {
"excludes": []
},
"aggs": {
"2": {
"terms": {
"field": "url.keyword",
"size": 5,
"order": {
"_count": "desc"
}
}
}
}
}
如果不適用kibana,自己定義聚合請求,那么可以這樣寫:
GET /page/test/_search?pretty
{
"query": {
"query_string" : {
"default_field" : "url",
"query" : "顏色"
}
},
"size": 0,
"aggs" : {
"agg1" : {
"terms" : {
"field" : "url.keyword",
"size" : 10
},
"aggs" : {
"pvs" : { "sum" : { "field" : "pvs" } },
"uvs" : { "sum" : { "field" : "uvs" } }
}
}
}
}
另外,聚合也支持嵌套聚合,就是跟terms或者sum等agg並列寫一個新的aggs對象就行。
高亮
如果是自己使用elasticsearch,高亮也是一個非常重要的內容,它可以幫助最后的使用者快速了解搜索的結果。

后台的原理,是利用ES提供的highlight API,針對搜索的關鍵字,返回對應的字段。該字段中包含了一個自定義的標簽,前端可以基於這個標簽高亮着色。
舉個簡單的例子:
GET /_search
{
"query" : {
"match": { "content": "kimchy" }
},
"highlight" : {
"fields" : {
"content" : {}
}
}
}
上面的請求會針對content字段搜索kimchy。並且返回對應的字段,比如原來的字段內容時hello kimchy,經過高亮后,會再搜索結果的hits中返回:
{
"took": 3,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 30,
"max_score": 13.945707,
"hits": [
{
"_index": "page",
"_type": "test",
"_id": "AVrvHh_kvobeDQC6Q5Sg",
"_score": 13.945707,
"_source": {
"date": "2016-03-14",
"pvs": "3",
"url": "hello kimchy",
"@timestamp": "2017-03-21T04:29:07.187Z",
"uvs": "1",
"@version": "1"
},
"highlight": {
"url": [
"hello <em>kimchy</em>"
]
}
}
]
}
}
這樣就可以直接利用highlight中的字段做前端的顯示了。
另外,上面的<em>標簽可以自定義,比如:
GET /_search
{
"query" : {
"match": { "user": "kimchy" }
},
"highlight" : {
"pre_tags" : ["<tag1>"],
"post_tags" : ["</tag1>"],
"fields" : {
"_all" : {}
}
}
}
經過上面的一步一步的探索,你應該了解ELK的數據分析的流程與技巧了吧!如果有任何問題,也可以直接留言,可以再交流!
參考
- 1 創建Mapping
- 2 查詢Mapping
- 3 動態Maping
- 4 創建索引
- 5 logstash file插件
- 6 logstash grok插件
- 7 logstash elasticsearch插件
- 8 grok調試插件
