天天看點

Elastic Search權威指南之聚合聚合

本文來自于Elastic官網中文文檔,相比于其他書籍,Elastic官網權威指南是更好的參考資料。

文章目錄

  • 聚合
    • 1. 高階概念
      • 1.1 桶
      • 1.2 名額
      • 1.3 桶和名額的組合
    • 2. 嘗試聚合
      • 2.1 添加度量名額
      • 2.2 嵌套桶
      • 2.3 最後的修改

聚合

在這之前,本書緻力于搜尋。通過搜尋,如果我們有一個查詢并且希望找到比對這個查詢的文檔集,就好比在大海撈針。

通過聚合,我們會得到一個資料的概覽。我們需要的是分析和總結全套的資料而不是尋找單個文檔:

  • 在大海裡有多少針?
  • 針的平均長度是多少?
  • 按照針的制造商來劃分,針的長度中位值是多少?
  • 每月加入到海中的針有多少?

聚合也可以回答更加細微的問題:

  • 你最受歡迎的針的制造商是什麼?
  • 這裡面有異常的針麼?

聚合允許我們向資料提出一些複雜的問題。雖然功能完全不同于搜尋,但它使用相同的資料結構。這意味着聚合的執行速度很快并且就像搜尋一樣幾乎是實時的。

這對報告和儀表盤是非常強大的。你可以實時顯示你的資料,讓你立即回應,而不是對你的資料進行彙總( 需要一周時間去運作的 Hadoop 任務 ),您的報告随着你的資料變化而變化,而不是預先計算的、過時的和不相關的。

最後,聚合和搜尋是一起的。這意味着你可以在單個請求裡同時對相同的資料進行搜尋/過濾和分析。并且由于聚合是在使用者搜尋的上下文裡計算的,你不隻是顯示四星酒店的數量,而是顯示比對查詢條件的四星酒店的數量。

聚合是如此強大以至于許多公司已經專門為資料分析建立了大型 Elasticsearch 叢集。

1. 高階概念

類似于 DSL 查詢表達式,聚合也有 可組合 的文法:獨立單元的功能可以被混合起來提供你需要的自定義行為。這意味着隻需要學習很少的基本概念,就可以得到幾乎無盡的組合。

要掌握聚合,你隻需要明白兩個主要的概念:

桶(Buckets) :: 滿足特定條件的文檔的集合

名額(Metrics) :: 對桶内的文檔進行統計計算

這就是全部了!每個聚合都是一個或者多個桶和零個或者多個名額的組合。翻譯成粗略的SQL語句來解釋吧:

SELECT COUNT(color) <1>
FROM table
GROUP BY color <2>
           

<1>

COUNT(color)

相當于名額。

<2>

GROUP BY color

相當于桶。

桶在概念上類似于 SQL 的分組(GROUP BY),而名額則類似于

COUNT()

SUM()

MAX()

等統計方法。

讓我們深入這兩個概念并且了解和這兩個概念相關的東西。

1.1 桶

桶 簡單來說就是滿足特定條件的文檔的集合:

  • 一個雇員屬于 男性 桶或者 女性 桶
  • 奧爾巴尼屬于 紐約 桶
  • 日期2014-10-28屬于 十月 桶

當聚合開始被執行,每個文檔裡面的值通過計算來決定符合哪個桶的條件。如果比對到,文檔将放入相應的桶并接着進行聚合操作。

桶也可以被嵌套在其他桶裡面,提供階層化的或者有條件的劃分方案。例如,辛辛那提會被放入俄亥俄州這個桶,而 整個 俄亥俄州桶會被放入美國這個桶。

Elasticsearch 有很多種類型的桶,能讓你通過很多種方式來劃分文檔(時間、最受歡迎的詞、年齡區間、地理位置等等)。其實根本上都是通過同樣的原理進行操作:基于條件來劃分文檔。

1.2 名額

桶能讓我們劃分文檔到有意義的集合,但是最終我們需要的是對這些桶内的文檔進行一些名額的計算。分桶是一種達到目的的手段:它提供了一種給文檔分組的方法來讓我們可以計算感興趣的名額。

大多數 名額 是簡單的數學運算(例如最小值、平均值、最大值,還有彙總),這些是通過文檔的值來計算。在實踐中,名額能讓你計算像平均薪資、最高出售價格、95%的查詢延遲這樣的資料。

1.3 桶和名額的組合

聚合 是由桶和名額組成的。聚合可能隻有一個桶,可能隻有一個名額,或者可能兩個都有。也有可能有一些桶嵌套在其他桶裡面。例如,我們可以通過所屬國家來劃分文檔(桶),然後計算每個國家的平均薪酬(名額)。

由于桶可以被嵌套,我們可以實作非常多并且非常複雜的聚合:

1.通過國家劃分文檔(桶)

2.然後通過性别劃分每個國家(桶)

3.然後通過年齡區間劃分每種性别(桶)

4.最後,為每個年齡區間計算平均薪酬(名額)

最後将告訴你每個

<國家, 性别, 年齡>

組合的平均薪酬。所有的這些都在一個請求内完成并且隻周遊一次資料!

