elasticsearch(6) 映射和分析

类似关系型数据库中每个字段都有对应的数据类型,例如nvarchar、int、date等等,elasticsearch也会将文档中的字段映射成对应的数据类型,这一映射可以使ES自动生成的,也是可以由我们自定义的。不同的映射关系是会影响到我们的搜索查询功能。

GET /_search?q=              #  results
GET /_search?q=-- # results !
GET /_search?q=date:-- # result
GET /_search?q=date: # results !

分析上面的查询语句,我们可以发现一个很奇怪的现象。为什么通过q=2014可以查到12条结果,但通过date字段查询年份q=date:2014却没有结果呢?我们推测就是因为不同的字段映射的类型不同而导致的。

  • 使用分析工具

通过_mapping api可以分析类型的映射

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"
}
}
}
}
}
}

我们可以看到date字段是date类型的,而q=2014查询的是_all字段,我们知道这个字段是String类型的。我们可能会猜测 date 字段和 string 字段 索引方式不同,所以搜索结果也不一样。

但实际上虽然不同的数据类型的索引方式会有所不同,但最大的区别是一个是代表精确值的字段(包括String类型),另一个是代表全文的字段。这个区别非常重要,这正是ES和其他关系型数据库最大的不同。ES会将全文域的所有词条形成倒排索引,这也是ES搜索的基础。

  • 倒排索引

elsaticsearch使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

例如,假设我们有两个文档,每个文档的 content 域包含如下内容:

  1. The quick brown fox jumped over the lazy dog
  2. 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 | |

由上图可得知,匹配的文档有两个,但是第一个文档要比第二个匹配度更好。但这也引发出了一些问题。

对于一个搜索操作来说:

1、用户可能不关心词条的大小写,Quick和quick可能是一样的

2、由于词法的变化,例如单复数的变化,对于用户来说可能也是不关心的,例如dog和dogs

3、同义词,一些同义词尽管不同,但实际上表达的含义是相同的,我们希望能够匹配出来。

解决方案:

对于输入和输出事先做预处理,也就是在倒排索引前对文档统一处理,例如把Quick统一成quick,把dogs统一成dog。搜索时,对搜索的内容也做统一处理,例如Quick dogs转化成quick dog。这样通过倒排索引就可以解决上面几个问题,优化搜索。这个过程就是分析。

  • 分析和分析器

所谓分析器实际上是把三个功能封装在了一个包里

1、字符过滤器

首先,字符串按顺序通过每个字符过滤器。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 & 转化成 `and`。

2、分词器

字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。

3、Token过滤器

词条按顺序通过每个Token过滤器。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a,and,the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)

  • 内置分析器

ES附带了可以直接使用的预包装的分析器

示例文本

"Set the shape to semi-transparent by calling set_trans(5)"

1、标准分析器

标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据Unicode联盟定义的单词边界划分文本。删除绝大部分标点。最后,将词条小写。

set, the, shape, to, semi, transparent, by, calling, set_trans, 

2、简单分析器

简单分析器在任何不是字母的地方分隔文本,将词条小写。

set, the, shape, to, semi, transparent, by, calling, set, trans

3、空格分析器

空格分析器在任何不适字母的地方分割文本,将词条小写。

Set, the, shape, to, semi-transparent, by, calling, set_trans()

4、语言分析器

特定语言分析器可用于很多语言。它们可以考虑指定语言的特点。例如, 英语分析器附带了一组英语无用词(常用单词,例如 and 或者the,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的词干。

set, shape, semi, transpar, call, set_tran, 

这里set_trans变为了set_tran,calling变为了call

  • 测试分析器

有些时候很难理解分词的过程和实际被存储到索引中的词条,你可以使用 analyze API 来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本:

GET 127.0.0.1:9200/_analyze

{
"analyzer": "standard",
"text": "our Fingertips owns the power of changing the world"
}

响应

{
"tokens": [
{
"token": "our",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "fingertips",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "owns",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "the",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "power",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "of_changing",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "the",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
},
{
"token": "world",
"start_offset": ,
"end_offset": ,
"type": "<ALPHANUM>",
"position":
}
]
}

从中我们可以看到分析器是怎么帮我们分词的。token 是实际存储到索引中的词条。 position 指明词条在原始文本中出现的位置。 start_offset 和end_offset 指明字符在原始字符串中的位置。

  • 核心简单域类型

elasticsearch支持一下简单域类型

1、字符串:string

2、整数:byte,short,integer,long

3、浮点型:float,double

4、布尔型:boolean

5、日期:date

当你索引一个包含新域的文档(之前未曾出现), Elasticsearch 会使用动态映射,通过JSON中基本数据类型,尝试猜测域类型,使用如下规则:

JSON type

域 type

布尔型: true 或者 false

boolean

整数: 123

long

浮点数: 123.45

double

字符串,有效日期: 2014-09-15

date

字符串: foo bar

string

这意味着如果通过“123”索引一个数字,那么它会被映射成为string,但如果这个域已经是long,那么ES会尝试将这个字符串转化为long,如果无法转化,则抛出一个异常。

  • 自定义域映射

自定义映射可以做到

1、全文字符串域和精确值字符串域的区别

2、使用特定的语言分析器

3、优化域以适应部分匹配

4、指定自定义数据格式

域属性:

  • type

域最重要的属性是type.对于不是string的域,一般只需要设置type

{
"number_of_clicks": {
"type": "integer"
}
}

默认, string 类型域会被认为包含全文。就是说,它们的值在索引前,会通过 一个分析器,针对于这个域的查询在搜索前也会经过一个分析器。

  • index

index属性控制了怎样所以字符串,值可以为:

1、analyzed

首先分析字符串,然后索引它。换句话说,以全文索引这个域。

2、not_analyzed

索引这个域,所以可以搜索到它,但索引指定的精确值。不对它进行分析。

3、no

这个域不会被搜索到。

{
"tag": {
"type": "string",
"index": "not_analyzed"
}
}
  • analyzer

对于 analyzed 字符串域,用 analyzer 属性指定在搜索和索引时使用的分析器。默认, Elasticsearch 使用 standard 分析器, 但你可以指定一个内置的分析器替代它,例如 whitespace 、 simple 和 english:

{
"tweet": {
"type": "string",
"analyzer": "english"
}
}

更新映射

首次 创建一个索引的时候,可以指定类型的映射。你也可以使用 /_mapping 为新类型(或者为存在的类型更新映射)增加映射。

注意:尽管你可以增加一个已经存在的映射,但无法修改存在的域映射。如果一个域的映射已经存在,那么该域的数据可能已经被索引。如果你意图修改这个域的映射,索引的数据可能会出错,不能被正常的搜索。

  • 创建索引时指定映射
PUT /gb
{
"mappings": {
"tweet" : {
"properties" : {
"tweet" : {
"type" : "string",
"analyzer": "english"
},
"date" : {
"type" : "date"
},
"name" : {
"type" : "string"
},
"user_id" : {
"type" : "long"
}
}
}
}
}

这段代码指定了tweet类型的映射,把tweet属性使用english分析器,是一个全文string类型。date为日期类型,name为全文string类型使用standard分析器,user_id是长整型。

  • 新增一个属性,并指定映射
PUT /gb/_mapping/tweet
{
"properties" : {
"tag" : {
"type" : "string",
"index": "not_analyzed"
}
}
}

新增了一个tag属性,为一个精确值string类型,即不对这个字段作分析

我们来测试一下

GET /gb/_analyze?field=tweet&text=Black-cats

GET /gb/_analyze?field=tag&text=Black-cats

可以看到对于tweet属性来说,索引为black和cat,而对于Black-cat 来说索引为Black-cat

上一篇:SharePoint2010搜索的简单设置


下一篇:用ElasticSearch搭建自己的搜索和分析引擎【转自腾讯Wetest】