ElasticSearch 的 聚合(Aggregations)


Elasticsearch有一個功能叫做 聚合(aggregations) ,它允許你在數據上生成復雜的分析統計。它很像SQL中的 GROUP BY 但是功能更強大。

Aggregations種類分為:

  • Metrics, Metrics 是簡單的對過濾出來的數據集進行avg,max等操作,是一個單一的數值。
  • Bucket, Bucket 你則可以理解為將過濾出來的數據集按條件分成多個小數據集,然后Metrics會分別作用在這些小數據集上。

對於最后聚合出來的結果,其實我們還希望能進一步做處理,所以有了Pipline Aggregations,其實就是組合一堆的Aggregations 對已經聚合出來的結果再做處理。

Elasticsearch 從1.0開始支持aggregation,基本上有了普通SQL的聚合能力。從 2.0 開始支持 pipeline aggregation,可以支持類似SQL sub query的嵌套聚合的能力。

簡單聚合的例子

內容來自:

http://es.xiaoleilu.com/010_Intro/35_Tutorial_Aggregations.html

我們找到所有職員中最大的共同點(興趣愛好)是什么:

GET /megacorp/employee/_search
{
  "aggs": {
    "all_interests": {
      "terms": { "field": "interests" }
    }
  }
}

暫時先忽略語法只看查詢結果:

{
   ...
   "hits": { ... },
   "aggregations": {
      "all_interests": {
         "buckets": [
            {
               "key":       "music",
               "doc_count": 2
            },
            {
               "key":       "forestry",
               "doc_count": 1
            },
            {
               "key":       "sports",
               "doc_count": 1
            }
         ]
      }
   }
}

我們可以看到兩個職員對音樂有興趣,一個喜歡林學,一個喜歡運動。這些數據並沒有被預先計算好,它們是實時的從匹配查詢語句的文檔中動態計算生成的。

如果我們想知道所有姓"Smith"的人最大的共同點(興趣愛好),我們只需要增加合適的語句既可:

GET /megacorp/employee/_search
{
  "query": {
    "match": {
      "last_name": "smith"
    }
  },
  "aggs": {
    "all_interests": {
      "terms": {
        "field": "interests"
      }
    }
  }
}

all_interests聚合已經變成只包含和查詢語句相匹配的文檔了:

...
  "all_interests": {
     "buckets": [
        {
           "key": "music",
           "doc_count": 2
        },
        {
           "key": "sports",
           "doc_count": 1
        }
     ]
  }

聚合也允許分級匯總。例如,讓我們統計每種興趣下職員的平均年齡:

GET /megacorp/employee/_search
{
    "aggs" : {
        "all_interests" : {
            "terms" : { "field" : "interests" },
            "aggs" : {
                "avg_age" : {
                    "avg" : { "field" : "age" }
                }
            }
        }
    }
}

雖然這次返回的聚合結果有些復雜,但任然很容易理解:

...
  "all_interests": {
     "buckets": [
        {
           "key": "music",
           "doc_count": 2,
           "avg_age": {
              "value": 28.5
           }
        },
        {
           "key": "forestry",
           "doc_count": 1,
           "avg_age": {
              "value": 35
           }
        },
        {
           "key": "sports",
           "doc_count": 1,
           "avg_age": {
              "value": 25
           }
        }
     ]
  }

該聚合結果比之前的聚合結果要更加豐富。我們依然得到了興趣以及數量(指具有該興趣的員工人數)的列表,但是現在每個興趣額外擁有avg_age字段來顯示具有該興趣員工的平均年齡。

 

Aggregations 一些概念

來自: http://blog.csdn.net/dm_vincent/article/details/42387161

Buckets(桶):

滿足某個條件的文檔集合。

Metrics(指標):

為某個桶中的文檔計算得到的統計信息。

如果是SQL,  則 SELECT COUNT(color)  FROM table GROUP BY color 這個語句中的COUNT(color)就相當於一個指標。GROUP BY color則相當於一個桶。

桶和SQL中的組(Grouping)擁有相似的概念,而指標則與COUNT(),SUM(),MAX()等相似。

桶(Buckets)

一個桶就是滿足特定條件的一個文檔集合:

  • 一名員工要么屬於男性桶,或者女性桶。
  • 城市Albany屬於New York州這個桶。
  • 日期2014-10-28屬於十月份這個桶。

