ES执行分布式搜索时可以执行不同的执行路径,分布式搜索需要将搜分散到所有相关的碎片shards
,然后收集所有结果。在执行分散/聚集类型操作时,有几种方法可以执行此操作,特别是使用搜索引擎。当执行分布式搜索时,有一个问题是从每个分片上搜索多少结果,例如,如果我们有10个分片,则第一个分片可能保持从0到10的最相关结果,其他分片结果排在其下方,因此,当执行搜索时,我们需要从所有碎片上获取0到10的结果,然后进行排序,如果想确保正确的结果,则返回结果。另一个与搜索引擎相关的问题是,每个碎片都独立存在。当具体的碎片执行搜索时,它不会考虑来自其他碎片的术语频率和其他搜索引擎信息。如果想要准确的排序,就需要首先从所有碎片上搜集术语term
的频率并计算全局术语频率,然后使用这些全局频率在每个碎片上执行查询。由于需要对结果进行排序、获取大型文档集、甚至滚动它,同时保持正确的排序行为,这样的可能会耗费资源。对于大型结果集的滚动,如果返回文档的顺序不重要,最好按_doc
排序。ES是非常灵活的并控制基于每个搜索请求执行的搜索类型,在查询参数中可以通过search_type
参数配置搜索的类型。搜索类型主要有:
query_then_fetch
。这个类型是分为两步完成请求的:第一步,查询将转发到所有涉及的碎片,每个碎片执行搜索、生成本地排序后的结果列表,每个碎片都向协调节点返回足够的信息,以允许它合并并将碎片级别结果重新排序为具有最大长度大小的全局排序结果集;第二步,协调节点仅从相关碎片上请求文档内容(以及突出显示的片段,如果有的话)。如果不特别指定search_type
,那默认就是query_then_fetch
;dfs_query_then_fetch
,与“查询然后拉取”相同,但是比它多了初始分散阶段,其进行并计算分布式术语频率以获得更准确的评分; 当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条记录的请求作几点说明:
GET
和POST
请求都可以使用;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
索引设置来绕过这个限制。