本章将详细介绍Elasticsearch提供的数据搜索功能。搜索API的格式有URI和body 两种形式。大多数搜索API都是支持多索引的,ExplainAPI除外(用于调试性能)。 5.1 基本概念和机制 执行搜索时,Elasticsearch将根据内部的选择公式选择数据的“最佳”副本。也可以通 过提供路由参数routing 来控制要搜索的分片。例如,索引twitter时,路由值可以是用 户名: POST /twitter/_doc?routing=kimchy { "user" : "kimchy", "postDate" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch" } 在这种情况下,如果只需要在twitter上搜索特定用户,可以将其指定为路由,从而使搜 索只命中相关的分片,具体实例如下: POST /twitter/_search?routing=kimchy { "query": { "bool" : { "must" : { "query_string" : { "query" : "some query string here" } }, "filter" : { "term" : { "user" : "kimchy" } } } } } 路由参数可以是多值的,用逗号分隔的字符串表示,这将会命中路由值匹配的相关 分片。 Elasticsearch权1 24 威指南 默认情况下,Elasticsearch将使用所谓的“自适应副本选择”机制。这样,协调节点(接 受请求的节点)可以根据多个条件将请求发送到被视为“最佳”的副本,主要考虑的因素 如下:. 协调节点和包含数据副本的节点之间传送请求的响应时间。 . 在包含数据的节点上执行过去的搜索请求所用的时间。 . 包含数据的节点上的搜索线程池的队列大小。 这可以通过更改动态集群设置cluster.routing.use_adaptive_replica_selection为false 来关闭这种分片选择机制: PUT /_cluster/settings { "transient": { "cluster.routing.use_adaptive_replica_selection": false } } 如果关闭“自适应副本选择”机制,则在所有数据副本(主分片和副本)之间以循环方式 将搜索发送到相关分片。 搜索可以与STATS组相关联,STATS组维护每个组的统计聚合信息。后面有章节来 讲解使用索引统计API来检索它。例如,下面的实例是一个将请求与两个不同组关联的搜 索正文请求: POST /_search { "query" : { "match_all" : {} }, "stats" : ["group1", "group2"] } 作为请求正文搜索的一部分,单个搜索可以有一个超时。由于搜索请求可以来自多个 源,因此Elasticsearch具有全局搜索超时的动态集群级别设置。该设置适用于所有未在请 求正文中设置超时的搜索请求,这些请求将在指定的时间后取消,因此同样的超时响应警告 也适用。 default_search_timeout可以动态设置,默认值为无全局超时。把此值设置为-1将全 局搜索超时重置为无超时。 可以使用标准的任务取消API终止搜索请求的执行。默认情况下,正在运行的搜索进 程只检查它是否被取消或不在段边界上,因此取消可能有较大延迟。当在大段上执行请求 时,通过将search.low_level_cancellation动态设置为true,可以提高搜索取消操作的响应能 力。然而,它带来了额外的开销,引发更频繁的取消检查,在高并发场景下可能会导致性能 问题。同时,更改此设置只影响更改后开始的搜索。 默认情况下,Elasticsearch 不会因为命中分片数量多而拒绝任何搜索请求。虽然 Elasticsearch将优化协调节点上的搜索执行,但大量分片会对CPU 和内存产生重大影响。 更好地组织数据的方法是分片数量少,单个分片数据量大。如果要配置软限制,可以更新 第5章 搜索数据1 25 action.search.shard_count.limit集群设置,以拒绝命中过多分片的搜索请求。 请求参数max_concurrent_shard_requests,用于控制每个节点的最大并发分片请求数 (每个节点上同时执行请求的分片的数量)。此参数应用于保护单个请求不会造成集群负载 过高(例如,默认请求将命中集群中的所有索引,如果每个节点的分片数较高,则可能导致分 片请求拒绝)。其默认值是所有群集中数据节点的数量,但最多为256。 5.2 搜索API 搜索API(_search)允许用来执行搜索查询并返回匹配的结果。可以使用简单查询字 符串作为参数提供查询(URI形式),也可以使用请求正文(body形式)。 所有搜索API都支持跨索引机制,并支持多索引语法。例如,搜索twitter索引中的所 有文档: GET /twitter/_search?q=user:kimchy 还可以在多个索引中搜索具有特定标记的所有文档,例如当每个用户有一个索引时: GET /kimchy,Elasticsearch/_search?q=tag:wow 或者使用_all搜索所有可用索引: GET /_all/_search?q=tag:wow 为了确保快速响应,如果一个或多个分片失败,搜索API将以部分结果响应。 5.3 URI模式 通过提供请求参数,可以纯粹使用URI执行搜索请求。在使用此模式执行搜索时,并 非所有搜索选项都可用,但对于快速的“测试”来说,它非常方便。下面是一个例子: GET twitter/_search?q=user:kimchy URI搜索模式支持的参数如表5-1所示。 表5-1 URI参数 参数名称含义及用法 q 查询字符串(映射到query_string查询,有关详细信息,请参阅相关章节DSL 内容) df 在查询中未定义字段前缀时使用的默认字段 analyzer 分析查询字符串时使用的分析器名称 analyze_wildcard 是否应分析通配符和前缀查询,默认为false batched_reduce_size 应在协调节点上一次减少的分片结果数。如果请求中潜在的分片数量很大, 则应将此值用作保护机制,以减少每个搜索请求的内存开销 续表 Elasticsearch权1 26 威指南 参数名称含义及用法 default_operator 要使用的默认运算符,可以是AND或OR,默认为OR lenient 如果设置为true将导致忽略基于格式的失败(如为数字字段提供文本),默认 为false explain 对于每个命中结果,包含如何计算命中得分的解释信息 _source 设置为false禁用_source字段检索。可以使用_source_include&_source_ exclude检索文档的部分字段 stored_fields 要返回的文档的存储字段,用逗号分隔,不指定任何值将导致没有字段返回 sort 排序,可以是fieldName或fieldName:asc/fieldName:desc。fieldName可以 是文档中的实际字段,也可以是特殊_score字段,表示基于分数的排序,可以 有几个sort参数(顺序很重要) track_scores 当为true时会始终计算相关性分数;当为false时,相关性计算开销巨大。如 果不用相关性排序,则不会计算,_score和max_score字段都是null。如果用 到了相关性排序,还是会计算相关性分数的 track_total_hits 值可以是false或正整数。当为false时,不再返回total计数,total始终为1; 当为正整数时,表示精确的命中文档计数,例如为1000时,返回如下结构: "total" : { "value" : 1, "relation" : "gte" } 此时如果命中结果小于或等于1000时,value是精确的计数,relation是 eq;如果命中的结果数大于1000时,此时value等于1000,relation是gte。如 果需要精确统计结果数,这个值要设置得很大,这个参数的设置更多地出于性 能考虑 timeout 搜索超时,将搜索请求限制在指定的时间内执行。如果超过这个时间没有执 行完毕,则请求返回。默认为无超时,一般无须更改这个值 terminate_after 每个分片可以收集的最大文档数,在达到这个值时查询将提前终止。如果设 置了这个参数,响应将具有一个布尔字段terminated_early,以指示查询执行是 否实际上已终止,默认为无限制 from 结果的偏移量,默认为0 size 要返回的结果的数量,默认为10 search_type 要执行的搜索操作的类型,可以是dfs_query_then_fetch或query_then_fetch, 默认为query_then_fetch allow _ partial_ search _results 为false时,如果请求将产生部分结果,则设置为返回整体请求失败;默认为 true,这将允许在超时或部分失败的情况下获得部分结果。可以通过search. default_allow_partial_results动态设置 5.4 Body模式 搜索请求可以在请求正文中使用QueryDSL(后面会有章节讲解)。下面是一个例子: 第5章 搜索数据1 27 GET /twitter/_search { "query" : { "term" : { "user" : "kimchy" } } } Body搜索模式支持的参数如表5-2所示。 表5-2 Body支持的参数 参数名称含义及用法 batched_reduce_size 此参数用来限制协调节点一次(批)处理的分片数量,如果命中的分片数量大 于此参数值,则会分批执行,默认值为512。如果请求中潜在的分片数量很大, 则应将此值用作保护机制,以减少每个搜索请求的内存开销 timeout 搜索超时,将搜索请求限制在指定的时间内执行,如果超过这个时间没有执行 完毕,则请求返回。默认为无超时,一般无须更改这个值 terminate_after 每个分片可以收集的最大文档数,在达到这个值时查询将提前终止。如果设 置了这个参数,响应将具有一个布尔字段terminated_early,指示查询执行是否 实际上已终止。默认为无限制 from 结果的偏移量,默认为0 size 要返回的结果的数量,默认为10 search_type 要执行的搜索操作的类型,可以是dfs_query_then_fetch或query_then_fetch, 默认为query_then_fetch allow _ partial_ search _results 为false时,如果请求将产生部分结果,则设置为返回整体请求失败;默认为 true,这将允许在超时或部分失败的情况下获得部分结果。可以通过search. default_allow_partial_results动态设置 result_cache 设置为true或false以启用或禁用对size为0 的请求(即aggregations和 suggestions)的搜索结果缓存 ccs _ minimize _roundtrips 默认为true,设置为false以禁用在执行跨集群搜索请求时最小化协调节点和 远程集群之间的网络往返 注意:search_type、request_cache和allow_partial_search_results必须作为查询字符 串参数传递(不能放在body里面,要放在URL里面)。搜索请求的其余部分应该在主体内 部传递。正文内容也可以作为名为source的REST 参数传递。HTTP GET 和HTTP POST都可以用于执行带Body的搜索。 terminate_after始终在post_filter之后应用,并在分片上收集到足够的命中结果后停 止查询和聚合。聚合上的文档计数可能不会反映响应中的hits.total,因为聚合是在post_ filter之前应用的。 如果只需要知道是否有任何文档匹配特定的查询,可以将size设置为0,以表示对搜索 结果不感兴趣。此外,还可以将terminate_after设置为1,以指示只要找到第一个匹配的文 档(每个分片),就可以终止查询执行。示例如下: GET /_search?q=message:number&size=0&terminate_after=1 Elasticsearch权1 28 威指南 响应如下: { "took": 3, "timed_out": false, "terminated_early": true, "_shards": { "total": 1, "successful": 1, "skipped" : 0, "failed": 0 }, "hits": { "total" : { "value": 1, "relation": "eq" }, "max_score": null, "hits": [] } } 可以看到,响应结果中不包含任何文档,因为size设置为0。hits.total将等于0,表示 没有匹配的文档,或者大于0,表示在提前终止查询时,至少有多个文档匹配此查询。此外, 如果查询提前终止,则在响应中将terminated_early标志设置为true。 响应中所用的时间took是处理此请求所用的毫秒数,从节点收到查询后快速开始,直 到完成所有与搜索相关的工作,然后再将上述JSON 返回到客户机。这意味着它包括在线 程池中等待、在整个集群中执行分布式搜索以及收集所有结果所花费的时间。 5.4.1 Explain 参数 Explain参数是Elasticsearch提供的辅助API,经常不为人所知和所用。Explain参数 用来帮助分析文档的相关性分数是如何计算出来的。 下面例子使用此参数查看分数的详细信息: GET /_search { "explain": true, "query" : { "term" : { "user" : "kimchy" } } } 响应如下: 1 { 2 "took" : 6, 3 "timed_out" : false, 4 "_shards" : { 第5章 搜索数据1 29 5 "total" : 11, 6 "successful" : 11, 7 "skipped" : 0, 8 "failed" : 0 9 }, 10 "hits" : { 11 "total" : { 12 "value" : 1, 13 "relation" : "eq" 14 }, 15 "max_score" : 0.2876821, 16 "hits" : [ 17 { 18 "_shard" : "[twitter][0]", 19 "_node" : "lMn0uhCRTRGvnE9VVzVK3w", 20 "_index" : "twitter", 21 "_type" : "_doc", 22 "_id" : "G2T6r2oBZFnCR_TIsu5i", 23 "_score" : 0.2876821, 24 "_routing" : "kimchy", 25 "_source" : { 26 "user" : "kimchy", 27 "postDate" : "2009-11-15T14:12:12", 28 "message" : "trying out Elasticsearch" 29 }, 30 "_explanation" : { 31 "value" : 0.2876821, 32 "description" : "weight(user:kimchy in 0) [PerFieldSimilarity], result of:", 33 "details" : [ 34 { 35 "value" : 0.2876821, 36 "description" : "score(freq=1.0), product of:", 37 "details" : [ 38 { 39 "value" : 2.2, 40 "description" : "boost", 41 "details" : [] 42 }, 43 { 44 "value" : 0.2876821, 45 "description" : "idf, computed as log(1 +(N -n +0.5) / (n +0.5)) from:", 46 "details" : [ 47 { 48 "value" : 1, 49 "description" : "n, number of documents containing term", 50 "details" : [] 51 }, 52 { 53 "value" : 1, 54 "description" : "N, total number of documents with field", Elasticsearch权1 30 威指南 55 "details" : [] 56 } 57 ] 58 }, 59 { 60 "value" : 0.45454544, 61 "description" : "tf, computed as freq / (freq +k1 * (1 -b +b * dl / avgdl)) from:", 62 "details" : [ 63 { 64 "value" : 1.0, 65 "description" : "freq, occurrences of term within document", 66 "details" : [] 67 }, 68 { 69 "value" : 1.2, 70 "description" : "k1, term saturation parameter", 71 "details" : [] 72 }, 73 { 74 "value" : 0.75, 75 "description" : "b, length normalization parameter", 76 "details" : [] 77 }, 78 { 79 "value" : 1.0, 80 "description" : "dl, length of field", 81 "details" : [] 82 }, 83 { 84 "value" : 1.0, 85 "description" : "avgdl, average length of field", 86 "details" : [] 87 } 88 ] 89 } 90 ] 91 } 92 ] 93 } 94 } 95 ] 96 } 97 } 结果形式上比较复杂,里面最重要的内容就是对文档计算得到的总分以及总分的计算 过程。如果总分等于0,则该文档将不能匹配给定的查询。另一个重要内容是关于不同打 分项的描述信息,根据查询类型的不同,打分项会以不同方式对最后得分产生影响。 其中,最重要的两个因子,词频和文档频率: 第5章 搜索数据1 31 第49行,整个索引中出现kimchy的文档数量为1,即文档频率是1。 第65行,kimchy在字段user只出现了一次。词频是1。 5.4.2 折叠结果 允许基于字段值折叠(collapse)搜索结果。折叠是通过每个折叠键仅选择顶部排序的 文档来完成的。其实就是按照某个字段分组,每个分组只取一条结果。例如,下面的查询示 例为每个用户检索最好的tweet,并按喜欢的次数(likes字段)对其进行排序。 1 GET /twitter/_search 2 { 3 "query": { 4 "match": { 5 "message": "Elasticsearch" 6 } 7 }, 8 "collapse" : { 9 "field" : "user" 10 }, 11 "sort": ["likes"], 12 "from": 10 13 } 第9行,使用user字段折叠结果集。 第11行,按likes字段对文档进行排序。 第12行,返回结果的偏移量。 注意:响应中的命中total指示匹配文档的数量,是非折叠的结果。非重复组(折叠后 的数量)的总数是未知的。 用于折叠的字段必须是单值keyword或数字numeric字段,而且doc_values属性 开启。 1.展开折叠结果 可以使用inner_hits选项展开每个折叠的顶部结果。示例如下: 1 GET /twitter/_search 2 { 3 "query": { 4 "match": { 5 "message": "Elasticsearch" 6 } 7 }, 8 "collapse" : { 9 "field" : "user", 10 "inner_hits": { 11 "name": "last_tweet", 12 "size": 5, 13 "sort": [{ "date": "asc" }] Elasticsearch权1 32 威指南 14 }, 15 "max_concurrent_group_searches": 4 16 }, 17 "sort": ["likes"] 18 } 第9行,使用use字段折叠结果集。 第11行,响应中每个组内部展开结果使用的名称。 第12行,每个折叠键要检索的结果数,也就是每组返回多少个结果。 第13行,如何对每组中的文档进行排序。 第15行,允许每个组检索内部结果的并发请求数。 还可以为每个折叠组定义不同的展开请求参数。当希望获得折叠的多个表示形式时, 这很有用。示例如下: 1 GET /twitter/_search 2 { 3 "query": { 4 "match": { 5 "message": "Elasticsearch" 6 } 7 }, 8 "collapse" : { 9 "field" : "user", 10 "inner_hits": [ 11 { 12 "name": "most_liked", 13 "size": 3, 14 "sort": ["likes"] 15 }, 16 { 17 "name": "most_recent", 18 "size": 3, 19 "sort": [{ "date": "asc" }] 20 } 21 ] 22 }, 23 "sort": ["likes"] 24 } 第9行,使用user字段折叠结果集。 第11~15行,为此用户返回三条最喜欢的tweet。 第17~19行,为用户返回最近的三条tweet。 2.二级折叠 二级折叠也是支持的,并可应用于inner_hits。例如,下面的请求为每个国家(country 字段)查找得分最高的tweet,在每个国家内为每个用户查找得分最高的tweet。