2. 嘗試聚合

我們可以用以下幾頁定義不同的聚合和它們的文法,但學習聚合的最佳途徑就是用執行個體來說明。一旦我們獲得了聚合的思想,以及如何合理地嵌套使用它們,那麼文法就變得不那麼重要了。

NOTE

聚合的桶操作和度量的完整用法可以在Elasticsearch 參考中找到。本章中會涵蓋其中很多内容,但在閱讀完本章後檢視它會有助于我們對它的整體能力有所了解。

是以讓我們先看一個例子。我們将會建立一些對汽車經銷商有用的聚合,資料是關于汽車交易的資訊:車型、制造商、售價、何時被出售等。

首先我們批量索引一些資料:

POST /cars/transactions/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
           

有了資料,開始建構我們的第一個聚合。汽車經銷商可能會想知道哪個顔色的汽車銷量最好,用聚合可以輕易得到結果,用

terms

桶操作:

GET /cars/transactions/_search
{
    "size" : 0,
    "aggs" : { <1>
        "popular_colors" : { <2>
            "terms" : { <3>
              "field" : "color"
            }
        }
    }
}
           

<1> 聚合操作被置于頂層參數

aggs

之下(如果你願意,完整形式

aggregations

同樣有效)。

<2> 然後,可以為聚合指定一個我們想要名稱,本例中是:

popular_colors

<3> 最後,定義單個桶的類型

terms

聚合是在特定搜尋結果背景下執行的,這也就是說它隻是查詢請求的另外一個頂層參數(例如,使用

/_search

端點)。聚合可以與查詢結對,但我們會晚些在限定聚合的範圍中來解決這個問題。

NOTE

可能會注意到我們将

size

設定成 0 。我們并不關心搜尋結果的具體内容,是以将傳回記錄數設定為 0 來提高查詢速度。設定

size: 0

與 Elasticsearch 1.x 中使用

count

搜尋類型等價。

然後我們為聚合定義一個名字,名字的選擇取決于使用者,響應的結果會以我們定義的名字為标簽,這樣應用就可以解析得到的結果。

随後我們定義聚合本身,在本例中,我們定義了一個單

terms

桶。這個

terms

桶會為每個碰到的唯一詞項動态建立新的桶。因為我們告訴它使用

color

字段,是以

terms

桶會為每個顔色動态建立新桶。

讓我們運作聚合并檢視結果:

{
...
   "hits": {
      "hits": [] <1>
   },
   "aggregations": {
      "popular_colors": { <2>
         "buckets": [
            {
               "key": "red", <3>
               "doc_count": 4 <4>
            },
            {
               "key": "blue",
               "doc_count": 2
            },
            {
               "key": "green",
               "doc_count": 2
            }
         ]
      }
   }
}
           

<1> 因為我們設定了

size

參數,是以不會有 hits 搜尋結果傳回。

<2>

popular_colors

聚合是作為

aggregations

字段的一部分被傳回的。

<3> 每個桶的

key

都與

color

字段裡找到的唯一詞對應。它總會包含

doc_count

字段,告訴我們包含該詞項的文檔數量。

<4> 每個桶的數量代表該顔色的文檔數量。

響應包含多個桶,每個對應一個唯一顔色(例如:紅 或 綠)。每個桶也包括

聚合進

該桶的所有文檔的數量。例如,有四輛紅色的車。

前面的這個例子完全是實時執行的:一旦文檔可以被搜到,它就能被聚合。這也就意味着我們可以直接将聚合的結果源源不斷的傳入圖形庫,然後生成實時的儀表盤。

不久,你又銷售了一輛銀色的車,我們的圖形就會立即動态更新銀色車的統計資訊。

瞧!這就是我們的第一個聚合!

2.1 添加度量名額

前面的例子告訴我們每個桶裡面的文檔數量,這很有用。但通常,我們的應用需要提供更複雜的文檔度量。例如,每種顔色汽車的平均價格是多少?

為了擷取更多資訊,我們需要告訴 Elasticsearch 使用哪個字段,計算何種度量。這需要将度量 嵌套 在桶内,度量會基于桶内的文檔計算統計結果。

讓我們繼續為汽車的例子加入

average

平均度量:

GET /cars/transactions/_search
{
   "size" : 0,
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": { <1>
            "avg_price": { <2>
               "avg": {
                  "field": "price" <3>
               }
            }
         }
      }
   }
}
           

<1> 為度量新增

aggs

層。

<2> 為度量指定名字:

avg_price

<3> 最後,為

price

字段定義

avg

度量。

正如所見,我們用前面的例子加入了新的

aggs

層。這個新的聚合層讓我們可以将

avg

度量嵌套置于

terms

桶内。實際上,這就為每個顔色生成了平均價格。

正如

顔色

的例子,我們需要給度量起一個名字(

avg_price

)這樣可以稍後根據名字擷取它的值。最後,我們指定度量本身(

avg

)以及我們想要計算平均值的字段(

price

):

