这就是结果跳跃问题:每次用户刷新页面都会发现结果的顺序不一样。
这个问题可以通过总是为相同用户指定同样的分片来避免:将preference参数设置为一个任意的字符串,比如用户的会话ID(Session ID)。
timeout
默认情况下,协调节点会等待所有分片的响应。如果一个节点有麻烦了,那么会让所有搜索请求的响应速度变慢。
timeout参数告诉协调节点它在放弃前要等待多长时间。如果放弃了,它会直接返回当前已经有的结果。返回一部分结果至少比什么都不返回要强。
在搜索请求的响应中,有用来表明搜索是否发生了超时的字段,同时也有多少分片成功响应的字段:
...
"timed_out": true,
"_shards": {
"total": 5,
"successful": 4,
"failed": 1
},
...
routing
在分布式文档存储(Distributed Document Store)一章中的路由文档到分片(Routing a document to a shard)一小节中,我们已经解释了自定义的routing参数可以在索引时被提供,来确保所有相关的文档,比如属于同一用户的文档,都会被保存在一个分片上。在搜索时,相比搜索索引中的所有分片,你可以指定一个或者多个routing值来限制搜索的范围到特定的分片上:
GET /_search routing=user_1,user2
该技术在设计非常大型的搜索系统有用处,在Designing for scale中我们会详细介绍。
search_type
除了query_then_fetch是默认的搜索类型外,还有其他的搜索类型可以满足特定的目的,比如:
GET /_search search_type=count
count
count搜索类型只有查询阶段。当你不需要搜索结果的时候可以使用它,它只会返回匹配的文档数量或者聚合(Aggregation)结果。
query_and_fetch
query_and_fetch搜索类型会将查询阶段和获取阶段合并成一个阶段。当搜索请求的目标只有一个分片时被使用,比如指定了routing值时,是一种内部的优化措施。尽管你可以选择使用该搜索类型,但是这样做几乎是没有用处的。
dfs_query_then_fetch和dfs_query_and_fetch
dfs搜索类型有一个查询前阶段(Pre-query phase)用来从相关分片中获取到词条频度(Term Frequencies)来计算群安居的词条频度。我们会在相关性错了(Relevance is broken)一节中进行讨论。
scan
scan搜索类型会和scroll API一起使用来高效的获取大量的结果。它通过禁用排序来完成。在下一小节中会进行讨论。
scan和scroll
scan搜索类型以及scroll API会被一同使用来从ES中高效地获取大量的文档,而不会有深度分页(Deep Pagination)中存在的问题。
scroll
一个滚动(Scroll)搜索能够让我们指定一个初始搜索(Initial Search),然后继续从ES中获取批量的结果,直到所有的结果都被获取。这有点像传统数据库中的游标(Cursor)。
一个滚动搜索会生成一个实时快照(Snapshot) - 它不会发现在初始搜索后,索引发生的任何变化。它通过将老的数据文件保存起来来完成这一点,因此它能够保存一个在它开始时索引的视图(View)。
scan
在深度分页中最耗费资源的部分是对全局结果进行排序,但是如果我们禁用了排序功能的话,就能够快速地返回所有文档了。我们可以使用scan搜索类型来完成。它告诉ES不要执行排序,只是让每个还有结果可以返回的分片返回下一批结果。
为了使用scan和scroll,我们将搜索类型设置为scan,同时传入一个scroll参数来告诉ES,scroll会开放多长时间:
GET /old_index/_search search_type=scan&scroll=1m
{
"query": { "match_all": {}},
"size": 1000
}
以上请求会让scroll开放一分钟。
此请求的响应不会含有任何的结果,但是它会含有一个_scroll_id,它是一个通过Base64编码的字符串。现在可以通过将_scroll_id发送到_search/scroll来获取第一批结果:
GET /_search/scroll scroll=1m
c2Nhbjs1OzExODpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExOTpRNV9aY1VyUVM4U0
NMd2pjWlJ3YWlBOzExNjpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzExNzpRNV9aY1Vy
UVM4U0NMd2pjWlJ3YWlBOzEyMDpRNV9aY1VyUVM4U0NMd2pjWlJ3YWlBOzE7dG90YW
xfaGl0czoxOw==
该请求会让scroll继续开放1分钟。_scroll_id能够通过请求的正文部分,URL或者查询参数传入。
注意我们又一次指定了 scroll=1m。scroll的过期时间在每次执行scroll请求后都会被刷新,因此它只需要给我们足够的时间来处理当前这一批结果,而不是匹配的所有文档。
这个scroll请求的响应包含了第一批结果。尽管我们指定了size为1000,我们实际上能够获取更多的文档。size会被每个分片使用,因此每批最多能够获取到size * number_of_primary_shards份文档。
NOTE
scroll请求还会返回一个新的
_scroll_id。每次我们执行下一个scroll请求时,都需要传入上一个scroll请求返回的_scroll_id。
当没有结果被返回是,我们就处理完了所有匹配的文档。
TIP
一些ES官方提供的客户端提供了scan和scroll的工具用来封装这个功能。