1. 程式人生 > >基於ELK的資料分析實踐——滿滿的乾貨送給你

基於ELK的資料分析實踐——滿滿的乾貨送給你

很多人剛剛接觸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的資料分析的流程與技巧了吧!如果有任何問題,也可以直接留言,可以再交流!

參考