前边的学习,我们已经可以将ElasticSearch作为一个分布式存储系统使用,但是ES的真正强大之处在于可以在混乱的数据中找出有意义的信息.
每个文档里的字段都会被索引并被查询,关于搜索Search,可以做:
结构化查询:比如在gender性别和年龄age这样的字段上使用结构化查询,在join_date这样的字段上使用排序.
全文查询:可以使用所有字段来匹配关键字,然后按照关联性relevance排序返回结果.
也可以两者结合使用.
搜索都是开箱即用的,为了深入了解ES的潜力,需要三个概念:
映射Mapping:数据在每个字段中的解释说明
分析Analysis:全文是如何被处理,被搜素的
特定领域语言查询Query DSL(Domian Specific Language):ES灵活强大的查询语言.
这三个概念每一个都是巨大的话题,后续详细学习,本章节简单介绍.
下边详细学习search API
1. 空搜索
最基本的search API是空搜索empty search,它没有指定任何搜索条件,返回的是集群索引中所有的文档.
GET /_search
响应内容类似于:
{
"took": 15,
"timed_out": false,
"_shards": {
"total": 15,
"successful": 15,
"failed": 0
},
"hits" : {
"total" : 14,
"max_score" : 1
"hits" : [
{
"_index": "us",
"_type": "tweet",
"_id": "7",
"_score": 1,
"_source": {
"date": "2014-09-17",
"name": "John Smith",
"tweet": "The Query DSL is really powerful and flexible",
"user_id": 2
}
},
... 9 RESULTS REMOVED ...
],
},
}
hits字段是响应中最重要的一部分,它包含total字段来表示匹配到的文档总数;max_score是所有匹配文档中相关性最高的值;hits数组包含了匹配到的前10条数据,数组的每个元素代表一个文档,其中_score为文档相关性指标,因为是空搜索,所以所有文档的_score都为1;
took字段,指请求花费的时间,单位为毫秒;
_shards代表参与查询的分片数的情况,total是参与的总分片数,successful是成功的分片数,通常我们不希望分片失败,但也有可能发生,如果硬件遭受大的故障导致主分片和复制分片都失效,那么这个分片将无法响应请求,ES将该分配报告为failed,但仍然继续返回剩余分片上的结果.
timed_out告诉我们查询是否超时;一般情况不会超时;如果要求的响应速度比结果更重要,可以在搜索的时候指定时间限,为10或者10ms,或者1s
GET /_search?timeout=10ms
ES将返回在时间限前查询到的结果.
注意:超过指定的timeout之后,不会停止执行查询,它仅顺利返回目前查询到的结果,然后关闭连接.在后台其他分片可能依旧查询操作;
使用超时是因为对业务的响应速度需求比结果重要,但并不是中断需要长时间进行的查询.
2. 多索引多类别搜索
通过限制搜索的索引或类型,可以在集群中跨所有文档搜索.ES通过转发请求到集群中主分片和复制分片上,收集结果后选择顶部的10个结果返回给客户端.
类型user属于索引us,类型tweet属于索引gb
根据索引和类型多样的搜索方式:
/_search:在所有索引的所有类型中搜索,即空搜索
/gb/_search:在索引gb的所有类型中搜索
/gb,us/_search:在索引gb和us的所有类型中搜索
/g*,u*/_search:在所有以g和u为首字母的索引的所有类型中搜索
/gb/user/_search:在索引gb的user类型中搜索
/gb,us/user,tweet/_search:在索引gb和us的user和tweet类型中搜索
/_all/user,tweet/_search:在所有的索引的user,tweet类型中搜索
当节点收到搜索请求后,节点转发请求到索引的主分片和复制分片上,然后收集每个分片的结果.
3. 分页
ES默认只在响应中返回top10的相关文档,如何更具应用的需要返回其他文档呢?就像SQL中LIMIT关键词的功效一样.
ES的请求URL接受size和from参数,
size:结果数,默认为10
from :跳过的数目,默认为0
所以在没指定size和from参数时,返回开始的10个相关文档.
如果显示每页五个结果,页码从1到3:
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
这里需要注意的问题,注意分页太深,和一次请求结果太多的问题.
一个请求常涉及到多个分片,每个分片都会生成自己的排序结果,它们需要再次集中起来排序以返回请求需要的结果给客户端.
深度分页存在的:
假设有五个分片,每一页返回10个结果,那么在请求第一页时(也就是top10的结果),每个分片需要各自产生10个结果,然后每个分片把产生的这10个结果返回个请求节点,请求节点对收到的10*5=50个结果再进行排序,选择前10个返回客户端;
但是当请求第1001页时,也就是请求(10000-10010)的结果,同样每个分片产生10010个结果,请求节点收到10010*5=50050的结果,请求节点对这50050个结果进行排序,然后返回第1000-10010的结果;
显然排序的花费随着分页的加深而成倍增长.
4. query-string方法
search API有两种方式:
精简版的,查询字符串query-string方法,通过在请求URL后加参数的方式;
请求体方式,使用一个JSON格式的请求体,这种成为DSL查询语言.
查询字符串方法在命令行下运行即席查询ad hoc queries特别有用.
查询所有类型为tweet,并且字段tweet中包含elasticsearch字符的文档:
GET /_all/tweet/_search?q=tweet:elasticsearch
如果还想查找name中包含join,tweet中包含mary的文档:
GET /_all/tweet/_search?q=name:join+tweet:mary
使用+符号连接,结果集为两个条件的合集,即有的结果文档中只匹配一个条件.
如果要是选择不满足条件的,比如name不包含join的:
GET /_all/tweet/_search?q=-name:join
比如,name包含join但tweet不包含jmary的:
GET /_all/tweet/_search?q=name:join+-tweet:mary
注意使用+号连接 -tweet:mary
_all字段:
返回所有包含mary字符串的文档,无论是在哪个字段中包含,只有有就返回:
GET /_search?q=mary
那么ES是如何实现这功能的呢?实际上文档有一个特殊的_all字段,该字段把所有的字段的数据连接成以一个大的字符串,匹配时通过_all字段进行匹配.
例如文档内容:
{
"tweet": "However did I manage before Elasticsearch?",
"date": "2014-09-14",
"name": "Mary Jones",
"user_id": 1
}
这个_all字段为:
"However did I manage before Elasticsearch? 2014-09-14 Mary Jones 1"
实际上请求就是:
GET /_search?q=_all:mary
结果是一样的.
更复杂的搜索
搜索要求:
name
字段包含"mary"
或"john"
date
晚于2014-09-10
_all
字段包含"aggregations"
或"geo"
GET /_search?q=name:(mary john) +date:>2014-09-10 +(aggregations geo)
但实际上(mary join)中间的空格并不正确,使用+号反而可以运行,日期的比较也是错误的:
GET /_search?q=name:(mary+john) +(aggregations+geo)
不知道日期如何比较...
可以看出字符串查询是非常强大的,它在命令行下一次性查询或者开发模式下非常有用.
但也可以看出,其是非常脆弱的,一点的符号错误都有可能到是查询失败.
还有就是安全性问题,不建议直接暴露查询字符串给用户,除非用户对集群可信.
所以取而代之,我们一般使用全功能的请求体API的方式,在学习请求体时,先了解数据是如何在ES中被索引的.