前言
目前正在出一个Es专题
系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~
承接上文,上节给大家讲的es聚合
还有一点内容,本节给大家更完~
本文偏实战一些,为了方便演示,本节示例沿用上节索引,好了, 废话不多说直接开整吧~
聚合排序
我们如何在聚合结果中进行自定义字段排序呢?
默认排序
之前给大家讲过,默认情况下terms
聚合默认使用doc_count倒序排列
,也可以使用_count
同样代表doc_count
,下面一起看个例子:
GET req_log/_search { "aggs": { "req_count": { "terms": { "field": "path" } } } }
返回:
.....此处省略 "aggregations" : { "req_count" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/3", "doc_count" : 3 }, { "key" : "/api/post/6", "doc_count" : 3 }, { "key" : "/api/post/1", "doc_count" : 2 }, { "key" : "/api/post/2", "doc_count" : 2 }, { "key" : "/api/post/4", "doc_count" : 2 }, { "key" : "/api/post/10", "doc_count" : 1 }, { "key" : "/api/post/12", "doc_count" : 1 }, { "key" : "/api/post/20", "doc_count" : 1 }, { "key" : "/api/post/7", "doc_count" : 1 }, { "key" : "/api/post/8", "doc_count" : 1 } ] } }
看结果可以看到,默认下_count
倒序,如果想升序
怎么操作呢?
GET req_log/_search { "aggs": { "req_count": { "terms": { "field": "path", "order": { "_count": "asc" } } } } }
再看结果:
.... 此处省略 "aggregations" : { "req_count" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/10", "doc_count" : 1 }, { "key" : "/api/post/12", "doc_count" : 1 }, { "key" : "/api/post/20", "doc_count" : 1 }, { "key" : "/api/post/7", "doc_count" : 1 }, { "key" : "/api/post/8", "doc_count" : 1 }, { "key" : "/api/post/1", "doc_count" : 2 }, { "key" : "/api/post/2", "doc_count" : 2 }, { "key" : "/api/post/4", "doc_count" : 2 }, { "key" : "/api/post/3", "doc_count" : 3 }, { "key" : "/api/post/6", "doc_count" : 3 } ] } }
看结果,它是按照_count升序
排序的。当然,这里也可以按照_key
进行排序,来看个例子:
GET req_log/_search { "aggs": { "req_count": { "terms": { "field": "times", "order": { "_key": "asc" } } } } }
结果:
.... "aggregations" : { "req_count" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 6, "buckets" : [ { "key" : 20, "doc_count" : 1 }, { "key" : 30, "doc_count" : 1 }, { "key" : 80, "doc_count" : 2 }, { "key" : 89, "doc_count" : 1 }, { "key" : 120, "doc_count" : 1 }, { "key" : 150, "doc_count" : 1 }, { "key" : 210, "doc_count" : 1 }, { "key" : 270, "doc_count" : 1 }, { "key" : 380, "doc_count" : 1 }, { "key" : 400, "doc_count" : 1 } ] } }
指定了times
字段,_key
按照升序进行排序
同层级自定义排序
那如何进行自定义排序呢?我们依然从层级上来讲,首先给大家说说同层级怎么进行排序,下面看个例子:
假设,有这么一个需求:要求统计所有请求日志中请求耗时最高的api
,怎么做呢?
其实很简单,我们只需要将排序的字段存在聚合的内容按照指定的字段进行排序即可,来看具体操作
GET req_log/_search { "aggs": { "req_total": { "terms": { "field": "path", "order": { "total_times": "desc" } }, "aggs": { "total_times": { "sum": { "field": "times" } } } } } }
结果:
.... "aggregations" : { "req_total" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/8", "doc_count" : 1, "total_times" : { "value" : 9000.0 } }, { "key" : "/api/post/6", "doc_count" : 3, "total_times" : { "value" : 3160.0 } }, { "key" : "/api/post/7", "doc_count" : 1, "total_times" : { "value" : 870.0 } }, { "key" : "/api/post/12", "doc_count" : 1, "total_times" : { "value" : 630.0 } }, { "key" : "/api/post/4", "doc_count" : 2, "total_times" : { "value" : 610.0 } }, { "key" : "/api/post/2", "doc_count" : 2, "total_times" : { "value" : 410.0 } }, { "key" : "/api/post/10", "doc_count" : 1, "total_times" : { "value" : 270.0 } }, { "key" : "/api/post/1", "doc_count" : 2, "total_times" : { "value" : 230.0 } }, { "key" : "/api/post/3", "doc_count" : 3, "total_times" : { "value" : 189.0 } }, { "key" : "/api/post/20", "doc_count" : 1, "total_times" : { "value" : 120.0 } } ] } }
深层级自定义排序
接下来难度加深,假设有这么一个需求:
统计每天请求中为GET
请求,并且按照请求耗时倒序排序,找出每天请求耗时最高的api
需求很短,但理解起来有不少关键点:
- 需要统计每天的结果
- 请求为GET
- 结果按照请求耗时倒序排序
这个怎么做呢?一起来看一下。先添加点数据,以便更好的理解这个例子:
POST req_log/_bulk { "index": {}} { "times" : 180, "method" : "GET", "path" : "/api/post/1", "created" : "2023-02-09" } { "index": {}} { "times" : 120, "method" : "GET", "path" : "/api/post/3", "created" : "2023-02-09" } { "index": {}} { "times" : 140, "method" : "GET", "path" : "/api/post/2", "created" : "2023-02-09" } { "index": {}} { "times" : 130, "method" : "GET", "path" : "/api/post/20", "created" : "2023-02-09" } { "index": {}} { "times" : 60, "method" : "GET", "path" : "/api/post/9", "created" : "2023-02-09" }
下面我们就按照需求,把结果统计出来:
GET req_log/_search { "aggs": { "date": { "date_histogram": { "field": "created", "calendar_interval": "1d", "format": "yyyy-MM-dd" }, "aggs": { "req_path": { "terms": { "field": "path", "order": { "req_method>total_times": "desc" } }, "aggs": { "req_method": { "filter": { "terms": { "method": [ "GET" ] } }, "aggs": { "total_times": { "sum": { "field": "times" } } } } } } } } } }
结果返回:
.... 此处省略 "aggregations" : { "date" : { "buckets" : [ { "key_as_string" : "2023-02-01", "key" : 1675209600000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/6", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 1300.0 } } } ] } }, { "key_as_string" : "2023-02-02", "key" : 1675296000000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/8", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 9000.0 } } } ] } }, { "key_as_string" : "2023-02-03", "key" : 1675382400000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/6", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 960.0 } } } ] } }, { "key_as_string" : "2023-02-04", "key" : 1675468800000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/3", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 80.0 } } } ] } }, { "key_as_string" : "2023-02-05", "key" : 1675555200000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/1", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 150.0 } } } ] } }, { "key_as_string" : "2023-02-06", "key" : 1675641600000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/20", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 120.0 } } } ] } }, { "key_as_string" : "2023-02-07", "key" : 1675728000000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/2", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 30.0 } } } ] } }, { "key_as_string" : "2023-02-08", "key" : 1675814400000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/3", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 20.0 } } } ] } }, { "key_as_string" : "2023-02-09", "key" : 1675900800000, "doc_count" : 6, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/1", "doc_count" : 2, "req_method" : { "doc_count" : 2, "total_times" : { "value" : 260.0 } } }, { "key" : "/api/post/2", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 140.0 } } }, { "key" : "/api/post/20", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 130.0 } } }, { "key" : "/api/post/3", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 120.0 } } }, { "key" : "/api/post/9", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 60.0 } } } ] } }, { "key_as_string" : "2023-02-10", "key" : 1675987200000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/4", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 400.0 } } } ] } }, { "key_as_string" : "2023-02-11", "key" : 1676073600000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/3", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 89.0 } } } ] } }, { "key_as_string" : "2023-02-12", "key" : 1676160000000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/2", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 380.0 } } } ] } }, { "key_as_string" : "2023-02-13", "key" : 1676246400000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/10", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 270.0 } } } ] } }, { "key_as_string" : "2023-02-14", "key" : 1676332800000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/12", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 630.0 } } } ] } }, { "key_as_string" : "2023-02-15", "key" : 1676419200000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/4", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 210.0 } } } ] } }, { "key_as_string" : "2023-02-16", "key" : 1676505600000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/6", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 900.0 } } } ] } }, { "key_as_string" : "2023-02-17", "key" : 1676592000000, "doc_count" : 1, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 0, "buckets" : [ { "key" : "/api/post/7", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 870.0 } } } ] } } ] } }
从结果来看,可以看出是按照日期
每天进行统计的,除了9号
其它的数据都是一条,所以我们之前插了一些9号
的数据,我们重点看9号
的数据,里边的数据是按照请求耗时倒序
排序的。
查询
语句看似复杂,其实拆解开很简单,其实就是将上节讲的深层聚合
,加个排序,如果还不熟悉的小伙伴,建议上节温习
一下。说一下几个关键词:
date_histogram
, 日期表达式,允许我们聚合以时间
为单位, 所以calendar_interval
就是时间单位,支持分,时,天,周,月,季度,年
"date_histogram": { "field": "created", "calendar_interval": "1d", "format": "yyyy-MM-dd" },
req_method>total_times
这个>
大家可以简单理解为类似css
选择器的>
,我们可以通过它将结果进行链接
size结合使用
假设,需求又变动了,嫌数据太多,我只想看到想要的数据,在原有的基础上,返回每天请求耗时最多的前两条数据,怎么做? 这是一个比较常见的需求
我们可以通过指定size
,这个其实前几节都给大家讲过,下面一起看下吧~
GET req_log/_search { "aggs": { "date": { "date_histogram": { "field": "created", "calendar_interval": "1d", "format": "yyyy-MM-dd" }, "aggs": { "req_path": { "terms": { "field": "path", "order": { "req_method>total_times": "desc" }, "size": 2 }, "aggs": { "req_method": { "filter": { "terms": { "method": [ "GET" ] } }, "aggs": { "total_times": { "sum": { "field": "times" } } } } } } } } } }
返回:
...省略 { "key_as_string" : "2023-02-09", "key" : 1675900800000, "doc_count" : 6, "req_path" : { "doc_count_error_upper_bound" : 0, "sum_other_doc_count" : 3, "buckets" : [ { "key" : "/api/post/1", "doc_count" : 2, "req_method" : { "doc_count" : 2, "total_times" : { "value" : 260.0 } } }, { "key" : "/api/post/2", "doc_count" : 1, "req_method" : { "doc_count" : 1, "total_times" : { "value" : 140.0 } } } ] } } ...省略
可以看出9号
的数据只返回了两条,是不是很简单~
去重
在es
中如何进行去重呢? 下面一起看下
cardinality & 去重统计
es聚合
中使用cardinality
来做去重操作,去重结果可能并不是很准确,但是可以保证极小的内存消耗
和极高的响应效率
下面看个例子:
GET req_log/_search { "aggs": { "path_num": { "cardinality": { "field": "path", "precision_threshold": 100 } } } }
结果返回:
"aggregations" : { "path_num" : { "value" : 11 } }
从结果得出,一共存在11个api
precision_threshold
代表的是精度,接受的范围是0–40,000
percentiles & 百分比统计
有时候,我们需要统计百分比,那么在es中如何进行操作呢? 可以使用latency_percentiles
来进行统计,来看个例子
GET req_log/_search { "aggs": { "latency_percentiles": { "percentiles": { "field": "times", "percents": [ 30, 40, 50, 60, 70, 80, 99 ] } } } }
结果:
"aggregations" : { "latency_percentiles" : { "values" : { "30.0" : 120.0, "40.0" : 133.0, "50.0" : 165.0, "60.0" : 251.99999999999994, "70.0" : 398.0, "80.0" : 873.0, "99.0" : 9000.0 } } }
大家第一眼看到这个结果可能有点懵,我们看到结果返回了我们之前指定的百分比
,percents
里边就是指定百分比
的,意思大概这样:
- 在所有请求中有
30%
的请求耗时达到了120
其它以此类推,有时候老板让我们统计在所有订单中,愿意付费的用户大概是多少,在付费用户中,付款金额的指标是多少,是不是就会统计了~
percentile_ranks & 百分比统计(反向)
为什么说是反向呢?假设,有这么一个需求,我想统计请求耗时达到80, 120,600
的请求大概占比多少?这也是一个很常见的反向需求,就像平时老板问你,购买黄金vip,白银vip,铂金vip
的用户占比多少。
接着看刚刚的需求:
GET req_log/_search { "aggs": { "load": { "percentile_ranks": { "field": "times", "values": [ 80, 120, 600 ] } } } }
返回:
... "aggregations" : { "load" : { "values" : { "80.0" : 18.181818181818183, "120.0" : 31.818181818181817, "600.0" : 74.37137330754351 } } } ....
从结果来看,请求耗时达到80
的占比18%
其它依次类推,对比刚刚的percentiles
是不是它的百分比在后边,为了方便理解,所以叫反向
结束语
本节到此就结束了,大家一定要学会举一反三,可以给自己出一些常见的场景需求,结合前面学的内容巩固一下,不用去背查询语句,理解了就可以。
下节我们就正式进入SpringBoot框架整合ES
的相关内容~
本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注
鼓励一下呗~
相关文章
- 利用docker搭建es集群
- 一起来学ElasticSearch(一)
- 一起来学ElasticSearch(二)
- 一起来学ElasticSearch(三)
- 一起来学ElasticSearch(四)
- 一起来学ElasticSearch(五)
- 一起来学ElasticSearch(六)
- 一起来学ElasticSearch(七)
- 一起来学ElasticSearch(八)
- 一起来学ElasticSearch(九)
项目源码(源码已更新 欢迎star⭐️)
- spring-cloud-all
- SpringCloud整合 Oauth2+Gateway+Jwt+Nacos 实现授权码模式的服务认证(一)
- SpringCloud整合 Oauth2+Gateway+Jwt+Nacos 实现授权码模式的服务认证(二)
往期并发编程内容推荐
- Java多线程专题之线程与进程概述
- Java多线程专题之线程类和接口入门
- Java多线程专题之进阶学习Thread(含源码分析)
- Java多线程专题之Callable、Future与FutureTask(含源码分析)
- 面试官: 有了解过线程组和线程优先级吗
- 面试官: 说一下线程的生命周期过程
- 面试官: 说一下线程间的通信
- 面试官: 说一下Java的共享内存模型
- 面试官: 有了解过指令重排吗,什么是happens-before
- 面试官: 有了解过volatile关键字吗 说说看
- 面试官: 有了解过Synchronized吗 说说看
- Java多线程专题之Lock锁的使用
- 面试官: 有了解过ReentrantLock的底层实现吗?说说看
- 面试官: 有了解过CAS和原子操作吗?说说看
- Java多线程专题之线程池的基本使用
- 面试官: 有了解过线程池的工作原理吗?说说看
- 面试官: 线程池是如何做到线程复用的?有了解过吗,说说看
- 面试官: 阻塞队列有了解过吗?说说看
- 面试官: 阻塞队列的底层实现有了解过吗? 说说看
- 面试官: 同步容器和并发容器有用过吗? 说说看
- 面试官: CopyOnWrite容器有了解过吗? 说说看
- 面试官: Semaphore在项目中有使用过吗?说说看(源码剖析)
- 面试官: Exchanger在项目中有使用过吗?说说看(源码剖析)
- 面试官: CountDownLatch有了解过吗?说说看(源码剖析)
- 面试官: CyclicBarrier有了解过吗?说说看(源码剖析)
- 面试官: Phaser有了解过吗?说说看
- 面试官: Fork/Join 有了解过吗?说说看(含源码分析)
- 面试官: Stream并行流有了解过吗?说说看