在ES执行分布式搜索时,分布式搜索操做须要分散到全部相关分片,若一个索引有3个主分片,每一个主分片有一个副本分片,那么搜索请求会在这6个分片中随机选择3个分片,这3个分片有多是主分片也多是副本分片,而后收集全部分片的查询结果。因此ES的搜索过程分为两个阶段,Query阶段和Fetch阶段;ES有两种搜索类型:query_then_fetch,dfs_query_then_fetch。html
1.Query阶段java
1)转发请求。在Query阶段客户端向ES节点发送,搜索请求,Coordinate节点接受客户端搜索请求,Coordinate节点负责解析搜索请求,并在索引的全部主副本分片中随机选择分片,而且发送给分片所在的数据节点。api
2)执行查询。接收到查询请求的数据节点执行查询操做,并对查询结果进行排序,每一个节点都会根据请求中参数返回from+size个排序后的文档Id和排序值给Coordinate节点。数组
2.Fetch阶段分布式
1)重排序。Coordinate节点收到数据节点返回的数据后,会按照返回的排序值对从全部分片取回的值从新进行排序,最终只选取客户端须要的from+size个文档的Id。性能
2)获取文档数据。Coordinate节点根据选取的文档的Id,到相应的分片获取详细的文档数据,最终将查询到的结果返回给客户端。学习
查询结果解读:fetch
{ "took":3, 查询所用的毫秒数 "timed_out":false, 是否有分片超时,便是否只返回了部分结果 "_shards":{ "total":1, 一共查询了多少分片 "successful":1, 多少分片成功返回 "skipped":0,跳过了多少分片 "failed":0 多少分片查询失败 }, "hits":{ "total":{ "value":1, 该搜索请求中返回的全部匹配的数量 "relation":"eq" 文档与搜索值的关系,eq表示相等 }, "max_score":8.044733, 返回结果中文档的最大得分 "hits":[ 查询结果的文档数组 { "_index":"kibana_sample_data_ecommerce", 查询的索引 "_type":"_doc", 查询的类型 "_id":"4X-j7XEB-r_IFm6PISqV", 返回文档的主键 "_score":8.044733, 返回文档的评分 "_source":{ 文档的原始内容 "currency":"EUR", "customer_first_name":"Eddie", "customer_full_name":"Eddie Underwood", "customer_gender":"MALE" ...... } } ] } }
Query Then Fetch潜在的问题spa
1.深度分页code
ES索引数据分布在多个分片上,在查询时,每一个分片都要查询from+size个文档,Coordinate节点会聚合全部的结果,因此Coordinate节点要处理查询分片数*(from+size)个文档记录,对这些记录进行从新排序,须要的size个文档,from+size的值越大占用内存越多,称为深度分页问题,ES默认限制分页的深度不能超过10000条,可经过max_result_window设置。
深度分页解决办法:
1)Search After
可使用Search After避免深度分页的性能问题,实时获取下一页的文档信息,search_after根据上一页最后一个文档的sort值来查询下一页,而且当索引数据有变化时,也能够同步被查到,是一个实时查询的方法。
例:http://127.0.0.1:9200/kibana_sample_data_ecommerce/_search
查询参数:在使用Search_After查询时,第一步查询时须要指定sort字段,而且该sort字段的排序结果是惟一的,建议使用_id来进行sort,能够指定多个sort字段。
{
"size": 1,
"query": {
"match": {
"currency": "EUR"
}
},
"sort": [
{
"order_id": {
"order": "asc"
}
}
]
}
返回中能够看到第一页查询返回的sort值,查询下一页时使用该sort值进行文档的定位,然后每一个查询都会返回一个sort值,供下一页进行定位使用。
"sort": [ "550375" ]
下一页查询:
{
"size": 1,
"query": {
"match": {
"currency": "EUR"
}
},
"search_after": [
550375
],
"sort": [
{
"order_id": {
"order": "asc"
}
}
]
}
Search_After存在的限制:
a.不能指定from值,即不能想翻到哪一页就直接跳转到那一页,只能一页一页按照顺序翻;
b.只能日后翻页,不能往前翻页。
2)Scroll API
scroll api能够用于从单个搜索请求中检索大量的结果,其原理是创建索引在某个时间点的快照,当快照创建后,以后的每次搜索都会在该快照上进行,对索引的全部新增操做都会被忽略,索引Scroll适合于处理大量数据,可是不能保证数据的实时性。
POST http://127.0.0.1:9200/kibana_sample_data_ecommerce/_search?scroll=1m
首次查询时指定scroll=5m,表示当前搜索过时时间为5分钟,即查询结果在搜到下一次请求以前会保存屡次时间,scroll的值不须要长到把整个快照的数据都处理完,只需保证下一次搜索请求到来以前能处理完前一批查询结果便可。
{ "size": 2, "query": { "match" : { "currency" : "EUR" } } }
返回中能够看到_scroll_id,total.value,scroll_id用于获取下一批的查询结果,total.value表示该查询有总共多少个结果。
{ "_scroll_id":"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABAGUWdks0dUtFMHZTYmE1Rl9ucGp5X0hoUQ==", "took": 1, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 4675, "relation": "eq" }, } }
下一页:
http://127.0.0.1:9200/_search/scroll
下一页查询的时候不用指定索引和查询参数,只须要指定scroll时间和上一次请求返回的scroll_id,由于快照已经建好,只须要在快照上往下翻页便可。每次执行该请求都会往下进行翻页,直到查询的结果为空。
{ "scroll":"5m", "scroll_id":"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABAGUWdks0dUtFMHZTYmE1Rl9ucGp5X0hoUQ==" }
Scroll API存在的限制:当快照创建后,对索引有新的操做时,没法被查询到,因此不适合作实时查询。
不一样查询的使用场景
通常查询:须要获取顶部的部分文档,查询索引最新的数据。
全量查询:使用scroll,当须要导出所有数据,且对数据的实时性要求不高时。
分页查询:使用from+size,当from+size过大时,使用search after。
2.相关度评分不许问题
当搜索请求在多个shard进行数据查找时,每一个分片都会基于本身分片上的文档数据进行相关度的计算,计算方法为TD/IDF,
TF:词频,表示词条在一个文档中出现的频率;IDF:逆文档频率,log(全本文档数/词条在全部文档中出现的次数),表示该term在全部文档中出现的频率;若是查询词条在某一个文档中出现的频率(即TF)高,在所有文档中出现的频率低(即IDF)低,则代表该文档的相关性高。
每一个分片计算IDF的时候只会基于本身分片上的数据进行计算,并不会包含其余分片上的数据,因此这样会致使相关性评分不许的状况;特别在文档总数不多状况下,主分片数越多,相关性算分会越不许。
解决相关度评分不许问题的方法:
1)合理设置分片数量,保证数据均匀分布。
当数据量不大时,能够考虑仅设置一个主分数;当数据量较大时,保证文档均匀的分布在各个分片上。ES提供了
routing_partition_size越大,数据的分布越均匀(【Elasticsearch学习】之一图读懂文档索引全过程 中有说起)。routing_partition_size参数,
2)使用dfs_query_then_fetch
在搜索时,指定搜索的类型search_type=dfs_query_the_fetch,在搜索的时候,每一个分片会把每一个分片的TF和IDF进行搜集,而后综合全部的数据进行一次完整的相关性评分计算,可是通常不推荐,由于这样会耗费较多的CPU和内存。