ES学习记录9.2——请求体搜索(Search Type和Scroll)

版权声明:发扬开源精神,欢迎大家转载对自己有用的文章(●'◡'●) https://blog.csdn.net/jacksonary/article/details/84036053

9.1 搜索类型(Search Type)

 ES执行分布式搜索时可以执行不同的执行路径,分布式搜索需要将搜分散到所有相关的碎片shards,然后收集所有结果。在执行分散/聚集类型操作时,有几种方法可以执行此操作,特别是使用搜索引擎。当执行分布式搜索时,有一个问题是从每个分片上搜索多少结果,例如,如果我们有10个分片,则第一个分片可能保持从0到10的最相关结果,其他分片结果排在其下方,因此,当执行搜索时,我们需要从所有碎片上获取0到10的结果,然后进行排序,如果想确保正确的结果,则返回结果。另一个与搜索引擎相关的问题是,每个碎片都独立存在。当具体的碎片执行搜索时,它不会考虑来自其他碎片的术语频率和其他搜索引擎信息。如果想要准确的排序,就需要首先从所有碎片上搜集术语term的频率并计算全局术语频率,然后使用这些全局频率在每个碎片上执行查询。由于需要对结果进行排序、获取大型文档集、甚至滚动它,同时保持正确的排序行为,这样的可能会耗费资源。对于大型结果集的滚动,如果返回文档的顺序不重要,最好按_doc排序。ES是非常灵活的并控制基于每个搜索请求执行的搜索类型,在查询参数中可以通过search_type参数配置搜索的类型。搜索类型主要有:

  • Query Then Fetch(查询再拉取):参数为query_then_fetch。这个类型是分为两步完成请求的:第一步,查询将转发到所有涉及的碎片,每个碎片执行搜索、生成本地排序后的结果列表,每个碎片都向协调节点返回足够的信息,以允许它合并并将碎片级别结果重新排序为具有最大长度大小的全局排序结果集;第二步,协调节点仅从相关碎片上请求文档内容(以及突出显示的片段,如果有的话)。如果不特别指定search_type,那默认就是query_then_fetch
  • DFS query then fetch:参数为dfs_query_then_fetch,与“查询然后拉取”相同,但是比它多了初始分散阶段,其进行并计算分布式术语频率以获得更准确的评分;

9.2 滚动(Scroll)

 当search请求返回结果的单个页面结果,scroll接口可以用来从一个搜索请求中取回大量(设置是所有)的结果,与在传统数据库中使用游标的方式大致相同。滚动不适用于用户的实时请求,而是用于处理大量数据,比如为了将一个索引的内容重新索引到具有不同配置的新索引中。从滚动请求返回的结果反映了初始搜索请求生成时的索引状态,如时间快照。对文档的后续更改(索引,更新或删除)只会影响以后的搜索请求。如果想要使用滚动,初始搜索请求应该在查询参数中指定scroll参数,这个参数告诉ES它应该保持“搜索上下文”存活多久,比如?scroll=1m

