一起来学ElasticSearch(十)

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 前言目前正在出一个Es专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~承接上文,上节给大家讲的es聚合还有一点内容,本节给大家更完~本文偏实战一些,为了方便演示,本节示例沿用上节索引,好了, 废话不多说直接开整吧~聚合排序我们如何在聚合结果中进行自定义字段排序呢?

前言

目前正在出一个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的相关内容~

本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注鼓励一下呗~

相关文章

项目源码(源码已更新 欢迎star⭐️)

往期并发编程内容推荐

博客(阅读体验较佳)

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)






相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
6月前
|
存储 搜索推荐 关系型数据库
为什么需要 Elasticsearch
为什么需要 Elasticsearch
46 0
|
5月前
|
自然语言处理 网络架构 索引
Elasticsearch7.1之cerebro使用(一)
Elasticsearch7.1之cerebro使用(一)
59 1
|
2月前
|
存储 关系型数据库 MySQL
elasticsearch系列(一)
elasticsearch系列(一)
elasticsearch系列(一)
|
6月前
|
Java 关系型数据库 API
ElasticSearch使用篇
ElasticSearch使用篇
|
存储 关系型数据库 MySQL
Elasticsearch(二)
Elasticsearch(二)
61 0
|
存储 缓存 索引
Elasticsearch(四)
Elasticsearch(四)
59 0
|
缓存 自然语言处理 安全
|
存储 JSON 搜索推荐
Elasticsearch是什么
Elasticsearch是什么
132 0