只列出了感觉重要的部分,完整内容请查看这里。
适用于Elasticsearch 2.x版本
添加故障转移
当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。
当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的 cluster.name
配置,它就会自动发现集群并加入到其中。 但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。
当第二个节点加入到集群后,3个副本分片将会分配到这个节点上——每个主分片对应一个副本分片。 这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。
所有新近被索引的文档都将会保存在主分片上,然后被并行的复制到对应的副本分片上。这就保证了我们既可以从主分片又可以从副本分片上获得文档。
搜索——最基本的工具
文档中的每个字段都将被索引并且可以被查询 。
搜索(search)可以做到:
- 在类似于 gender 或者 age 这样的字段上使用结构化查询,join_date 这样的字段上使用排序,就像SQL的结构化查询一样。
- 全文检索,找出所有匹配关键字的文档并按照相关性(relevance)排序后返回结果。
- 以上二者兼而有之。
很多搜索都是开箱即用的,为了充分挖掘Elasticsearch的潜力,你需要理解以下三个概念:
映射(Mapping)
- 描述数据在每个字段内如何存储
分析(Analysis)
- 全文是如何处理使之可以被搜索的
领域特定查询语言(Query DSL)
- Elasticsearch中强大灵活的查询语言
timeout
timed_out
值告诉我们查询是否超时。默认情况下,搜索请求不会超时。 如果低响应时间比完成结果更重要,你可以指定 timeout 为 10 或者 10ms(10毫秒),或者 1s(1秒):
GET /_search?timeout=10ms
在请求超时之前,Elasticsearch 将会返回已经成功从每个分片获取的结果。
应当注意的是 timeout 不是停止执行查询,它仅仅是告知正在协调的节点返回到目前为止收集的结果并且关闭连接。在后台,其他的分片可能仍在执行查询即使是结果已经被发送了。
使用超时是因为SLA(服务等级协议)对你是很重要的,而不是因为想去中止长时间运行的查询。
多索引,多类型
如果不对某一特殊的索引或者类型做限制,就会搜索集群中的所有文档。Elasticsearch 转发搜索请求到每一个主分片或者副本分片,汇集查询出的前10个结果,并且返回给我们。
/_search
在所有的索引中搜索所有的类型
/gb/_search
在 gb 索引中搜索所有的类型
/gb,us/_search
在 gb 和 us 索引中搜索所有的文档
/g*,u*/_search
在任何以 g 或者 u 开头的索引中搜索所有的类型
分页
和 SQL 使用 LIMIT
关键字返回单个 page 结果的方法相同,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 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。
现在假设我们请求第 1000 页--结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。
可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。这就是 web 搜索引擎对任何查询都不要返回超过 1000 个结果的原因。
“轻量”搜索
有两种形式的 搜索 API:一种是 “轻量的” 查询字符串 版本,要求在查询字符串中传递所有的 参数,另一种是更完整的请求体版本,要求使用 JSON 格式和更丰富的查询表达式作为搜索语言。
查询字符串搜索非常适用于通过命令行做即席查询。例如,查询在 tweet 类型中 tweet 字段包含 elasticsearch 单词的所有文档:
GET /_all/tweet/_search?q=tweet:elasticsearch
但是查询字符串参数所需要的百分比编码(译者注:URL编码)实际上更加难懂:
GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary
查询字符串搜索允许任何用户在索引的任意字段上执行可能较慢且重量级的查询,这可能会暴露隐私信息,甚至将集群拖垮。因为这些原因,不推荐直接向用户暴露查询字符串搜索功能,除非对于集群和数据来说非常信任他们。
相反,我们经常在生产环境中更多地使用功能全面的 request body 查询API,除了能完成以上所有功能,还有一些附加功能。
映射和分析
让我们看一看 Elasticsearch 是如何解释我们文档结构的:
{
"gb": {
"mappings": {
"tweet": {
"properties": {
"date": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"name": {
"type": "string"
},
"tweet": {
"type": "string"
},
"user_id": {
"type": "long"
}
}
}
}
}
}
基于对字段类型的猜测, Elasticsearch 动态为我们产生了一个映射。这个响应告诉我们 date
字段被认为是 date
类型的。由于 _all
是默认字段,所以没有提及它。但是我们知道 _all
字段是 string
类型的。
所以 date
字段和 string
字段 索引方式不同,因此搜索结果也不一样。这完全不令人吃惊。你可能会认为核心数据类型 strings、numbers、Booleans 和 dates 的索引方式有稍许不同。没错,他们确实稍有不同。
但是,到目前为止,最大的差异在于 代表 精确值 (它包括 string
字段)的字段和代表 全文 的字段。这个区别非常重要——它将搜索引擎和所有其他数据库区别开来。
在Elasticsearch 5.X版本中有变化。
精确值 VS 全文
Elasticsearch 中的数据可以概括的分为两类:精确值和全文。
精确值如它们听起来那样精确。例如日期或者用户 ID,但字符串也可以表示精确值,例如用户名或邮箱地址。对于精确值来讲,Foo 和 foo 是不同的,2014 和 2014-09-15 也是不同的。
另一方面,全文是指文本数据(通常以人类容易识别的语言书写),例如一个推文的内容或一封邮件的内容。
精确值很容易查询。结果是二进制的:要么匹配查询,要么不匹配。这种查询很容易用 SQL 表示:
WHERE name = "John Smith"
AND user_id = 2
AND date > "2014-09-15"
查询全文数据要微妙的多。我们问的不只是“这个文档匹配查询吗”,而是“该文档匹配查询的程度有多大?”换句话说,该文档与给定查询的相关性如何?
我们很少对全文类型的域做精确匹配。相反,我们希望在文本类型的域中搜索。不仅如此,我们还希望搜索能够理解我们的意图 :
- 搜索 UK ,会返回包含 United Kindom 的文档。
- 搜索 jump ,会匹配 jumped , jumps , jumping ,甚至是 leap 。
- 搜索 johnny walker 会匹配 Johnnie Walker , johnnie depp 应该匹配 Johnny Depp 。
- fox news hunting 应该返回福克斯新闻( Foxs News )中关于狩猎的故事,同时, fox hunting news 应该返回关于猎狐的故事。
为了促进这类在全文域中的查询,Elasticsearch首先分析文档,之后根据结果创建 倒排索引 。
倒排索引
Elasticsearch 使用一种称为倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
例如,假设我们有两个文档,每个文档的 content
域包含如下内容:
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
为了创建倒排索引,我们首先将每个文档的 content
域拆分成单独的词(我们称它为词条或tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:
Term Doc_1 Doc_2
-------------------------
Quick | | X
The | X |
brown | X | X
dog | X |
dogs | | X
fox | X |
foxes | | X
in | | X
jumped | X |
lazy | X | X
leap | | X
over | X | X
quick | X |
summer | | X
the | X |
------------------------
现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:
Term Doc_1 Doc_2
-------------------------
brown | X | X
quick | X |
------------------------
Total | 2 | 1
两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单 相似性算法,那么我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。
分析与分析器
分析包含下面的过程:
- 首先,将一块文本分成适合于倒排索引的独立的词条
- 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者
recall
分析器执行上面的工作。 分析器实际上是将三个功能封装到了一个包里:
字符过滤器
- 首先,字符串按顺序通过每个字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将
&
转化成and
。
分词器
- 其次,字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
Token 过滤器
- 最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像
a
,and
,the
等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。
Elasticsearch提供了开箱即用的字符过滤器、分词器和token 过滤器。 这些可以组合起来形成自定义的分析器以用于不同的目的。
内置分析器
但是, Elasticsearch还附带了可以直接使用的预包装的分析器。 接下来我们会列出最重要的分析器。为了证明它们的差异,我们看看每个分析器会从下面的字符串得到哪些词条:
"Set the shape to semi-transparent by calling set_trans(5)"
1、标准分析器
标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟定义的单词边界划分文本。删除绝大部分标点。最后,将词条小写。它会产生:
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
2、简单分析器
简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生
set, the, shape, to, semi, transparent, by, calling, set, trans
3、空格分析器
空格分析器在空格的地方划分文本。它会产生
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
4、语言分析器
特定语言分析器可用于很多语言。它们可以考虑指定语言的特点。例如,英语 分析器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的词干。
英语 分词器会产生下面的词条:
set, shape, semi, transpar, call, set_tran, 5
注意看 transparent
、 calling
和 set_trans
已经变为词根格式。
什么时候使用分析器
当我们 索引 一个文档,它的全文域被分析成词条以用来创建倒排索引。 但是,当我们在全文域搜索的时候,我们需要将查询字符串通过相同的分析过程 ,以保证我们搜索的词条格式与索引中的词条格式一致。
全文查询理解每个域是如何定义的,因此它们可以做正确的事:
- 当你查询一个 全文 域时,会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。
- 当你查询一个 精确值 域时,不会分析查询字符串,而是搜索你指定的精确值。
现在你可以理解在开始章节的查询为什么返回那样的结果:
date
域包含一个精确值:单独的词条 2014-09-15
。
_all
域是一个全文域,所以分词进程将日期转化为三个词条: 2014
, 09
, 和 15
。
当我们在 _all
域查询 2014
,它匹配所有的12条推文,因为它们都含有 2014
。
映射
为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。这个信息包含在映射中。
Elasticsearch 支持 如下简单域类型:
- 字符串:
string
- 整数 :
byte
,short
,integer
,long
- 浮点数:
float
,double
- 布尔型:
boolean
- 日期:
date
当你索引一个包含新域的文档--之前未曾出现-- Elasticsearch 会使用 动态映射 ,通过JSON中基本数据类型,尝试猜测域类型,使用如下规则:
JSON类型 | 域类型 |
---|---|
布尔型: true 或者 false |
boolean |
整数: 123 | long |
浮点数: 123.45 | double |
字符串,有效日期: 2014-09-15 | date |
字符串: foo bar | string |
注意:这些类型在Elasticsearch 5.X版本中有变化,以后会更新。
这意味着如果你通过引号( "123" )索引一个数字,它会被映射为 string
类型,而不是 long
。但是,如果这个域已经映射为 long
,那么 Elasticsearch 会尝试将这个字符串转化为 long
,如果无法转化,则抛出一个异常。
查看映射
通过 /_mapping
,我们可以查看 Elasticsearch 在一个或多个索引中的一个或多个类型的映射 。取得索引 gb 中类型 tweet 的映射:
GET /gb/_mapping/tweet
返回结果:
{
"gb": {
"mappings": {
"tweet": {
"properties": {
"date": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
},
"name": {
"type": "string"
},
"tweet": {
"type": "string"
},
"user_id": {
"type": "long"
}
}
}
}
}
}
错误的映射,例如 将 age 域映射为 string
类型,而不是 integer
,会导致查询出现令人困惑的结果。
自定义域映射
自定义映射允许你执行下面的操作:
- 全文字符串域和精确值字符串域的区别
- 使用特定语言分析器
- 优化域以适应部分匹配
- 指定自定义数据格式
- 还有更多
域最重要的属性是 type
。对于不是 string
的域,你一般只需要设置 type
:
{
"number_of_clicks": {
"type": "integer"
}
}
默认, string
类型域会被认为包含全文。也就是说它们的值在索引前会通过 一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。
string
域映射的两个最重要的属性是 index
和 analyzer
。
index
index
属性控制怎样索引字符串。它可以是下面三个值:
analyzed(默认)
- 首先分析字符串,然后索引它。换句话说,以全文索引这个域。
not_analyzed
- 索引这个域,所以可以搜索到它,但索引指定的精确值。不对它进行分析。
no
- 不索引这个域。这个域不会被搜索到。
如果我们想映射string
类型域为一个精确值,我们需要设置它的index
属性为 not_analyzed
:
{
"tag": {
"type": "string",
"index": "not_analyzed"
}
}
其他简单类型(例如 long
, double
, date
等)也接受 index
参数,但有意义的值只有 no
和 not_analyzed
, 因为它们永远不会被分析。
analyzer
对于index
被设置为analyzed
的字符串域,用analyzer
属性指定在搜索和索引时使用的分析器。默认, Elasticsearch 使用 standard
分析器, 但你可以指定一个内置的分析器替代它,例如 whitespace
、 simple
和 english
:
{
"tweet": {
"type": "string",
"analyzer": "english"
}
}
更新映射
当你首次创建一个索引的时候,可以指定类型的映射。你也可以使用 /_mapping
为新类型增加映射。
尽管你可以增加一个映射,你不能修改 存在的域映射。如果一个域的映射已经存在,那么该域的数据可能已经被索引。如果你意图修改这个域的映射,索引的数据可能会出错,不能被正常的搜索。
我们决定在 tweet 映射增加一个新的名为 tag 的 not_analyzed
的文本域,使用 _mapping
:
PUT /gb/_mapping/tweet
{
"properties" : {
"tag" : {
"type" : "string",
"index": "not_analyzed"
}
}
}
注意,我们不需要再次列出所有已存在的域,因为无论如何我们都无法改变它们。新域已经被合并到存在的映射中。
复杂类型
除了我们提到的简单标量数据类型, JSON 还有 null
值,数组,和对象,这些 Elasticsearch 都是支持的。
数组
很有可能,我们希望 tag 域包含多个标签。我们可以以数组的形式索引标签:
{ "tag": [ "search", "nosql" ]}
对于数组,没有特殊的映射需求。任何域都可以包含0、1或者多个值。
这暗示数组中所有的值必须是相同数据类型的 。你不能将日期和字符串混在一起。如果你通过索引数组来创建新的域,Elasticsearch 会用数组中第一个值的数据类型作为这个域的类型。
未完,待续...