Search APIs之Request Body Search(4)

Named Queries

每个过滤器和查询都可以在其顶层定义中接受_name。

GET /_search
{
    "query": {
        "bool" : {
            "should" : [
                {"match" : { "name.first" : {"query" : "shay", "_name" : "first"} }},
                {"match" : { "name.last" : {"query" : "banon", "_name" : "last"} }}
            ],
            "filter" : {
                "terms" : {
                    "name.last" : ["banon", "kimchy"],
                    "_name" : "test"
                }
            }
        }
    }
}

搜索响应将包括每次命中匹配的matched_queries。查询和过滤器的标签只对bool查询有意义。

Post filter

         在计算了聚合之后,post_filter应用于搜索请求末尾的搜索结果。它的目的最好用例子来解释:

假设您正在销售具有以下特性的衬衫:

PUT /shirts
{
    "mappings": {
        "properties": {
            "brand": { "type": "keyword"},
            "color": { "type": "keyword"},
            "model": { "type": "keyword"}
        }
    }
}

PUT /shirts/_doc/1?refresh
{
    "brand": "gucci",
    "color": "red",
    "model": "slim"
}

假设用户指定了两个过滤器:

颜色:红色和品牌:古奇。你只需要在搜索结果中显示Gucci生产的红色衬衫。通常你会这样做与一个bool查询:

GET /shirts/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "color": "red"   }},
        { "term": { "brand": "gucci" }}
      ]
    }
  }
}

不过,您还希望使用分面导航来显示用户可以单击的其他选项列表。也许您有一个模型字段,允许用户将搜索结果限制为红色Gucci t恤或正装衬衫。

这可以通过来terms aggregation实现:

GET /shirts/_search
{
  "query": {
    "bool": {
      "filter": [
        { "term": { "color": "red"   }},
        { "term": { "brand": "gucci" }}
      ]
    }
  },
  "aggs": {
    "models": {
      "terms": { "field": "model" }    ------------1
    }
  }
}

1: 返回最受欢迎的古驰红色衬衫模型。

但也许你还想告诉用户有多少其他颜色的古琦衬衫可供选择。如果您只是在颜色字段上添加一个聚合项,您将只返回红色,因为您的查询只返回Gucci的红色衬衫。

相反,您希望在聚合期间包含所有颜色的衬衫,然后仅对搜索结果应用颜色过滤器。这就是post_filter的目的:

GET /shirts/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": { "brand": "gucci" }        ----------------1
      }
    }
  },
  "aggs": {
    "colors": {
      "terms": { "field": "color" }   ------------------2
    },
    "color_red": {
      "filter": {
        "term": { "color": "red" }  -------------------3
      },
      "aggs": {
        "models": {
          "terms": { "field": "model" }  ---------------------4
        }
      }
    }
  },
  "post_filter": { 
    "term": { "color": "red" } -----------------5
  }
}

1: 主查询现在找到了Gucci所有的衬衫,不管颜色如何。

2: colors聚合返回古驰衬衫的流行颜色。

3 and 4:color_red聚合将模型子聚合限制为红色Gucci衬衫

5: 最后,post_filter从搜索结果中删除红色以外的颜色。

Preference

         控制要执行搜索的分片副本的首选项, 默认情况下,Elasticsearch按未指定的顺序从可用的分片副本中进行选择,同时考虑了分配感知和自适应副本选择配置。但是,有时可能需要尝试将某些搜索路由到某些分片副本集,例如更好地使用每个副本的缓存。

         preference是一个查询字符串参数,可以设置为:

         _only_local

该操作将仅在分配给本地节点的分片上执行。

_local

如果可能,该操作将在分配给本地节点的切分上执行,如果不执行,则返回到其他切分片。

_prefer_nodes:abc,xyz

如果可能,该操作将在具有提供的节点id(本例中为abc或xyz)之一的节点上执行。如果选择的节点中有多个存在合适的分片副本,则这些副本之间的优先顺序未指定。

_shards:2,3

将操作限制为指定的分片。(这里是2和3)此首选项可以与其他首选项组合,但必须首先出现:_shards:2,3|_local

_only_nodes:abc*,x*yz,...

将操作限制为根据节点规范指定的节点。如果选择的节点中有多个存在合适的分片副本,则这些副本之间的优先顺序未指定。

Custom (string) value

任何不以 _ 开头的值。如果两个搜索都为它们的首选项提供相同的自定义字符串值,并且底层集群状态没有更改,那么将对搜索使用相同的分片排序。这并不能保证每次都使用完全相同的分片:集群状态以及因此选择的分片可能由于许多原因而发生变化,包括分片重定位和分片故障,节点有时可能拒绝搜索,从而导致对备选节点的回退。然而,在实践中,分片的顺序往往在很长一段时间内保持稳定。定制首选项值的一个很好的候选项是web会话id或用户名之类的东西。

