二、 基础知识
6. 映射和分析
当摆弄索引里面的数据时,我们发现一些奇怪的事情。一些事情看起来被打乱了:在我们的索引中有12条推文,其中只有一条包含日期 2014-09-15
,但是看一看下面查询命中的 总数
(total):
GET /_search?q=2014 # 12 results
GET /_search?q=2014-09-15 # 12 results !
GET /_search?q=date:2014-09-15 # 1 result
GET /_search?q=date:2014 # 0 results !
为什么在 _all
字段查询日期返回所有推文,而在 date
字段只查询年份却没有返回结果?为什么我们在 _all
字段和 date
字段的查询结果有差别?
推测起来,这是因为数据在 all
字段与 date
字段的索引方式不同。所以,通过请求 gb
索引中 tweet
类型的_映射(或模式定义),让我们看一看 Elasticsearch 是如何解释我们文档结构的:
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"
}
}
}
}
}
}
-
基于对字段类型的猜测, Elasticsearch 动态为我们产生了一个映射。这个响应告诉我们
date
字段被认为是date
类型的。由于_all
是默认字段,所以没有提及它。但是我们知道_all
字段是string
类型的。 -
所以
date
字段和string
字段索引方式不同,因此搜索结果也不一样。这完全不令人吃惊。你可能会认为核心数据类型 strings、numbers、Booleans 和 dates 的索引方式有稍许不同。
6.1. 精确值 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
应该返回关于猎狐的故事。
- 搜索
6.2. 倒排索引
-
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
- 两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单 相似性算法 ,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。
-
我们目前的倒排索引有一些问题:
Quick
和quick
以独立的词条出现,然而用户可能认为它们是相同的词。fox
和foxes
非常相似, 就像dog
和dogs
;他们有相同的词根。jumped
和leap
, 尽管没有相同的词根,但他们的意思很相近。他们是同义词。
-
使用前面的索引搜索
+Quick +fox
不会得到任何匹配文档。(记住,+
前缀表明这个词必须存在。)只有同时出现Quick
和fox
的文档才满足这个查询条件,但是第一个文档包含quick fox
,第二个文档包含Quick foxes
。 -
如果我们将词条规范为标准模式,那么我们可以找到与用户搜索的词条不完全一致,但具有足够相关性的文档。例如:
Quick
可以小写化为quick
。foxes
可以 词干提取 --变为词根的格式-- 为fox
。类似的,dogs
可以为提取为dog
。jumped
和leap
是同义词,可以索引为相同的单词jump
。
-
现在索引看上去像这样:
Term Doc_1 Doc_2
-------------------------
brown | X | X
dog | X | X
fox | X | X
in | | X
jump | X | X
lazy | X | X
over | X | X
quick | X | X
summer | | X
the | X | X
------------------------
- 这还远远不够。我们搜索
+Quick +fox
仍然 会失败,因为在我们的索引中,已经没有Quick
了。但是,如果我们对搜索的字符串使用与content
域相同的标准化规则,会变成查询+quick +fox
,这样两个文档都会匹配!
6.3. 分析与分析器
分析 包含下面的过程:
- 首先,将一块文本分成适合于倒排索引的独立的 词条 ,
- 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall
分析器执行上面的工作。 分析器 实际上是将三个功能封装到了一个包里:
-
字符过滤器
首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将
&
转化成and
。 -
分词器
其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
-
Token 过滤器
最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化
Quick
),删除词条(例如, 像a
,and
,the
等无用词),或者增加词条(例如,像jump
和leap
这种同义词)。
Elasticsearch提供了开箱即用的字符过滤器、分词器和token 过滤器。 这些可以组合起来形成自定义的分析器以用于不同的目的。
6.3.1. 内置分析器
- 词条:
"Set the shape to semi-transparent by calling set_trans(5)"
-
标准分析器
标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写。它会产生
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
-
简单分析器
简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生
set, the, shape, to, semi, transparent, by, calling, set, trans
-
空格分析器
空格分析器在空格的地方划分文本。它会产生
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
-
语言分析器
特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。例如,
英语
分析器附带了一组英语无用词(常用单词,例如and
或者the
,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的 词干 。英语
分词器会产生下面的词条:set, shape, semi, transpar, call, set_tran, 5
注意看
transparent
、calling
和set_trans
已经变为词根格式。
6.3.2. 什么时候使用分析器
-
当查询的时候,需要进行一次相同的分析过程,保证我们搜索的词语和索引种的词条格式一致
-
date
域包含一个精确值:单独的词条2014-09-15
。 -
_all
域是一个全文域,所以分词进程将日期转化为三个词条:2014
,09
, 和15
。
当我们在 _all
域查询 2014
,它匹配所有的12条推文,因为它们都含有 2014
:
GET /_search?q=2014 # 12 results
当我们在 all
域查询 2014-09-15
,它首先分析查询字符串,产生匹配 2014
, 09
, 或 15
中 _任意 词条的查询。这也会匹配所有12条推文,因为它们都含有 2014
:
GET /_search?q=2014-09-15 # 12 results !
当我们在 date
域查询 2014-09-15
,它寻找 精确 日期,只找到一个推文:
GET /_search?q=date:2014-09-15 # 1 result
当我们在 date
域查询 2014
,它找不到任何文档,因为没有文档含有这个精确日志:
GET /_search?q=date:2014 # 0 results !
6.3.3. 测试分析器
有些时候很难理解分词的过程和实际被存储到索引中的词条,特别是你刚接触Elasticsearch。为了理解发生了什么,你可以使用 analyze
API 来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本:
GET /_analyze
{
"analyzer": "standard",
"text": "Text to analyze"
}
结果中每个元素代表一个单独的词条:
{
"tokens": [
{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 3
}
]
}
6.3.4. 指定分析器
- 当Elasticsearch在你的文档中检测到一个新的字符串域,它会自动设置其为一个全文
字符串
域,使用标准
分析器对它进行分析。
6.4. 映射
- 为了能够将时间域视为时间,数字域视为数字,字符串域视为全文或精确值字符串, Elasticsearch 需要知道每个域中数据的类型。这个信息包含在映射中。 Elasticsearch 支持如下简单域类型:
6.4.1. 核心简单域类型
- 字符串:
string
- 整数 :
byte
,short
,integer
,long
- 浮点数:
float
,double
- 布尔型:
boolean
- 日期:
date
当你索引一个包含新域的文档—之前未曾出现-- Elasticsearch 会使用 动态映射 ,通过JSON中基本数据类型,尝试猜测域类型,使用如下规则:
JSON type | 域 type |
---|---|
布尔型: true 或者 false | boolean |
整数: 123 | long |
浮点数: 123.45 | double |
字符串,有效日期: 2014-09-15 | date |
字符串: foo bar | string |
6.4.2. 查看映射
6.4.3. 自定义域映射
尽管在很多情况下基本域数据类型已经够用,但你经常需要为单独域自定义映射,特别是字符串域。自定义映射允许你执行下面的操作:
- 全文字符串域和精确值字符串域的区别
- 使用特定语言分析器
- 优化域以适应部分匹配
- 指定自定义数据格式
- 还有更多
域最重要的属性是 type
。对于不是 string
的域,你一般只需要设置 type
:
{
"number_of_clicks": {
"type": "integer"
}
}
默认, string
类型域会被认为包含全文。就是说,它们的值在索引前,会通过一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。
string
域映射的两个最重要属性是 index
和 analyzer
。
index
index
属性控制怎样索引字符串。它可以是下面三个值:
-
analyzed
首先分析字符串,然后索引它。换句话说,以全文索引这个域。
-
not_analyzed
索引这个域,所以它能够被搜索,但索引的是精确值。不会对它进行分析。
-
no
不索引这个域。这个域不会被搜索到。
string
域 index
属性默认是 analyzed
。如果我们想映射这个字段为一个精确值,我们需要设置它为 not_analyzed
:
{
"tag": {
"type": "string",
"index": "not_analyzed"
}
}
analyzer
- 对于
analyzed
字符串域,用analyzer
属性指定在搜索和索引时使用的分析器。 - 默认, Elasticsearch 使用
standard
分析器, 但你可以指定一个内置的分析器替代它,例如whitespace
、simple
和english
:
{
"tweet": {
"type": "string",
"analyzer": "english"
}
}