Elasticsearch搜索辅助功能解析(十)

这篇具有很好参考价值的文章主要介绍了Elasticsearch搜索辅助功能解析(十)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        ES提供的各种搜索辅助功能。例如,为优化搜索性能,需要指定搜索结果返回一部分字段内容。为了更好地呈现结果,需要用到结果计数和分页功能;当遇到性能瓶颈时,需要剖析搜索各个环节的耗时;面对不符合预期的搜索结果时,需要分析各个文档的评分细节。

指定返回的字段

        考虑到性能问题,需要对搜索结果进行“瘦身”——指定返回的字段。在ES中,通过_source子句可以设定返回结果的字段。_source指向一个JSON数组,数组中的元素是希望返回的字段名称。

PUT /hotel
{
  "mappings": {
    "properties": {
      "title":{"type": "text"},
      "city":{"type": "keyword"},
      "price":{"type": "double"},
      "create_time":{"type": "date","format": "yyyy-MM-dd HH:mm:ss"},
      "attachment":{"type": "text"},
      "full_room":{"type": "boolean"},
      "location":{"type": "geo_point"},
      "praise":{"type": "integer"}
    }
  }
}

向hotel新增数据:

POST /_bulk
{"index":{"_index":"hotel","_id":"001"}}
{"title":"java旅馆","city":"深圳","price":50.00,"create_time":"2022-08-05 00:00:00","location":{"lat":40.012312,"lon":116.497122},"praise":10}
{"index":{"_index":"hotel","_id":"002"}}
{"title":"python旅馆","city":"北京","price":50.00,"create_time":"2022-08-05 00:00:00","location":{"lat":40.012312,"lon":116.497122},"praise":10}
{"index":{"_index":"hotel","_id":"003"}}
{"title":"go旅馆","city":"上海","price":50.00,"create_time":"2022-08-05 00:00:00","location":{"lat":40.012312,"lon":116.497122},"praise":10}
{"index":{"_index":"hotel","_id":"004"}}
{"title":"C++旅馆","city":"广州","price":50.00,"create_time":"2022-08-05 00:00:00","location":{"lat":40.012312,"lon":116.497122},"praise":10}

下面的DSL指定搜索结果只返回title和city字段:

POST /hotel/_search
{
  "_source": ["title","city"],
  "query": {
    "term": {
      "city": {
        "value": "深圳"
      }
    }
  }
}

返回结果:

{
  "took" : 361,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.2039728,
    "hits" : [
      {
        "_index" : "hotel",
        "_type" : "_doc",
        "_id" : "001",
        "_score" : 1.2039728,
        "_source" : {
          "city" : "深圳",
          "title" : "java旅馆"
        }
      }
    ]
  }
}

在上述搜索结果中,每个命中文档的_source结构体中只包含指定的city和title两个字段的数据。

        在Java客户端中,通过调用searchSourceBuilder.fetchSource()方法可以设定搜索返回的字段,该方法接收两个参数,即需要的字段数组和不需要的字段数组。以下代码片段将和上面的DSL呈现相同的效果:

    @Test
    public void testQueryNeedFields() throws IOException {
        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(Arrays.stream("127.0.0.1:9200".split(","))
                .map(host->{
                    String[] split = host.split(":");
                    String hostName = split[0];
                    int port = Integer.parseInt(split[1]);
                    return new HttpHost(hostName,port,HttpHost.DEFAULT_SCHEME_NAME);
                }).filter(Objects::nonNull).toArray(HttpHost[]::new)));
        SearchRequest request = new SearchRequest("hotel");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(new TermQueryBuilder("city","深圳"));
        sourceBuilder.fetchSource(new String[]{"title","city"},null);
        request.source(sourceBuilder);
        SearchResponse search = client.search(request, RequestOptions.DEFAULT);
        System.out.println(search.getHits());
    }

结果计数

为提升搜索体验,需要给前端传递搜索匹配结果的文档条数,即需要对搜索结果进行计数。针对这个要求,ES提供了_count API功能,在该API中,用户提供query子句用于结果匹配,ES会返回匹配的文档条数。下面的DSL将返回城市为“北京”的旅馆个数:

POST /hotel/_count
{
  "query": {
    "term": {
      "city": {
        "value": "北京"
      }
    }
  }
}

执行上述DSL后,返回信息如下:

{
  "count" : 1,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  }
}