curl -X POST "localhost:9200/twitter/_search?q=*&scroll=1m" -H 'Content-Type: application/json' -d'
{
    "size": 100,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
'

size参数用于控制每个批次返回命中结果的最大数量结果,返回的结果为:

{
    "_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAABCFmJLZUdDLVEtU1h1eXlHbGNhckRyTWcAAAAAAAAAQxZiS2VHQy1RLVNYdXl5R2xjYXJEck1nAAAAAAAAAEQWYktlR0MtUS1TWHV5eUdsY2FyRHJNZwAAAAAAAABFFmJLZUdDLVEtU1h1eXlHbGNhckRyTWcAAAAAAAAARhZiS2VHQy1RLVNYdXl5R2xjYXJEck1n",
    "took": 1,
    "timed_out": false,
    "_shards": {
        ...
    },
    "hits": {
        ...
}

上述返回的结果包含一个_scroll_id字段,显示了前100条记录,那要显示第101-200条记录时应该将其传递给滚动API以便检索下一批结果,如下:

// POST和GET方法都可以使用
curl -X POST "localhost:9200/_search/scroll" -H 'Content-Type: application/json' -d'
{
    "scroll" : "1m",
    "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAABCFmJLZUdDLVEtU1h1eXlHbGNhckRyTWcAAAAAAAAAQxZiS2VHQy1RLVNYdXl5R2xjYXJEck1nAAAAAAAAAEQWYktlR0MtUS1TWHV5eUdsY2FyRHJNZwAAAAAAAABFFmJLZUdDLVEtU1h1eXlHbGNhckRyTWcAAAAAAAAARhZiS2VHQy1RLVNYdXl5R2xjYXJEck1n"
}
'

对上述获取第101-200条记录的请求作几点说明:

  • GETPOST请求都可以使用;
  • 请求URL不应该再出现索引名(这里即twitter),因为它是在原始search请求中指定的;
  • scroll参数告诉ES将搜索的上下再保持1分钟;
  • scroll_id参数用于识别之前的search请求结果上下文以便滚动;

注意:

  • 初始搜索请求和每个后续滚动请求均会返回_scroll_id,虽然_scroll_id可能会在请求之间发生变化,但并不总是会发生变化,反正在任何情况下,都是应该使用最近返回的_scroll_id字段;
  • 如果请求中指定了某种聚合方式,那只有最初的搜索响应会包含聚合结果,后续批次的结果是不包含聚合结果的;
  • 当排序方式是_doc,ES会对滚动会进行优化提高执行速度,如果想叠代所有的文档且对排序没有要求,那使用_doc排序是最高效的方式,如:
curl -X GET "localhost:9200/_search?scroll=1m" -H 'Content-Type: application/json' -d'
{
  "sort": [
    "_doc"
  ]
}
'

滚动参数scroll(传递给搜索请求和每个滚动请求)告诉ES应该保持搜索上下文存活多长时间,这个时间不需要足够长到处理搜索数据,只需要时间足够处理结果的前一批数据,每个滚动请求(使用滚动参数scroll)设置新的到期时间(使用scroll参数),如果滚动请求未传递滚动参数,则搜索上下文将作为该滚动请求的一部分被释放。(疑问:这里有一个疑问,如果使用scroll参数是设置前一批数据上下文标识符的_scroll_id存活时间,那初次请求中传入的scroll=1m有什么意义呢?)。通常,后台合并过程通过将较小的段合并在一起来创建新的较大段,从而优化索引,此时删除较小的段。此过程在滚动期间继续,但打开的搜索上下文可防止旧片段在仍在使用时被删除。这就是Elasticsearch能够返回初始搜索请求的结果,无论后续对文档的更改如何。检查有多少搜索上下文处于打开的状态可以使用下面的API:

curl -X GET "localhost:9200/_nodes/stats/indices/search"

搜索上下文会在超出scroll设置的时间后自动移除,虽然超时会自动移除上下文,但是保持滚动状态为打开会有额外的开销,和之前的说的一样,一旦使用clear-scroll接口就不再使用滚动,此时就会显式清除滚动:

curl -X DELETE "localhost:9200/_search/scroll" -H 'Content-Type: application/json' -d'
{
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
'

如果想一次移除多个滚动,可以使用数组传入:

curl -X DELETE "localhost:9200/_search/scroll" -H 'Content-Type: application/json' -d'
{
    "scroll_id" : [
      "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
      "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
    ]
}
'

如果想直接移除所有的搜索上下文,可以使用_all参数,如下:

curl -X DELETE "localhost:9200/_search/scroll/_all"

当然移除滚动也可以直接在URL中传入请求(如果有多个滚动,需要使用逗号分隔),如下:

curl -X DELETE "localhost:9200/_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"

滚动切片

对于返回大量文档的滚动查询,可以将滚动分割为多个切片,这样可以单独使用,如:

curl -X GET "localhost:9200/twitter/_search?scroll=1m" -H 'Content-Type: application/json' -d'
{
    "slice": {
        // 设置切片的id
        "id": 0,
        // 设置切片的最大数目
        "max": 2
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
'

第一个请求的结果返回属于第一个切片(id:0)的文档,第二个请求的结果返回属于第二个切片的文档。由于最大切片数设置为2,因此两个请求的结果的并集等效于没有切片的滚动查询的结果。默认情况下,首先在分片上完成分割,然后在每个分片上使用_uid字段在以下公式上进行分割:slice(doc)= floorMod(hashCode(doc._uid),max),例如,如果分片shards数为2,用户请求4个切片slices,然后将切片0和2分配给第一个碎片,并将切片1和3分配给第二个碎片。每个滚动都是独立的,可以像任何滚动请求一样并行处理。如果切片slice的数量大于碎片shards的数量,则切片过滤器在第一次调用时会非常慢,它具有O(N)的复杂度,并且存储器成本等于每个切片的 N bits(其中N是碎片shards中文档的总数)。在几次调用之后,过滤器被缓存之后,后续调用应该会变快,但使用时应该限制并行执行的切片slices查询的数量以避免内存崩掉。

 为了完全避免这样的成本,可以使用另一个字段doc_values进行切片,但用户必须确保该字段具有以下属性:

  • 字段为数字;
  • 字段开启了doc_values
  • 每个文档都应包含单个值,如果文档具有指定字段的多个值,则使用第一个值;
  • 每个文档的值仅在创建时被设置一次,永远不会更新每个文档的值,这可确保每个切片获得确定性结果;
  • 该字段的基数应该很高,这可确保每个切片slice获得大致相同数量的文档;

下面是一个小栗子:

curl -X GET "localhost:9200/twitter/_search?scroll=1m" -H 'Content-Type: application/json' -d'
{
    "slice": {
        "field": "date",
        "id": 0,
        "max": 10
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
'

注:默认情况下,每个滚动允许的最大切片sclice数限制为1024,可以更新index.max_slices_per_scroll索引设置来绕过这个限制。

猜你喜欢

转载自blog.csdn.net/jacksonary/article/details/84036053