本文來自于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 美元。