從本篇將開始進入ES系列的聚合部分(Aggregations)。
本篇重點介紹Elasticsearch Metric Aggregations(度量聚合)。
Metric聚合,主要針對數值類型的字段,類似於關係型數據庫中的sum、avg、max、min等聚合類型。
本例基於如下索引進行試驗:
public static void createMapping_agregations() {
RestHighLevelClient client = EsClient.getClient();
try {
CreateIndexRequest request = new CreateIndexRequest("aggregations_index02");
XContentBuilder jsonBuilder = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("orderId")
.field("type", "integer")
.endObject()
.startObject("orderNo")
.field("type", "keyword")
.endObject()
.startObject("totalPrice")
.field("type", "double")
.endObject()
.startObject("sellerId")
.field("type", "integer")
.endObject()
.startObject("sellerName")
.field("type", "keyword")
.endObject()
.startObject("buyerId")
.field("type", "integer")
.endObject()
.startObject("buyerName")
.field("type", "keyword")
.endObject()
.startObject("createTime")
.field("type", "date")
.field("format", "yyyy-MM-dd HH:mm:ss")
.endObject()
.startObject("status")
.field("type", "integer")
.endObject()
.startObject("reciveAddressId")
.field("type", "integer")
.endObject()
.startObject("reciveName")
.field("type", "keyword")
.endObject()
.startObject("phone")
.field("type", "keyword")
.endObject()
.startObject("skuId")
.field("type", "integer")
.endObject()
.startObject("skuNo")
.field("type", "keyword")
.endObject()
.startObject("goodsId")
.field("type", "integer")
.endObject()
.startObject("goodsName")
.field("type", "keyword")
.endObject()
.startObject("num")
.field("type", "integer")
.endObject()
.endObject()
.endObject();
request.mapping("_doc", jsonBuilder);
System.out.println(client.indices().create(request, RequestOptions.DEFAULT));
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
對應的SQL表結構如下:
CREATE TABLE `es_order_tmp` (
`orderId` int(11) NOT NULL DEFAULT '0' COMMENT '主鍵',
`orderNo` varchar(30) DEFAULT NULL COMMENT '訂單編號',
`totalPrice` decimal(10,2) DEFAULT NULL COMMENT '訂單總價,跟支付中心返回金額相等,包括了雅豆,餘額,第三方支付的金額。運費包含在內,優惠券抵扣的金額不含在內',
`sellerId` int(11) DEFAULT NULL COMMENT '商家ID',
`selerName` varchar(50) DEFAULT NULL COMMENT '商家名稱',
`buyerId` int(11) DEFAULT NULL COMMENT '創建者,購買者',
`buyerName` varchar(255) DEFAULT NULL COMMENT '業主姓名',
`createTime` varchar(22) DEFAULT NULL,
`status` int(11) DEFAULT NULL COMMENT '訂單狀態,0:待付款,1:待發貨,2:待收貨,3:待評價,4:訂單完成,5:訂單取消,6:退款處理中,7:拒絕退貨,8:同意退貨,9:退款成功,10:退款關閉,11:訂單支付超時,12:半支付狀態',
`reciveAddressId` int(11) DEFAULT NULL COMMENT '收貨地址ID',
`reciveName` varchar(50) DEFAULT NULL,
`phone` varchar(30) DEFAULT NULL COMMENT '聯繫號碼',
`skuId` int(11) DEFAULT NULL COMMENT '貨品ID',
`skuNo` varchar(100) DEFAULT NULL COMMENT 'SKU編號',
`goodsId` int(11) DEFAULT NULL COMMENT '商品ID',
`goodsName` varchar(100) DEFAULT NULL COMMENT '商品名稱',
`num` int(11) DEFAULT NULL COMMENT '數量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
avg 平均值
POST /exams/_search?size=0
{
"aggs" : {
"avg_grade" : { "avg" : { "field" : "grade" } }
}
}
對字段grade取平均值。
對應的java示例如下:
public static void testMatchQuery() {
RestHighLevelClient client = EsClient.getClient();
try {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("aggregations_index02");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
AggregationBuilder avg = AggregationBuilders.avg("avg-aggregation").field("num").missing(0); // @1
sourceBuilder.aggregation(avg);
sourceBuilder.size(0);
sourceBuilder.query(
QueryBuilders.termQuery("sellerId", 24)
);
searchRequest.source(sourceBuilder);
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(result);
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
其中代碼@1:missing(0)表示如果文檔中沒有取平均值的字段時,則使用該值進行計算,本例中使用0參與計算。
其返回結果如下:
{
"took":2,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":39,
"max_score":0,
"hits":[
]
},
"aggregations":{
"avg#avg-aggregation":{
"value":1.2820512820512822
}
}
}
Weighted Avg Aggregation 加權平均聚合
加權平均算法,∑(value * weight) / ∑(weight)。
加權平均(weghted_avg)支持的參數列表:
- value
提供值的字段或腳本的配置。例如定義計算哪個字段的平均值,該值支持如下子參數: - field
用來定義平均值的字段名稱。 - missing
用來定義如果匹配到的文檔沒有avg字段,使用該值來參與計算。 - weight
用來定義權重的對象,其可選屬性如下: - field
定義權重來源的字段。 - missing
如果文檔缺失權重來源字段,以該值來代表該文檔的權重值。 - format
數值類型格式化。 - value_type
用來指定value的類型,例如ValueType.DATE、ValueType.IP等。
示例如下:
POST /exams/_search
{
"size": 0,
"aggs" : {
"weighted_grade": {
"weighted_avg": {
"value": {
"field": "grade"
},
"weight": {
"field": "weight" // @2
}
}
}
}
}
從文檔中抽取屬性爲weight的字段的值來當權重值。
其JAVA示例如下:
public static void test_weight_avg_aggregation() {
RestHighLevelClient client = EsClient.getClient();
try {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("aggregations_index02");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
WeightedAvgAggregationBuilder avg = AggregationBuilders.weightedAvg("avg-aggregation")
.value(
(new MultiValuesSourceFieldConfig.Builder())
.setFieldName("num")
.setMissing(0)
.build()
)
.weight(
(new MultiValuesSourceFieldConfig.Builder())
.setFieldName("num")
.setMissing(1)
.build()
)
// .valueType(ValueType.LONG)
;
avg.toString();
sourceBuilder.aggregation(avg);
sourceBuilder.size(0);
sourceBuilder.query(
QueryBuilders.termQuery("sellerId", 24)
);
searchRequest.source(sourceBuilder);
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(result);
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
Cardinality Aggregation
基數聚合,先distinct,再聚合,類似關係型數據庫(count(distinct))。
示例如下:
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : {
"cardinality" : {
"field" : "type"
}
}
}
}
對應的JAVA示例如下:
public static void test_Cardinality_Aggregation() {
RestHighLevelClient client = EsClient.getClient();
try {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("aggregations_index02");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
AggregationBuilder aggregationBuild = AggregationBuilders.cardinality("buyerid_count").field("buyerId");
sourceBuilder.aggregation(aggregationBuild);
sourceBuilder.size(0);
sourceBuilder.query(
QueryBuilders.termQuery("sellerId", 24)
);
searchRequest.source(sourceBuilder);
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(result);
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
返回結果如下:
{
"took":30,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":39,
"max_score":0,
"hits":[
]
},
"aggregations":{
"cardinality#type_count":{
"value":11
}
}
}
上述實現與SQL:SELECT COUNT(DISTINCT buyerId) from es_order_tmp where sellerId=24; 效果類似,表示購買了商家id爲24的買家個數。
其核心參數如下:
- precision_threshold
精確度控制。在此計數之下,期望計數接近準確。在這個值之上,計數可能會變得更加模糊(不準確)。支持的最大值是40000,超過此值的閾值與40000的閾值具有相同的效果。默認值是3000。
上述示例中返回的11是精確值,如果改寫成下面的代碼,結果將變的不準確:
field("buyerId").precisionThreshold(5)
其返回結果如下:
{
"took":5,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":39,
"max_score":0,
"hits":[
]
},
"aggregations":{
"cardinality#buyerid_count":{
"value":9
}
}
}
- Pre-computed hashes
一個比較好的實踐是需要對字符串類型的字段進行基數聚合的話,可以提前索引該字符串的hash值,通過對hash值的聚合,提高效率。 - Missing Value
missing參數定義了應該如何處理缺少值的文檔。默認情況下,它們將被忽略,但也可以將它們視爲具有一個值,通過missing value來設置。
Extended Stats Aggregation
stats聚合的擴展版本,示例如下:
GET /exams/_search
{
"size": 0,
"aggs" : {
"grades_stats" : { "extended_stats" : { "field" : "grade" } }
}
}
對應的JAVA示例如下:
public static void test_Extended_Stats_Aggregation() {
RestHighLevelClient client = EsClient.getClient();
try {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("aggregations_index02");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
AggregationBuilder aggregationBuild = AggregationBuilders.extendedStats("extended_stats")
.field("num")
;
sourceBuilder.aggregation(aggregationBuild);
sourceBuilder.size(0);
sourceBuilder.query(
QueryBuilders.termQuery("sellerId", 24)
);
searchRequest.source(sourceBuilder);
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(result);
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
返回的結果如下:
{
"took":13,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":39,
"max_score":0,
"hits":[
]
},
"aggregations":{
"extended_stats#extended_stats":{
"count":39, // @1
"min":1, // @2
"max":11, // @3
"avg":1.2820512820512822, // @4
"sum":50, // @5
"sum_of_squares":162, // @6
"variance":2.5101906640368177, // @7
"std_deviation":1.5843581236692725, // @8
"std_deviation_bounds":{ // @9
"upper":4.450767529389827,
"lower":-1.886664965287263
}
}
}
}
將所能支持的聚合類型都返回。
@1:返回符合條件的總條數。
@2:該屬性在符合條件中的最小值。
@3:該屬性在符合條件中的最大值。
@4:該屬性在符合條件的文檔中的平均值。
@5:該屬性在符合條件的文檔中的sum求和。
@6-9:暫未理解其含義。
同樣支持missing屬性。
max Aggregation
求最大值,與avg Aggregation聚合類似,不再重複介紹。
min Aggregation
求最小值,與avg Aggregation聚合類似,不再重複介紹。
Percentiles Aggregation
百分位計算,ES提供的另外一種近似度量方式。主要用於展現以具體百分比下觀察到的數值,例如,第95個百分位上的數值,是高於 95% 的數據總和。百分位聚合通常用來找出異常,適用與使用統計學中正態分佈來觀察問題。
官方文檔:https://www.elastic.co/guide/cn/elasticsearch/guide/current/percentiles.html
例如:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time"
}
}
}
}
load_time,在官方文檔中的字段含義爲字段加載時間,其返回值如下:
{
...
"aggregations": {
"load_time_outlier": {
"values" : {
"1.0": 5.0,
"5.0": 25.0,
"25.0": 165.0,
"50.0": 445.0,
"75.0": 725.0,
"95.0": 945.0,
"99.0": 985.0
}
}
}
}
默認的百分比key爲[ 1, 5, 25, 50, 75, 95, 99 ]。
按照官方的解讀,可以這樣理解上述返回結果:
"1.0": 5.0;表示(100-1)%的數據都大於5.0;也表示1%的數據小於5.0。
"5.0": 25.0 表示,95%的請求的加載時間大於等於25。
"99.0": 985.0 表示1%的請求的加載時間大於985.0。
- percentile
用來定義其百分比,例如percents:[10,50,95,99] - keyed
默認情況下,keyed參數爲true,其結果的返回格式如上:
"values" : {
"1.0": 5.0,
"5.0": 25.0,
"25.0": 165.0,
"50.0": 445.0,
"75.0": 725.0,
"95.0": 945.0,
"99.0": 985.0
}
如果設置keyed=false,則返回值的格式如下:
"aggregations": {
"load_time_outlier": {
"values": [
{
"key": 1.0,
"value": 5.0
},
{
"key": 5.0,
"value": 25.0
},
...
]
}
}
- 百分位使用場景
百分位通常使用近似統計。
計算百分位數有許多不同的算法。簡單實現只是將所有值存儲在一個排序數組中。要找到第50個百分位,只需找到my_array[count(my_array) * 0.5]處的值。
顯然,這種簡單的實現沒有伸縮性——排序數組隨數據集中值的數量線性增長。爲了計算es集羣中可能存在的數十億個值的百分位數,兼顧性能的需求,故ES通常使用計算近似百分位數。近似百分位通常使用TDigest 算法。
在使用近似百分位時,通常需要考慮這些:
- 準確度與q(1-q)成正比。這意味着極端百分位數(如99%)比不那麼極端的百分位數(如中位數)更準確
- 對於較小的值集,百分位數是非常準確的(如果數據足夠小,可能是100%準確)。
- 當桶中值的數量增加時,算法開始近似百分位數。它有效地以準確性換取內存節省。準確的不準確程度很難一概而論,因爲它取決於您的數據分佈和聚合的數據量。
- Compression
近似算法必須平衡內存利用率和估計精度。這個平衡可以使用參數compression來控制。
TDigest算法使用許多“節點”來近似百分位數——可用節點越多,與數據量成比例的準確性(和大內存佔用)就越高。壓縮參數將節點的最大數量限制爲20 * compression。
因此,通過增加壓縮值,可以以增加內存爲代價來提高百分位數的準確性。較大的壓縮值也會使算法變慢,因爲底層樹數據結構的大小會增加,從而導致更昂貴的操作。默認壓縮值是100。
一個“節點”使用大約32字節的內存,因此在最壞的情況下(大量數據按順序到達),默認設置將產生大約64KB(32 20 100)大小的TDigest。實際上,數據往往更隨機,TDigest使用的內存更少。
HDR Histogram(直方圖)
HDR直方圖(High Dynamic Range Histogram,高動態範圍直方圖)是一種替代實現,在計算延遲度量的百分位數時非常有用,因爲它比t-digest實現更快,但需要更大的內存佔用。此實現維護一個固定的最壞情況百分比錯誤(指定爲有效數字的數量)。這意味着如果數據記錄值從1微秒到1小時(3600000000毫秒)直方圖設置爲3位有效數字,它將維持一個價值1微秒的分辨率值1毫秒,3.6秒(或更好的)最大跟蹤值(1小時)。
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_outlier" : {
"percentiles" : {
"field" : "load_time",
"percents" : [95, 99, 99.9],
"hdr": {
"number_of_significant_value_digits" : 3
}
}
}
}
}
- hdr
通過hdr屬性指定直方圖相關的參數。 - number_of_significant_value_digits
指定以有效位數爲單位的直方圖值的分辨率。
注意:hdr直方圖只支持正值,如果傳遞負值,則會出錯。如果值的範圍是未知的,那麼使用HDRHistogram也不是一個好主意,因爲這可能會導致內存的大量使用。
- Missing value
missing參數定義了應該如何處理缺少值的文檔。默認情況下,它們將被忽略,但也可以將它們視爲具有一個值。
Percentiles Aggregation示例(Java Demo):
public static void test_Percentiles_Aggregation() {
RestHighLevelClient client = EsClient.getClient();
try {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("aggregations_index02");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
AggregationBuilder aggregationBuild = AggregationBuilders.percentiles("percentiles")
.field("load_time")
.percentiles(75,90,99.9)
.compression(100)
.method(PercentilesMethod.HDR)
.numberOfSignificantValueDigits(3)
;
sourceBuilder.aggregation(aggregationBuild);
sourceBuilder.size(0);
sourceBuilder.query(
QueryBuilders.termQuery("sellerId", 24)
);
searchRequest.source(sourceBuilder);
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(result);
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
Percentile Ranks Aggregation
百分位範圍表示觀察值低於某一值的百分比。例如,如果一個值大於或等於觀察值的95%,那麼它就屬於第95百分位。
假設您的數據包含網站加載時間。您可能有一個服務協議,95%的頁面加載完全在500ms內完成,99%的頁面加載完全在600ms內完成。
示例:
GET latency/_search
{
"size": 0,
"aggs" : {
"load_time_ranks" : { // @1
"percentile_ranks" : { // @2
"field" : "load_time", // @3
"values" : [500, 600] // @4
}
}
}
}
代碼@1:聚合的名稱。
代碼@2:聚合的類型,這裏使用percentile_ranks。
代碼@3:用於聚合的字段。
代碼@5:設置觀察值。
其他的使用與1.7 Percentiles Aggregation類似,就不單獨給出JAVA示例了。
Stats Aggregation
返回的統計信息包括:min、max、sum、count和avg。
其示例如下:
POST /exams/_search?size=0
{
"aggs" : {
"grades_stats" : { "stats" : { "field" : "grade" } }
}
}
對應的返回結果爲:
{
...
"aggregations": {
"grades_stats": {
"count": 2,
"min": 50.0,
"max": 100.0,
"avg": 75.0,
"sum": 150.0
}
}
}
因爲與avg的使用類似,故JAVA示例就不重複給出。
Sum Aggregation
求和聚合。類似於關係型數據庫的sum函數,其使用與avg類似,故只是簡單羅列一下restful的使用方式:
POST /sales/_search?size=0
{
"query" : {
"constant_score" : {
"filter" : {
"match" : { "type" : "hat" }
}
}
},
"aggs" : {
"hat_prices" : { "sum" : { "field" : "price" } }
}
}
value count aggregation
值個數聚合,主要是統計一個字段有多少個不同的值,例如關係型數據庫中一張用戶表user中一個性別字段sex,其取值爲0,1,2,那不管這個表有多少行數據,sex的value count最多爲3。
示例如下:
POST /sales/_search?size=0
{
"aggs" : {
"types_count" : { "value_count" : { "field" : "type" } }
}
}
其響應結果如下:
{
...
"aggregations": {
"types_count": {
"value": 7
}
}
}
median absolute deviation aggregation
中位絕對偏差聚合。由於這部分內容與統計學關係密切,但這並不是我的特長,故對該統計的含義做深入解讀,在實際場景中,我們只需要知道ES提供了中位數偏差統計的功能,如果有這方面的需求,我們知道如何使用ES的中位數統計即可。
官方場景:
假設我們收集了商品評價數據(1星到5星之間的數值)。在實際使用過程中通常會使用平均值來展示商品的整體評價等級。中位絕對偏差聚合可以幫助我們瞭解評審之間的差異有多大。
在這個例子中,我們有一個平均評級爲3星的產品。讓我們看看它的評級的絕對偏差中值,以確定它們的變化有多大。按照我的理解,中位絕對偏差聚合 ,聚合的數據來源於(原始數據 - 所有原始數值的平均值 的絕對值進行聚合)。
例如評論原始數據如下:
1、2、5、5、4、3、5、5、5、5
其平均值:4
那中位數絕對偏差值聚合的數據爲:
3、2、1、1、0、1、1、1、1、1
其Restfull示例如下:
GET reviews/_search
{
"size": 0,
"aggs": {
"review_average": { // @1
"avg": {
"field": "rating"
}
},
"review_variability": { // @2
"median_absolute_deviation": {
"field": "rating"
}
}
}
}
該聚合包含兩部分。
代碼@1:針對字段rating使用AVG進行聚合(平均聚合,求出中位數)
代碼@2:針對字段rating進行中位數絕對偏差聚合。
備註:在es high rest api中未封裝(median absolute deviation aggregation)聚合。
ES 關於 Metric聚合就介紹到這裏了,接下來將重點分析Es Buket聚合。
原文發佈時間爲:2019-03-10
本文作者:丁威,《RocketMQ技術內幕》作者。
本文來自中間件興趣圈,瞭解相關信息可以關注中間件興趣圈。