搜索 – 基本工具
到目前为止,我们已经学习了Elasticsearch的分布式NOSQL文档存储,我们可以直接把JSON文档扔到Elasticsearch中,然后直接通过ID来进行调取。但是Elasticsearch真正的强大之处在于将混乱变得有意义——将大数据变成大量的信息。
这也是我们使用JSON文档而不是无规则数据的原因。Elasticsearch不仅仅只是存储文档,同时它还索引了这些文档以便搜索。文档中每一个字段都被索引并且可以被查询。不仅如此,在一个查询中,Elasticsearch可以使用所有索引,并且以惊人的速度返回结果。这是传统数据库永远也不能企及的。
这个搜索可以是:
- 类似于
年龄
、性别
、加入日期
等结构化数据,类似于在SQL中进行查询。 - 全文搜索,查找整个文档中匹配关键字的内容,并根据相关性
- 或者结合两者。
虽然很多搜索操作是安装好Elasticsearch就可以用的,但是想发挥它的潜力,你需要明白以下内容:
名字 | 说明 |
---|---|
映射 (Mapping) | 每个字段中的数据如何被解释 |
统计 (Analysis) | 可搜索的全文是如何被处理的 |
查询 (Query DSL) | Elasticsearch使用的灵活强的查询语言 |
上述的每一个内容都是一个大的主题,我们将会在之后的《深入搜索》中详细探讨它们。 本章中我们将针对先去介绍它们三个的基本概念 —— 已经足够能帮助你理解搜索是如何运作的了。
我们将向你介绍search
API的简单实用方式。
测试数据
我们本章使用的文档可以在下面的git中找到:https://gist.github.com/clintongormley/8579281
你可以下载然后导入到你的shell中以方便你的学习使用。
5.1 空白搜索
空白搜索
搜索API最常用的一种形式就是空白搜索,也就是不加任何查询条件的,只是返回集群中所有文档的搜索。
GET /_search
返回内容如下(有删减):
{
"hits" : {
"total" : 14,
"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 个结果被隐藏 ... ], "max_score" : 1 }, "took" : 4, "_shards" : { "failed" : 0, "successful" : 10, "total" : 10 }, "timed_out" : false }
hits
返回内容中最重要的内容就是hits
,它指明了匹配查询的文档的总数
,hits
数组里则会包含前十个匹配文档——也就是搜索结果。
hits
数组中的每一条结果都包含了文档的_index
, _type
以及_id
信息,以及_source
字段。这也就意味着你可以直接从搜索结果中获取到整个文档的内容。这与其他搜索引擎只返回给你文档编号,还需要自己去获取文档是截然不同的。
每一个元素还拥有一个_score
字段。这个是相关性评分,这个数值表示当前文档与查询的匹配程度。通常来说,搜索结果会先返回最匹配的文档,也就是说它们会按照_score
由高至低进行排列。在这个例子中,我们并没有声明任何查询,因此_score
就都会返回1
max_score
数值会显示所有匹配文档中的_score
的最大值。
took
took
数值告诉我们执行这次搜索请求所耗费的时间有多少毫秒。
shards
_shards
告诉了我们参与查询分片的总数,以及有多少successful
和failed
。通常情况下我们是不会得到失败的反馈,但是有的时候它会发生。如果我们的服务器突然出现了重大事故,然后我们丢失了同一个分片中主从两个版本的数据。在查询请求中,无法提供可用的备份。这种情况下,Elasticsearch就会返回`failed提示,但是它还会继续返回剩下的内容。
timeout
timed_out
数值告诉了我们查询是否超时。通常,搜索请求不会超时。如果相比完整的结果你更需要的是快速的响应时间,这是你可以指定timeout
值,例如10
、"10ms"
(10毫秒)或者"1s"
(1秒钟):
GET /_search?timeout=10ms
Elasticsearch会尽可能地返回你指定时间内它所查到的内容。
Timeout并不是终止者
这里应该强调一下timeout
并不会终止查询,它只是会在你指定的时间内返回当时已经查询到的数据,然后关闭连接。在后台,其他的查询可能会依旧继续,尽管查询结果已经被返回了。
使用超时是因为你要保障你的品质,并不是因为你需要终止你的查询。
5.2 多索引多类型
多索引,多类型
你是否注意到了《空白搜索》一章节的文档中包含了很多不同的类型 —— user
与tweet
,它们也分别来自us
、gb
这两个不同的索引?
当我们没有特别指定一个索引或者类型的时候,我们将会搜索整个集群中的所有文档。Elasticsearch会把搜索请求转发给集群中的每一个主从分片,然后按照结果的相关性得到前十名,并将它们返回给我们。
然而,往往我们只需要在某一个特定的索引的几个类型中进行搜索。我们可以通过在URL中定义它来实现这个功能:
URL | 说明 |
---|---|
/_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 内的所有文档 |
当你在一个索引中搜索的时候,Elasticsearch或将你的搜索请求转发给相应索引中的所有主从分片,然后收集每一个分片的结果。在多个索引中搜索也是相同的流程,只不过是增加了一些参与分片。
重要提示
搜索一个拥有五个主分片的索引与搜索五个都只拥有一个主分片是完全一样的。
在后面,你将会了解到如何利用这一点,来根据你的需要灵活打造系统。
5.3 分页
分页
在《空白搜索》一节中,搜索结果告诉我们在集群中共有14个文档匹配我们的(空白)查询。但是在hits
数组中只有10个文档。我们怎样才能看到其他的呢?
与SQL使用LIMIT
来控制单“页”数量类似,Elasticsearch使用的是from
以及size
两个参数:
参数 | 说明 |
---|---|
size | 每次返回多少个结果,默认值为10 |
from | 忽略最初的几条结果,默认值为0 |
假设每页显示5条结果,那么1至3页的请求就是:
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
当心不要一次请求过多或者页码过大的结果。它们会在返回前排序。一个请求会经过多个分片。每个分片都会生成自己的排序结果。然后再进行集中整理,以确保最终结果的正确性。
分布式系统中的大页码页面
为了说明白为什么页码过大的请求会产生问题,我们就先预想一下我们在搜索一个拥有5个主分片的索引。当我们请求第一页搜索的时候,每个分片产生自己前十名,然后将它们返回给请求节点,然后这个节点会将50条结果重新排序以产生最终的前十名。
现在想想一下我们想获得第1,000页,也就是第10,001到第10,010条结果,与之前同理,每一个分片都会先产生自己的前10,010名,然后请求节点统一处理这50,050条结果,然后再丢弃掉其中的50,040条!
现在你应该明白了,在分布式系统中,大页码请求所消耗的系统资源是呈指数式增长的。这也是为什么网络搜索引擎不会提供超过1,000条搜索结果的原因。
TIP
在《重索引》一章中,我们将详细探讨如何才能高效地获取大量数据。
5.4 查询语句
精简 搜索
搜索的API分为两种:其一是通过参数来传递查询的“精简版”查询语句(query string),还有一种是通过JSON来传达丰富的查询的完整版请求体(request body),这种搜索语言被称为查询DSL。
查询语句在行命令中运行点对点查询的时候非常实用。比如我想要查询所有tweet
类型中,所有tweet
字段为"elasticsearch"
的文档:
GET /_all/tweet/_search?q=tweet:elasticsearch
下一个查询是想要寻找name
字段为"john"
且tweet
字段为"mary"
的文档,实际的查询就是:
+name:john +tweet:mary
但是经过百分号编码(percent encoding)处理后,会让它看起来稍显神秘:
GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary
前缀"+"
表示必须要满足我们的查询匹配条件,而前缀"-"
则表示绝对不能匹配条件。没有+
或者-
的表示可选条件。匹配的越多,文档的相关性就越大。
字段_all
下面这条简单的搜索将会返回所有包含"mary"
字符的文档:
GET /_search?q=mary
在之前的例子中,我们搜索tweet
或者name
中的文字。然而,搜索的结果显示"mary"
在三个不同的字段中:
-
- 用户的名字为"Mary"
- 6个"Mary"发送的推文
- 1个"@mary"
那么Elasticsearch是如何找到三个不同字段中的内容呢?
当我们在索引一个文档的时候,Elasticsearch会将所有字段的数值都汇总到一个大的字符串中,并将它索引成一个特殊的字段_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"
除非指定了字段名,不然查询语句就会搜索字段_all
。
TIP: 在你刚开始创建程序的时候你可能会经常使用_all
这个字段。但是慢慢的,你可能就会在请求中指定字段。当字段_all
已经没有使用价值的时候,那就可以将它关掉。之后的《字段all》一节中将会有介绍
更加复杂的查询
再实现一个查询:
-
- 字段
name
包含"mary"
或"john"
date
大于2014-09-10
_all
字段中包含"aggregations"
或"geo"
- 字段
+name:(mary john) +date:>2014-09-10 +(aggregations geo)
最终处理完的语句可读性可能很差:
?q=%2Bname%3A(mary+john)+%2Bdate%3A%3E2014-09-10+%2B(aggregations+geo)
正如你所看到的,这个简明查询语句是出奇的强大。在查询语句语法中,有关于它详细的介绍。借助它我们就可以在开发的时候提高很多效率。
不过,你也会发现简洁带来的易读性差和难以调试,以及它的脆弱:当其中出现-
, :
, /
或者 "
时,它就会返回错误提示。
最后要提一句,任何用户都可以通过查询语句来访问臃肿的查询,或许会得到一些私人的信息,或许会通过大量的运算将你的集群压垮!
TIP
出于以上原因,我们不建议你将查询语句直接暴露给用户,除非是你信任的可以访问数据与集群的权限用户。
与此同时,在生产环境中,我们经常会使用到查询语句。在了解更多关于搜索的知识前,我们先来看一下它是怎样运作的。