由结果可知,ES不仅返回了匹配的文档数量(值为1),并且还返回了和分片相关的元数据,如总共扫描的分片个数,以及成功、失败、跳过的分片个数等。

        在Java客户端中,通过CountRequest执行_count API,然后调用CountRequest对象的source()方法设置查询逻辑。countRequest.source()方法返回CountResponse对象,通过countResponse.getCount()方法可以得到匹配的文档条数。以下代码将和上面的DSL呈现相同的效果:

    @Test
    public void testCount() throws IOException {
        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(Arrays.stream("127.0.0.1:9200".split(","))
                .map(host->{
                    String[] split = host.split(":");
                    String hostName = split[0];
                    int port = Integer.parseInt(split[1]);
                    return new HttpHost(hostName,port,HttpHost.DEFAULT_SCHEME_NAME);
                }).filter(Objects::nonNull).toArray(HttpHost[]::new)));
        CountRequest countRequest = new CountRequest("hotel");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(new TermQueryBuilder("city","深圳"));
        countRequest.source(sourceBuilder);
        CountResponse response = client.count(countRequest,RequestOptions.DEFAULT);
        System.out.println(response.getCount());
    }

结果分页

        在实际的搜索应用中,分页是必不可少的功能。在默认情况下,ES返回前10个搜索匹配的文档。用户可以通过设置from和size来定义搜索位置和每页显示的文档数量,from表示查询结果的起始下标,默认值为0,size表示从起始下标开始返回的文档个数,默认值为10。下面的DSL将返回下标从0开始的20个结果。

GET /hotel/_search
{
  "from":0, //设置搜索起始位置
  "size": 2,//设置搜索返回的文档个数
  "query": {
    "term": {
      "city": {
        "value": "深圳"
      }
    }
  }
}

在默认情况下,用户最多可以取得10 000个文档,即from为0时,size参数最大为10 000,如果请求超过该值,ES返回如下报错信息:

{
  "error" : {
    "root_cause" : [
      {
        "type" : "illegal_argument_exception",
        "reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
      }
    ],
    "type" : "search_phase_execution_exception",
    "reason" : "all shards failed",
    "phase" : "query",
    "grouped" : true,
    "failed_shards" : [
      {
        "shard" : 0,
        "index" : "hotel",
        "node" : "tiANekxXS_GtirH4DamrFA",
        "reason" : {
          "type" : "illegal_argument_exception",
          "reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
        }
      }
    ],
    "caused_by" : {
      "type" : "illegal_argument_exception",
      "reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting.",
      "caused_by" : {
        "type" : "illegal_argument_exception",
        "reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10001]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
      }
    }
  },
  "status" : 400
}

对于普通的搜索应用来说,size设为10 000已经足够用了。如果确实需要返回多于10 000条的数据,可以适当修改max_result_window的值。以下示例将hotel索引的最大窗口值修改为了20 000。

PUT /hotel/_settings
{
  "index":{
    "max_result_window":20000
  }
}

注意,如果将配置修改得很大,一定要有足够强大的硬件作为支撑。

        作为一个分布式搜索引擎,一个ES索引的数据分布在多个分片中,而这些分片又分配在不同的节点上。一个带有分页的搜索请求往往会跨越多个分片,每个分片必须在内存中构建一个长度为from+size的、按照得分排序的有序队列,用以存储命中的文档。然后这些分片对应的队列数据都会传递给协调节点,协调节点将各个队列的数据进行汇总,需要提供一个长度为number_of_shards*(from+size)的队列用以进行全局排序,然后再按照用户的请求从from位置开始查找,找到size个文档后进行返回。

        基于上述原理,ES不适合深翻页。什么是深翻页呢?简而言之就是请求的from值很大。假设在一个3个分片的索引中进行搜索请求,参数from和size的值分别为1000和10,其响应过程如下图所示。