例如,使用用户的会话ID xyzabc123如下:

GET /_search?preference=xyzabc123
{
    "query": {
        "match": {
            "title": "elasticsearch"
        }
    }
}

注意: _only_local首选项保证只在本地节点上使用分片副本,这有时对于故障排除非常有用。所有其他选项都不能完全保证在搜索中使用任何特定的分片副本,对于一个不断变化的索引,这可能意味着如果在处于不同刷新状态的不同分片副本上执行重复搜索,可能会产生不同的结果。

Query

搜索请求主体中的查询元素允许使用查询DSL定义查询。

GET /_search
{
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}

Rescoring

Rescoring可以通过仅对查询和post_filter阶段返回的顶部(例如100 - 500)文档进行重新排序来帮助提高精度,方法是使用二级(通常更昂贵)算法,而不是将昂贵的算法应用于索引中的所有文档。

rescore请求在每个切分上执行,然后返回结果,按照处理整个搜索请求的节点进行排序。

目前,rescore API只有一个实现:查询rescorer,它使用查询调整评分。在未来,可能会有替代的rescorers可用,例如,a pair-wise rescorer。

注意:如果在rescore查询中提供显式排序(除_score按降序排列外),则会引发错误。

注意:当向用户公开分页时,不应该在遍历每个页面时更改window_size(通过传递与值不同的值),因为这会更改顶部的点击率,导致结果在用户遍历页面时发生令人困惑的变化。

Query rescorer

查询rescorer仅对查询和post_filter阶段返回的Top-K结果执行第二个查询。每个切分上要检查的文档数量可以由window_size参数控制,默认值为10。

默认情况下,原始查询和rescore查询的得分是线性组合的,以生成每个文档的最终_score。可以分别使用query_weight和rescore_query_weight控制原始查询和rescore查询的相对重要性。两者都默认为1。

例如:

POST /_search
{
   "query" : {
      "match" : {
         "message" : {
            "operator" : "or",
            "query" : "the quick brown"
         }
      }
   },
   "rescore" : {
      "window_size" : 50,
      "query" : {
         "rescore_query" : {
            "match_phrase" : {
               "message" : {
                  "query" : "the quick brown",
                  "slop" : 2
               }
            }
         },
         "query_weight" : 0.7,
         "rescore_query_weight" : 1.2
      }
   }
}

可以用score_mode控制分数的组合方式:

Score Mode                            Description

 total       添加原始分数和rescore查询分数。默认值。

multiply     将原始得分乘以rescore查询得分。用于函数查询重核。

avg        平均原始得分和重核查询得分。

max        取原始分数和重核查询分数的最大值。

min        取原始分数和重核查询分数的最小值。

Multiple Rescores

也可以按顺序执行多个重核:

POST /_search
{
   "query" : {
      "match" : {
         "message" : {
            "operator" : "or",
            "query" : "the quick brown"
         }
      }
   },
   "rescore" : [ {
      "window_size" : 100,
      "query" : {
         "rescore_query" : {
            "match_phrase" : {
               "message" : {
                  "query" : "the quick brown",
                  "slop" : 2
               }
            }
         },
         "query_weight" : 0.7,
         "rescore_query_weight" : 1.2
      }
   }, {
      "window_size" : 10,
      "query" : {
         "score_mode": "multiply",
         "rescore_query" : {
            "function_score" : {
               "script_score": {
                  "script": {
                    "source": "Math.log10(doc.likes.value + 2)"
                  }
               }
            }
         }
      }
   } ]
}

第一个获取查询的结果,然后第二个获取第一个的结果,等等。第二个rescore将“查看”第一个rescore完成的排序,因此可以在第一个rescore上使用一个大窗口将文档拖放到第二个rescore的一个较小窗口中。

Script Fields

允许为每个击中返回一个脚本评估(基于不同的字段)例如:

GET /_search
{
    "query" : {
        "match_all": {}
    },
    "script_fields" : {
        "test1" : {
            "script" : {
                "lang": "painless",
                "source": "doc['price'].value * 2"
            }
        },
        "test2" : {
            "script" : {
                "lang": "painless",
                "source": "doc['price'].value * params.factor",
                "params" : {
                    "factor"  : 2.0
                }
            }
        }
    }
}

脚本字段可以处理未存储的字段(在上面的例子中是price),并允许返回要返回的自定义值(脚本的评估值)。