隨着聚合被執行,每份文檔中的值會被計算來決定它們是否匹配了桶的條件。如果匹配成功,那么該文檔會被置入該桶中,同時聚合會繼續執行。

桶也能夠嵌套在其它桶中,能讓你完成層次或者條件划分這些需求。比如,Cincinnati可以被放置在Ohio州這個桶中,而整個Ohio州則能夠被放置在美國這個桶中。

ES中有很多類型的桶,讓你可以將文檔通過多種方式進行划分(按小時,按最流行的詞條,按年齡區間,按地理位置,以及更多)。但是從根本上,它們都根據相同的原理運作:按照條件對文檔進行划分。

指標(Metrics)

桶能夠讓我們對文檔進行有意義的划分,但是最終我們還是需要對每個桶中的文檔進行某種指標計算。分桶是達到最終目的的手段:提供了對文檔進行划分的方法,從而讓你能夠計算需要的指標。

多數指標僅僅是簡單的數學運算(比如,min,mean,max以及sum),它們使用文檔中的值進行計算。在實際應用中,指標能夠讓你計算例如平均薪資,最高出售價格,或者百分之95的查詢延遲。

將兩者結合起來

一個聚合就是一些桶和指標的組合。一個聚合可以只有一個桶,或者一個指標,或者每樣一個。在桶中甚至可以有多個嵌套的桶。比如,我們可以將文檔按照其所屬國家進行分桶,然后對每個桶計算其平均薪資(一個指標)。

因為桶是可以嵌套的,我們能夠實現一個更加復雜的聚合操作:

  1. 將文檔按照國家進行分桶。(桶)
  2. 然后將每個國家的桶再按照性別分桶。(桶)
  3. 然后將每個性別的桶按照年齡區間進行分桶。(桶)
  4. 最后,為每個年齡區間計算平均薪資。(指標)

此時,就能夠得到每個<國家,性別,年齡>組合的平均薪資信息了。它可以通過一個請求,一次數據遍歷來完成!

 

聚合的進一步演示

根據這個理論,我們再來看看聚合,我們建立一個也許對汽車交易商有所用處的聚合。

內容來自: http://blog.csdn.net/dm_vincent/article/details/42407823

數據

數據是關於汽車交易的:汽車型號,制造商,銷售價格,銷售時間以及一些其他的相關數據。

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?search_type=count
{
    "aggs" : {
        "colors" : {
            "terms" : {
              "field" : "color"
            }
        }
    }
}

因為我們並不關心搜索結果,使用的search_type是count,它的速度更快。

聚合工作在頂層的aggs參數下(當然你也可以使用更長的aggregations)。

然后給這個聚合起了一個名字:colors。

最后,我們定義了一個terms類型的桶,它針對color字段。

我們為聚合起一個名字colors。命名規則是有你決定的; 聚合的響應會被該名字標記,因此在應用中你就能夠根據名字來得到聚合結果,並對它們進行操作了。

比如,我們定義了一個terms類型的桶。terms桶會動態地為每一個它遇到的不重復的詞條創建一個新的桶。因為我們針對的是color字段,那么terms桶會動態地為每種顏色創建一個新桶。

讓我們執行該聚合來看看其結果:

{
...
   "hits": {
      "hits": []
   },
   "aggregations": {
      "colors": {
         "buckets": [
            {
               "key": "red",
               "doc_count": 4
            },
            {
               "key": "blue",
               "doc_count": 2
            },
            {
               "key": "green",
               "doc_count": 2
            }
         ]
      }
   }
}

因為我們使用的search_type為count,所以沒有搜索結果被返回。 每個桶中的key對應的是在color字段中找到的不重復的詞條。它同時也包含了一個doc_count,用來表示包含了該詞條的文檔數量。

響應包含了一個桶列表,每個桶都對應着一個不重復的顏色(比如,紅色或者綠色)。每個桶也包含了“掉入”該桶中的文檔數量。比如,有4輛紅色的車。

前面的例子是完全實時(Real-Time)的:如果文檔是可搜索的,那么它們就能夠被聚合。這意味着你能夠將拿到的聚合結果置入到一個圖形庫中來生成實時的儀表板(Dashboard)。一旦你賣出了一台銀色汽車,在圖形上關於銀色汽車的統計數據就會被動態地更新。

 

添加一個指標(Metric)

從前面的例子中,我們可以知道每個桶中的文檔數量。但是,通常我們的應用會需要基於那些文檔的更加復雜的指標(Metric)。比如,每個桶中的汽車的平均價格是多少?