elasticsearch fetchsource,elasticsearch,java,elasticsearch,大数据,搜索引擎

         当深翻页的请求过多时会增加各个分片所在节点的内存和CPU消耗。尤其是协调节点,随着页码的增加和并发请求的增多,该节点需要对这些请求涉及的分片数据进行汇总和排序,过多的数据会导致协调节点资源耗尽而停止服务。

        作为搜索引擎,ES更适合的场景是对数据进行搜索,而不是进行大规模的数据遍历。一般情况下,只需要返回前1000条数据即可,没有必要取到10 000条数据。如果确实有大规模数据遍历的需求,可以参考使用scroll模式或者考虑使用其他的存储引擎。

        在Java客户端中,可以调用SearchSourceBuilder的from()和size()方法来设定from和size参数。以下代码片段将from和size的值分别设置为20和10。

    @Test
    public void testQueryByPage() throws IOException {
        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(Arrays.stream("127.0.0.1:9200".split(","))
                .map(host->{
                    String[] split = host.split(":");
                    String hostName = split[0];
                    int port = Integer.parseInt(split[1]);
                    return new HttpHost(hostName,port,HttpHost.DEFAULT_SCHEME_NAME);
                }).filter(Objects::nonNull).toArray(HttpHost[]::new)));
        SearchRequest request = new SearchRequest("hotel");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder.query(new TermQueryBuilder("city","深圳"));
        searchSourceBuilder.from(20);
        searchSourceBuilder.size(10);
        request.source(searchSourceBuilder);
        client.search(request,RequestOptions.DEFAULT);
    }

性能分析

        在使用ES的过程中,有的搜索请求的响应可能比较慢,其中大部分的原因是DSL的执行逻辑有问题。ES提供了profile功能,该功能详细地列出了搜索时每一个步骤的耗时,可以帮助用户对DSL的性能进行剖析。开启profile功能只需要在一个正常的搜索请求的DSL中添加"profile":"true"即可。以下查询将开启profile功能:

GET /hotel/_search
{
  "profile": true,
  "query": {
    "match": {
      "title": "北京"
    }
  }
}

执行以上DSL后ES返回了一段比较冗长的信息:

{
  "took" : 60,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "profile" : {
    "shards" : [
      {
        "id" : "[tiANekxXS_GtirH4DamrFA][hotel][0]",
        "searches" : [
          {
            "query" : [
              {
                "type" : "BooleanQuery",
                "description" : "title:北 title:京",
                "time_in_nanos" : 1032417,
                "breakdown" : {
                  "set_min_competitive_score_count" : 0,
                  "match_count" : 0,
                  "shallow_advance_count" : 0,
                  "set_min_competitive_score" : 0,
                  "next_doc" : 0,
                  "match" : 0,
                  "next_doc_count" : 0,
                  "score_count" : 0,
                  "compute_max_score_count" : 0,
                  "compute_max_score" : 0,
                  "advance" : 0,
                  "advance_count" : 0,
                  "score" : 0,
                  "build_scorer_count" : 1,
                  "create_weight" : 1023459,
                  "shallow_advance" : 0,
                  "create_weight_count" : 1,
                  "build_scorer" : 8958
                },
                "children" : [
                  {
                    "type" : "TermQuery",
                    "description" : "title:北",
                    "time_in_nanos" : 182334,
                    "breakdown" : {
                      "set_min_competitive_score_count" : 0,
                      "match_count" : 0,
                      "shallow_advance_count" : 0,
                      "set_min_competitive_score" : 0,
                      "next_doc" : 0,
                      "match" : 0,
                      "next_doc_count" : 0,
                      "score_count" : 0,
                      "compute_max_score_count" : 0,
                      "compute_max_score" : 0,
                      "advance" : 0,
                      "advance_count" : 0,
                      "score" : 0,
                      "build_scorer_count" : 1,
                      "create_weight" : 179167,
                      "shallow_advance" : 0,
                      "create_weight_count" : 1,
                      "build_scorer" : 3167
                    }
                  },
                  {
                    "type" : "TermQuery",
                    "description" : "title:京",
                    "time_in_nanos" : 15167,
                    "breakdown" : {
                      "set_min_competitive_score_count" : 0,
                      "match_count" : 0,
                      "shallow_advance_count" : 0,
                      "set_min_competitive_score" : 0,
                      "next_doc" : 0,
                      "match" : 0,
                      "next_doc_count" : 0,
                      "score_count" : 0,
                      "compute_max_score_count" : 0,
                      "compute_max_score" : 0,
                      "advance" : 0,
                      "advance_count" : 0,
                      "score" : 0,
                      "build_scorer_count" : 1,
                      "create_weight" : 14792,
                      "shallow_advance" : 0,
                      "create_weight_count" : 1,
                      "build_scorer" : 375
                    }
                  }
                ]
              }
            ],
            "rewrite_time" : 183625,
            "collector" : [
              {
                "name" : "SimpleTopScoreDocCollector",
                "reason" : "search_top_hits",
                "time_in_nanos" : 32000
              }
            ]
          }
        ],
        "aggregations" : [ ]
      }
    ]
  }
}

        如上所示,在带有profile的返回信息中,除了包含搜索结果外,还包含profile子句,在该子句中展示了搜索过程中各个环节的名称及耗时情况。需要注意的是,使用profile功能是有资源损耗的,建议用户只在前期调试的时候使用该功能,在生产中不要开启profile功能。

        因为一个搜索可能会跨越多个分片,所以使用shards数组放在profile子句中。每个shard子句中包含3个元素,分别是id、searches和aggregations。

  1. id表示分片的唯一标识,它的组成形式为[nodeID][indexName][shardID]。
  2. searches以数组的形式存在,因为有的搜索请求会跨多个索引进行搜索。每一个search子元素即为在同一个索引中的子查询,此处不仅返回了该search子元素耗时的信息,而且还返回了搜索“北京”的详细策略,即被拆分成“title:北”和“title:京”两个子查询。同理,children子元素给出了“title:北”“title:京”的耗时和详细搜索步骤的耗时,此处不再赘述。
  3. aggregations只有在进行聚合运算时才有内容

        上面只是一个很简单的例子,如果查询比较复杂或者命中的分片比较多,profile返回的信息将特别冗长。在这种情况下,用户进行性能剖析的效率将非常低。为此,Kibana提供了可视化的profile功能,该功能建立在ES的profile功能基础上。在Kibana的Dev Tools界面中单击Search Profiler链接,就可以使用可视化的profile了,其区域布局如下图所示:

elasticsearch fetchsource,elasticsearch,java,elasticsearch,大数据,搜索引擎

评分分析

        在使用搜索引擎时,一般都会涉及排序功能。如果用户不指定按照某个字段进行升序或者降序排列,那么ES会使用自己的打分算法对文档进行排序。有时我们需要知道某个文档具体的打分详情,以便于对搜索DSL问题展开排查。ES提供了explain功能来帮助使用者查看搜索时的匹配详情。explain的使用形式如下:

GET /${index_name}/_explain/${doc_id}
{
  "query":{
    ...
  }
}

以下示例为按照标题进行搜索的explain查询请求:

GET /hotel/_explain/002
{
  "query":{
    "match": {
      "title": "python"
    }
  }
}

 执行上述explain查询请求后,ES返回的信息如下:

{
  "_index" : "hotel",
  "_type" : "_doc",
  "_id" : "002",
  "matched" : true,
  "explanation" : {
    "value" : 1.2039728,
    "description" : "weight(title:python in 1) [PerFieldSimilarity], result of:",
    "details" : [
      {
        "value" : 1.2039728,
        "description" : "score(freq=1.0), computed as boost * idf * tf from:",
        "details" : [
          {
            "value" : 2.2,
            "description" : "boost",
            "details" : [ ]
          },
          {
            "value" : 1.2039728,
            "description" : "idf, computed as log(1 + (N - n + 0.5) / (n + 0.5)) from:",
            "details" : [
              {
                "value" : 1,
                "description" : "n, number of documents containing term",
                "details" : [ ]
              },
              {
                "value" : 4,
                "description" : "N, total number of documents with field",
                "details" : [ ]
              }
            ]
          },
          {
            "value" : 0.45454544,
            "description" : "tf, computed as freq / (freq + k1 * (1 - b + b * dl / avgdl)) from:",
            "details" : [
              {
                "value" : 1.0,
                "description" : "freq, occurrences of term within document",
                "details" : [ ]
              },
              {
                "value" : 1.2,
                "description" : "k1, term saturation parameter",
                "details" : [ ]
              },
              {
                "value" : 0.75,
                "description" : "b, length normalization parameter",
                "details" : [ ]
              },
              {
                "value" : 3.0,
                "description" : "dl, length of field",
                "details" : [ ]
              },
              {
                "value" : 3.0,
                "description" : "avgdl, average length of field",
                "details" : [ ]
              }
            ]
          }
        ]
      }
    ]
  }
}

可以看到,explain返回的信息比较全面。

另外,如果一个文档和查询不匹配,explain也会直接返回信息告知用户,具体如下:文章来源地址https://www.toymoban.com/news/detail-537630.html

{
  "_index" : "hotel",
  "_type" : "_doc",
  "_id" : "001",
  "matched" : false,
  "explanation" : {
    "value" : 0.0,
    "description" : "no matching term",
    "details" : [ ]
  }
}