脚本字段还可以访问实际的_source文档,并使用params['_source']提取要从中返回的特定元素。举个例子:

GET /_search
    {
        "query" : {
            "match_all": {}
        },
        "script_fields" : {
            "test1" : {
                "script" : "params['_source']['message']"
            }
        }
    }

注意这里的_source关键字,以导航类似于json的模型。

理解doc['my_field'].value 和params [' _source '] [' my_field ']之间的区别很重要。第一个,使用doc关键字,将导致将该字段的terms加载到内存(缓存)中,这将导致更快的执行,但更多的内存消耗。此外,doc[…]表示法只允许简单的值字段(不能从中返回json对象),只适用于未分析的或基于单个terms的字段。但是,如果可能的话,使用doc仍然是访问文档值的推荐方法,因为每次使用_source时都必须加载和解析它。使用_source非常慢。

Scroll

         虽然搜索请求返回一个结果“页面”,但是滚动API可以用于从一个搜索请求检索大量结果(甚至所有结果),这与在传统数据库上使用游标的方法非常相似。

滚动不是为实时用户请求而设计的,而是为处理大量数据而设计的,例如,为了将一个索引的内容重新编入具有不同配置的新索引中。

注意:滚动请求返回的结果反映了发出初始搜索请求时索引的状态,就像时间快照一样。文档的后续更改(索引、更新或删除)只会影响以后的搜索请求。

为了使用滚动,初始搜索请求应该在查询字符串中指定滚动参数,该参数告诉Elasticsearch应该保持“搜索上下文”活动多长时间(参见保持搜索上下文活动),例如?scroll=1m。