{
...
   "aggregations": {
      "colors": {
         "buckets": [
            {
               "key": "red",
               "doc_count": 4,
               "avg_price": { <1>
                  "value": 32500
               }
            },
            {
               "key": "blue",
               "doc_count": 2,
               "avg_price": {
                  "value": 20000
               }
            },
            {
               "key": "green",
               "doc_count": 2,
               "avg_price": {
                  "value": 21000
               }
            }
         ]
      }
   }
...
}
           

<1> 響應中的新字段

avg_price

盡管響應隻發生很小改變,實際上我們獲得的資料是增長了。之前,我們知道有四輛紅色的車,現在,紅色車的平均價格是 $32,500 美元。這個資訊可以直接顯示在報表或者圖形中。

2.2 嵌套桶

在我們使用不同的嵌套方案時,聚合的力量才能真正得以顯現。 在前例中,我們已經看到如何将一個度量嵌入桶中,它的功能已經十分強大了。

但真正令人激動的分析來自于将桶嵌套進 另外一個桶 所能得到的結果。 現在,我們想知道每個顔色的汽車制造商的分布:

GET /cars/transactions/_search
{
   "size" : 0,
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": {
            "avg_price": { <1>
               "avg": {
                  "field": "price"
               }
            },
            "make": { <2>
                "terms": {
                    "field": "make" <3>
                }
            }
         }
      }
   }
}
           

<1> 注意前例中的 avg_price 度量仍然保持原位。

<2> 另一個聚合 make 被加入到了 color 顔色桶中。

<3> 這個聚合是 terms 桶,它會為每個汽車制造商生成唯一的桶。

這裡發生了一些有趣的事。 首先,我們可能會觀察到之前例子中的

avg_price

度量完全沒有變化,還在原來的位置。 一個聚合的每個 層級 都可以有多個度量或桶,avg_price 度量告訴我們每種顔色汽車的平均價格。它與其他的桶和度量互相獨立。

這對我們的應用非常重要,因為這裡面有很多互相關聯,但又完全不同的度量需要收集。聚合使我們能夠用一次資料請求獲得所有的這些資訊。

另外一件值得注意的重要事情是我們新增的這個 make 聚合,它是一個 terms 桶(嵌套在 colors 、 terms 桶内)。這意味着它 會為資料集中的每個唯一組合生成( color 、 make )元組。

讓我們看看傳回的響應(為了簡單我們隻顯示部分結果):

{
...
   "aggregations": {
      "colors": {
         "buckets": [
            {
               "key": "red",
               "doc_count": 4,
               "make": { <1>
                  "buckets": [
                     {
                        "key": "honda", <2>
                        "doc_count": 3
                     },
                     {
                        "key": "bmw",
                        "doc_count": 1
                     }
                  ]
               },
               "avg_price": {
                  "value": 32500 <3>
               }
            },
...
}
           

<1> 正如期望的那樣,新的聚合嵌入在每個顔色桶中。

<2> 現在我們看見按不同制造商分解的每種顔色下車輛資訊。

<3> 最終,我們看到前例中的 avg_price 度量仍然維持不變。

響應結果告訴我們以下幾點:

  • 紅色車有四輛。
  • 紅色車的平均售價是 $32,500 美元。
  • 其中三輛是 Honda 本田制造,一輛是 BMW 寶馬制造。

2.3 最後的修改

讓我們回到話題的原點,在進入新話題之前,對我們的示例做最後一個修改,為每個汽車生成商計算最低和最高的價格:

GET /cars/transactions/_search
{
   "size" : 0,
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": {
            "avg_price": { "avg": { "field": "price" }
            },
            "make" : {
                "terms" : {
                    "field" : "make"
                },
                "aggs" : { <1>
                    "min_price" : { "min": { "field": "price"} }, <2>
                    "max_price" : { "max": { "field": "price"} } <3>
                }
            }
         }
      }
   }
}
           

<1> 我們需要增加另外一個嵌套的

aggs

層級。

<2> 然後包括

min

最小度量。

<3> 以及

max

最大度量。

得到以下輸出(隻顯示部分結果):

{
...
   "aggregations": {
      "colors": {
         "buckets": [
            {
               "key": "red",
               "doc_count": 4,
               "make": {
                  "buckets": [
                     {
                        "key": "honda",
                        "doc_count": 3,
                        "min_price": {
                           "value": 10000 <1>
                        },
                        "max_price": {
                           "value": 20000 <1>
                        }
                     },
                     {
                        "key": "bmw",
                        "doc_count": 1,
                        "min_price": {
                           "value": 80000
                        },
                        "max_price": {
                           "value": 80000
                        }
                     }
                  ]
               },
               "avg_price": {
                  "value": 32500
               }
            },
...
           

<1>

min

max

度量現在出現在每個汽車制造商(

make

)下面。

有了這兩個桶,我們可以對查詢的結果進行擴充并得到以下資訊:

  • 有四輛紅色車。
  • 紅色車的平均售價是 $32,500 美元。
  • 其中三輛紅色車是 Honda 本田制造,一輛是 BMW 寶馬制造。
  • 最便宜的紅色本田售價為 $10,000 美元。
  • 最貴的紅色本田售價為 $20,000 美元。

繼續閱讀