到了这里,关于Elasticsearch搜索辅助功能解析(十)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Elasticsearch搜索功能的实现(五)-- 实战

    实战环境 elastic search 8.5.0 + kibna 8.5.0 + springboot 3.0.2 + spring data elasticsearch 5.0.2 + jdk 17 实现效果图片: 实际执行的DSL语句: 注意: 当指定排序条件时 _score 会被置空 加权前效果: 加权后效果: DSL 语句:

    2023年04月18日
    浏览(34)
  • Elasticsearch的地理位置搜索与功能

    地理位置搜索是一种非常重要的搜索功能,它可以根据用户的位置信息来提供相关的搜索结果。在现代的互联网和移动应用中,地理位置搜索已经成为一种基本的功能需求。Elasticsearch是一个强大的搜索引擎,它提供了一套完善的地理位置搜索功能。在本文中,我们将深入探讨

    2024年02月21日
    浏览(39)
  • SpringCloud实用篇6——elasticsearch搜索功能

    elasticsearch的查询依然是基于JSON风格的DSL来实现的。 Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括: 查询所有 :查询出所有数据,一般测试用。例如: match_all 全文检索(full text)查询 :利用分词器对用户输入内容分词,然后去倒排索

    2024年02月12日
    浏览(32)
  • 【ElasticSearch和whoosh实现项目中搜索功能】

    说明: 我们的项目中经常会遇到搜索的功能,最近也写过搜索的功能,用具体的python项目来实现 一.单表搜索 实现对于特定表中的某些字段的模糊搜索匹配 通过用orm查询操作来实现简单真的搜索,虽然比较简单方便但是效率不高,遇到大数据量的就会非常的吃力。 二,全局

    2023年04月09日
    浏览(54)
  • Elasticsearch:在 Elasticsearch 中使用 NLP 和向量搜索增强聊天机器人功能

    作者:Priscilla Parodi 会话界面已经存在了一段时间,并且作为协助各种任务(例如客户服务、信息检索和任务自动化)的一种方式而变得越来越流行。 通常通过语音助手或消息应用程序访问,这些界面模拟人类对话,以帮助用户更有效地解决他们的查询。 随着技术的进步,聊

    2024年02月07日
    浏览(48)
  • Elasticsearch 分布式全文搜索引擎原理解析

    作者:禅与计算机程序设计艺术 Elasticsearch是一个开源的分布式全文搜索引擎,它可以近实时地存储、检索数据。本系列文章将从以下几个方面对Elasticsearch进行深入分析: Elasticsearch的主要组成部分 索引、类型和映射(Mapping) 搜索请求处理流程 查询缓存机制 Elasticsearch集群

    2024年02月05日
    浏览(49)
  • Elasticsearch(十二)搜索---搜索匹配功能③--布尔查询及filter查询原理

    本节主要学习ES匹配查询中的布尔查询以及布尔查询中比较特殊的filter查询及其原理。 复合搜索,顾名思义是一种在一个搜索语句中包含一种或多种搜索子句的搜索。 布尔查询是常用的复合查询,它把多个子查询组合成一个布尔表达式,这些子查询之间的逻辑关系是\\\"与\\\",即

    2024年02月04日
    浏览(50)
  • Elasticsearch(十一)搜索---搜索匹配功能②--range查询和exists查询

    继上一节学习了ES的搜索的查询全部和term搜索后,此节将把搜索匹配功能剩余的2个学习完,分别是range搜索和exists搜索 range查询用于范围查询,一般是对数值型和日期型数据的查询。使用range进行范围查询时,用户可以按照需求中是否包含边界数值进行选项设置,可供组合的

    2024年02月09日
    浏览(49)
  • 分布式搜索引擎elasticsearch搜索功能介绍及实际案例剖析

    1.1.1 DSLQuery的分类 Elasticsearch提供了基于JSON的DSL(Domain Specific  Language)来定义查询。常见的查询类型包括: 查询所有:查询出所有数据,一般测试用。例如:match_all 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如: match_query mu

    2024年02月20日
    浏览(41)
  • [Spring Boot]12 ElasticSearch实现分词搜索功能

    我们在使用搜索功能的时候,有时,为了使搜索的结果更多更广,比如搜索字符串“领导力”,希望有这些组合的结果(领导力、领导、领、导、力)都要能够全部展示出来。 这里我们引入ElasticSearch结合分词插件,来实现这样的搜索功能。 比如:一款app需要对“课程”进行

    2024年02月03日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包