POST /twitter/_search?scroll=1m
{
    "size": 100,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

上面请求的结果包含一个_scroll_id,该id应该传递给scroll API,以便检索下一批结果。

POST /_search/scroll  ------------1
{
    "scroll" : "1m",  -----------2
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="  ----3
}

1: 可以使用GET或POST, URL不应该包含索引名——这是在原始搜索请求中指定的。

2: scroll参数告诉Elasticsearch将搜索上下文保持1m

3: scroll_id参数

size参数允许您配置每批结果返回的最大命中次数。每次调用滚动API都会返回下一批结果,直到没有剩余的结果返回,即hits数组为空。

重要: 初始搜索请求和随后的每个滚动请求都返回_scroll_id。虽然_scroll_id在请求之间可能会更改,但它并不总是更改—在任何情况下,应该只使用最近接收到的_scroll_id。

注意:如果请求指定聚合,则只有初始搜索响应将包含聚合结果。

注意: 滚动请求具有优化功能,当排序顺序为_doc时,滚动请求的速度更快。如果你想遍历所有文档,不管顺序如何,这是最有效的选择:

GET /_search?scroll=1m
{
  "sort": [
    "_doc"
  ]
}

Keeping the search context alive

滚动返回在初始搜索请求时匹配搜索的所有文档。它将忽略对这些文档的任何后续更改。scroll_id标识一个搜索上下文,该上下文跟踪Elasticsearch返回正确文档所需的所有内容。搜索上下文由初始请求创建,并由后续请求保持活动状态。

滚动参数(传递给搜索请求和每个滚动请求)告诉Elasticsearch应该保持搜索上下文活动多长时间。它的值(例如1m,参见时间单位)不需要足够长来处理所有数据—它只需要足够长来处理前一批结果。每个滚动请求(带有滚动参数)设置一个新的过期时间。如果滚动请求没有传递滚动参数,那么搜索上下文将作为滚动请求的一部分被释放。

通常,后台合并过程通过合并较小的段来优化索引,从而创建新的较大的段。一旦不再需要更小的段,它们就会被删除。这个过程在滚动期间继续,但是一个开放的搜索上下文阻止删除旧段,因为它们仍然在使用。

提示: 保持旧段存活意味着需要更多的磁盘空间和文件句柄。确保已将节点配置为具有足够的空闲文件句柄。 See File Descriptors

此外,如果一个段包含删除或更新的文档,那么搜索上下文必须跟踪段中的每个文档在初始搜索请求时是否处于活动状态。如果索引上有许多打开的滚动条,且索引可能正在进行删除或更新,请确保节点有足够的堆空间。
         为了防止打开过多的滚动条而导致的问题,用户不允许打开超过一定限制的滚动条。默认情况下,打开的最大滚动数为500。这个限制可以通过更新search.max_open_scroll_context集群设置。

你可以用node stats API检查打开了多少个搜索上下文:

GET /_nodes/stats/indices/search

Clear scroll API

当超过滚动超时时,将自动删除搜索上下文。然而,保持滚动条打开是有代价的,正如前一节所讨论的,所以一旦滚动条不再使用clear-scroll API,就应该显式地清除滚动条:

DELETE /_search/scroll
{
    "scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}

Multiple scroll IDs可以作为数组传递:

DELETE /_search/scroll
{
    "scroll_id" : [
      "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
      "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
    ]
}

使用_all参数可以清除所有搜索上下文:

DELETE /_search/scroll/_all

scroll_id也可以作为查询字符串参数或在请求体中传递。多个滚动id可以作为逗号分隔的值传递:

DELETE /_search/scroll/DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==,DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB

对于返回大量文档的滚动查询,可以将滚动分割成多个可独立使用的片:

GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 0,          ------------1
        "max": 2     -------------2
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}
GET /twitter/_search?scroll=1m
{
    "slice": {
        "id": 1,
        "max": 2
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

1: Slicedid

2: 最大切分数

第一个请求的结果返回属于第一个片的文档(id: 0),第二个请求的结果返回属于第二个片的文档。由于最大切分数被设置为2,因此两个请求的结果的并集相当于不进行切分的滚动查询的结果。默认情况下,首先对分片进行分割,然后使用具有以下公式的_id字段在每个分片上进行本地分割:slice(doc) = floorMod(hashCode(doc._id), max),例如,如果分片的数量等于2,并且用户请求了4个切分,则切分0和2被分配给第一个分片,切分1和3被分配给第二个分片。

每个滚动是独立的,可以像任何滚动请求一样并行处理。

如果切分的数量大于第一次的分片数量,则切分过滤器在第一次调用时非常慢,它具有O(N)的复杂度,并且内存开销等于每个切片的N位,其中N是分片中文档的总数。少量调用过滤器应该被缓存,后续的调用才会变得更快,但是应该限制并行执行的切分查询的数量,以避免内存溢出。

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

字段是数值型的。

在该字段上启用doc_values

每个文档都应该包含一个值。如果文档具有指定字段的多个值,则使用第一个值。

每个文档的值应该在创建文档时设置一次,并且永不更新。这确保每个切分都得到确定的结果。

字段的基数应该很高。这可以确保每个片获得大致相同数量的文档。

GET /twitter/_search?scroll=1m
{
    "slice": {
        "field": "date",
        "id": 0,
        "max": 10
    },
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    }
}

对于只附加基于时间的索引,可以安全地使用时间戳字段。

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

Search After

结果的分页可以通过使用from和size来完成,但是当到达深度分页时,成本变得非常高昂,index.max_result_window默认值为10,000,这是一种保护措施,搜索请求占用堆内存,时间与+ size成正比。滚动api推荐用于高效的深度滚动,但滚动上下文的成本很高,不建议将其用于实时用户请求。search_after参数通过提供活动游标来绕过这个问题。其思想是使用上一页的结果来帮助检索下一页。

假设检索第一个页面的查询如下:

GET twitter/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"date": "asc"},
        {"tie_breaker_id": "asc"}      -------------1
    ]
}

启用doc_values的_id字段的副本

重要: 每个文档具有唯一值的字段应该用作排序规范的平分符。否则,具有相同排序值的文档的排序顺序将是未定义的,并可能导致丢失或重复结果。_id字段对于每个文档都有一个惟一的值,但不建议将其直接用作平分符。注意,search_after查找完全或部分匹配tiebreaker提供的值的第一个文档。因此,如果一个文档的tiebreaker值为“654323”,而您在search_after中搜索“654”,它仍然会匹配该文档并返回在该文档之后找到的结果。该字段禁用doc值,因此对其排序需要在内存中加载大量数据。相反,建议在启用doc值的另一个字段中复制_id字段的内容(客户端或使用一组ingest处理器),并使用这个新字段作为排序的决定符。

上述请求的结果为每个文档包括一个sort values的数组,这些sort values可以与search_after一起被使用在任何文档之后开始返回结果列表中的结果

例如,我们可以使用上一个文档的排序值,并将其传递给search_after来检索下一页的结果:

GET twitter/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [1463538857, "654323"],
    "sort": [
        {"date": "asc"},
        {"tie_breaker_id": "asc"}
    ]
}

注意: 当使用search_after时,from参数必须设置为0(或-1)。

search_after不是一个可以自由跳转到随机页面的解决方案,而是一个可以并行滚动许多查询的解决方案。它与滚动API非常相似,但与之不同的是,search_after参数是无状态的,它总是针对搜索器的最新版本进行解析。因此,根据索引的更新和删除,排序顺序可能在遍历期间发生变化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值