為了得到該信息,我們得告訴ES需要為哪些字段計算哪些指標。這需要將指標嵌套到桶中。指標會基於桶中的文檔的值來計算相應的統計信息。

讓我們添加一個計算平均值的指標:

GET /cars/transactions/_search?search_type=count
{
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": {
            "avg_price": {
               "avg": {
                  "field": "price"
               }
            }
         }
      }
   }
}

我們添加了一個新的aggs層級來包含該指標。然后給該指標起了一個名字:avg_price。最后定義了該指標作用的字段為price。.

正如你所看到的,我們向前面的例子中添加了一個新的aggs層級。這個新的聚合層級能夠讓我們將avg指標嵌套在terms桶中。這意味着我們能為每種顏色都計算一個平均值。

同樣的,我們需要給指標起一個名(avg_price)來讓我們能夠在將來得到其值。最后,我們指定了指標本身(avg)以及該指標作用的字段(price):

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

現在,在響應中多了一個avg_price元素。

盡管得到的響應只是稍稍有些變化,但是獲得的數據增加的了許多。之前我們只知道有4輛紅色汽車。現在我們知道了紅色汽車的平均價格是32500刀。這些數據你可以直接插入到報表中。

 

桶中的桶(Buckets inside Buckets)

當你開始使用不同的嵌套模式時,聚合強大的能力才會顯現出來。在前面的例子中,我們已經知道了如何將一個指標嵌套進一個桶的,它的功能已經十分強大了。

但是真正激動人心的分析功能來源於嵌套在其它桶中的桶。現在,讓我們來看看如何找到每種顏色的汽車的制造商分布信息:

GET /cars/transactions/_search?search_type=count
{
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": {
            "avg_price": {
               "avg": {
                  "field": "price"
               }
            },
            "make": {
                "terms": {
                    "field": "make"
                }
            }
         }
      }
   }
}

此時發生了一些有意思的事情。首先,你會注意到前面的avg_price指標完全沒有變化。一個聚合的每個層級都能夠擁有多個指標或者桶。avg_price指標告訴了我們每種汽車顏色的平均價格。為每種顏色創建的桶和指標是各自獨立的。

這個性質對你的應用而言是很重要的,因為你經常需要收集一些互相關聯卻又完全不同的指標。聚合能夠讓你對數據遍歷一次就得到所有需要的信息。

另外一件重要的事情是添加了新聚合make,它是一個terms類型的桶(嵌套在名為colors的terms桶中)。這意味着我們會根據數據集創建不重復的(color, make)組合。

讓我們來看看得到的響應(有省略,因為響應太長了):

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

該響應告訴了我們如下信息:

  • 有4輛紅色汽車。
  • 紅色汽車的平均價格是32500美刀。
  • 紅色汽車中的3輛是Honda,1輛是BMW。

 

最后的一個修改(One Final Modification)


在繼續討論新的話題前,為了把問題講清楚讓我們對該例子進行最后一個修改。為每個制造商添加兩個指標來計算最低和最高價格:

GET /cars/transactions/_search?search_type=count
{
   "aggs": {
      "colors": {
         "terms": {
            "field": "color"
         },
         "aggs": {
            "avg_price": { "avg": { "field": "price" }
            },
            "make" : {
                "terms" : {
                    "field" : "make"
                },
                "aggs" : {
                    "min_price" : { "min": { "field": "price"} },
                    "max_price" : { "max": { "field": "price"} }
                }
            }
         }
      }
   }
}

我們需要添加另一個aggs層級來進行對min和max的嵌套。

得到的響應如下(仍然有省略):

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

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

在每個make桶下,多了min和max的指標。

此時,我們可以得到如下信息:

  • 有4輛紅色汽車。
  • 紅色汽車的平均價格是32500美刀。
  • 紅色汽車中的3輛是Honda,1輛是BMW。
  • 紅色Honda汽車中,最便宜的價格為10000美刀。
  • 最貴的紅色Honda汽車為20000美刀。

參考資料:

ElasticSearch Aggregations 分析
http://www.jianshu.com/p/56ad2b7e27b7

ElasticSearch Aggregation Bucket 實例分析
http://www.jianshu.com/p/643a946d6c9a

http://es.xiaoleilu.com/010_Intro/35_Tutorial_Aggregations.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM