ElasticSearch--映射和分析
Posted 準提童子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ElasticSearch--映射和分析相关的知识,希望对你有一定的参考价值。
映射mapping机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型(string, boolean, date);
分析analysis机制用于进行全文文本Full Text的分词,以建立供搜索用的倒排索引Inverted index.
上边做个总结,后续学习之后就明白了.先看一个查询现象:
在索引中有12个tweets,只有一个包含日期2014-09-15,但是我们看看下面查询中的total hits。
GET /_search?q=2014 # 12 个结果 GET /_search?q=2014-09-15 # 还是 12 个结果 ! GET /_search?q=date:2014-09-15 # 1 一个结果 GET /_search?q=date:2014 # 0 个结果 !相互比较后会发现,为什么查询_all字段与查询date字段得到的结果不同呢?
是因为数据在_all字段和在date字段中的索引方式不同导致的.
1. 映射
现在通过_mpping查看文档的结构是如何被映射的:
GET /website/_mapping/blog这个请求可以查看类型blog中的文档是被映射成为什么样的结构:
{ "website": { "mappings": { "blog": { "properties": { "date": { "type": "date", "format": "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis" }, "tags": { "type": "string" }, "text": { "type": "string" }, "title": { "type": "string" }, "views": { "type": "long" } } } } } }可以看出,blog类型中的文档结构,每个字段都有其特定类型;
ES为对字段的数据类型进行猜测,动态生成了字段与数据类型的映射关系._all因为是默认字段,所以没有在此处显示,它是string类型.
date类型和string类型字段的索引方式是不同的,因此导致查询结果不同.
每一种数据类型的字段会以不同的索引方式进行索引,确切值和全文文本的索引方式存在很大的区别,这是区分搜索引擎和其他数据库的根本差异.
确切值的查询其实就是精确匹配,就像传统数据库中:SELECT * FROM table WHERE name="yuchen" ,结果就是要么匹配要么不匹配.
而对于全文文本来说,并不是匹配与否二分这么简单,它是考虑的匹配程度如何.
例如下列需要,都是全文文本查询需要考虑的:
-
一个针对
"UK"
的查询将返回涉及"United Kingdom"
的文档 -
一个针对
"jump"
的查询同时能够匹配"jumped"
,"jumps"
,"jumping"
甚至"leap"
-
"johnny walker"
也能匹配"Johnnie Walker"
,"johnnie depp"
及"Johnny Depp"
-
"fox news hunting"
能返回有关hunting on Fox News的故事,而"fox hunting news"
也能返回关于fox hunting的新闻故事。
2. 倒排索引
ES使用一种倒排索引的结构来做快速的全文搜索.倒排索引由文本中出现的唯一的单词列表,和每个单词所在文本中的位置组成.
例如有两个文档,每个文档包含content字段的内容:
- The quick brown fox jumped over the lazy dog
- Quick brown foxes leap over lazy dogs in summer
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 |
Term | Doc_1 | Doc_2 |
---|---|---|
brown | X | X |
quick | X | |
----- | ------- | ----- |
Total | 2 | 1 |
但是,查询还有另外一些问题:
1. "Quick"和"quick"被认为是不同的单词,但是用户可能认为它们是相同的。(大小写转换)
2. "fox"和"foxes"很相似,就像"dog"和"dogs"——它们都是同根词。(单复数转换)
3. "jumped"和"leap"不是同根词,但意思相似——它们是同义词。(同义词转换)
为了解决这一问题,可以将单词标准化,同一为标准格式:
"Quick"
可以转为小写成为"quick"
。"foxes"
可以被转为根形式"fox"
。同理"dogs"
可以被转为"dog"
。"jumped"
和"leap"
同义就可以只索引为单个词"jump"
索引文档和请求字符串都要标准化到统一的格式,才能进行全文文本查询.
3. 分析
分析是这样一个过程:
首先,标记一个文本块为适用于倒排索引的单独的词terms;
然后,标准化这些词为标准形式,提高它们的可搜索性.
这个工作是由分析器analyzer完成的,一个分析器只是一个包,把三个功能放到一个包里:
字符过滤器Character filters
首先字符串经过字符过滤器,它们的工作是在标准化前处理字符串.字符过滤器能够去除html标记,或者转&为and
分词器Tokenizer
下一步,字符串被分词器标记为独立的词,一个简单的分词器可以根据空格或逗号将单词分开
标记过滤器Token Filter
每个词都依次通过标记过滤器,它可以修改词(将Quick转为小写),去掉词(停用词a and the),增加词(同义词 jump或leap)
ES提供很多开箱即用的字符过滤器,分词器,标记过滤器,可以组合以创建自定义的分析器,以应对不同的需求.
内置分析器
ES附带了一些预装的分析器,可以直接使用它们,下边列几个重要的分析器,用这个字符串来表现分析后的差异:
"Set the shape to semi-transparent by calling set_trans(5)"标准分析器Standard analyzer
标准分析器是ES默认的分析器,对于分本分析,它对任何语言都适用.他跟据Unicode Consortium定义的单词边界word boundaries来切分文本,然后去掉大部分标点符号,最后把所有词转为小写.使用标准分析器的结果:
set, the, shape, to, semi, transparent, by, calling, set_trans, 5简单分析器Simple analyzer
简单分析器将非单个字母的文本切分,然后把每个词转为小写.
set, the, shape, to, semi, transparent, by, calling, set, trans空格分析器Whitespace analyzer
空格分析器依据空格切分文本,它不转换为小写
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)语言分析器Language analyzer
特定的语言分析器适用于很多语言,它们能够考虑到特定语言的特性,例如english analyzer自带一套停用词集,它可以在分析时去除它们,它还可以将英语单词的转为词根(term English words),因为它理解英语语法规则.
set, shape, semi, transpar, call, set_tran, 5
注意transparent,calling,set_trans被准为词根形式
查询字符串的处理
当我们索引一个文档,全文字段_all会被分析为单独的词来创建倒排索引,不过在全文字段搜索时,我们要让查询字符串经过同样的分析流程处理,以确保这些词在索引中存在.
查询全文字段,会对查询字符串进行分析流程处理;查询确切值字段,不对查询字符串进行分析处理,但是可以自己指定.
测试分析器
可以使用analyze API查看文本是如何被分析的,在查询字符串中指定要使用的分析器,被分析的文本作为请求体:
GET /_analyze?analyzer=standard { "text":"Text to analyze" }
注意,GET请求与请求体之间不能有空格,响应:
{ "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 } ] }token是一个实际被存储在索引中的词,position指明词第几个出现的,start_offset和end_offset是指明词在原文被中占据的位置.type的值
<ALPHANUM>会随着分析器变化,可先忽略.
analyze API对于理解ES索引的内在细节是很有用的工具,后边会继续谈论.
指定分析器:
当ES在文档中探测到新的字段时,将自动配置它为全文字符串full-text string字段,并用standard分析器分析.
你并不总是想这样做,你或许需要一个适合你的语言的分析器,有时候你想将string字段作为确切值匹配,不需要分析,例如ID,内部状态字段,标签等
为了达到这些,我们需要通过指定映射手动配置这些字段.
4. 自定义映射
为了能够把日期字段处理成日期,数字字段处理成数字,字符串字段处理成全文本或者确切值,ES需要知道每个字段包含了什么类型,这些类型和字段信息存储在映射中.
索引中每个文档都有一个类型type,每个类型拥有自己的映射mapping或模式定义schema definition.一个映射定义了字段类型,包括字段的数据类型,以及字段被ES处理的方式.
映射还用于设置关联到类型上的元数据.
ES支持以下简单字段类型:
类型 | 表示的数据类型 |
---|---|
String | string |
Whole number | byte , short , integer , long |
Floating point | float , double |
Boolean | boolean |
Date | date |
JSON type | Field type |
---|---|
Boolean: true or false |
"boolean" |
Whole number: 123 |
"long" |
Floating point: 123.45 |
"double" |
String, valid date: "2014-09-15" |
"date" |
String: "foo bar" |
"string" |
可以使用mapping API查看一个类型的映射,
GET /website/_mapping/blog响应见第一节
错误的映射,例如把age映射为string,会造成查询结果混乱.要检查映射的数据类型,而不是假设映射是正确的.
自定义字段映射
尽管基本的字段数据类型已经能够满足很多需求,你需要为一些个别字段定义映射,尤其是string字段.
自定义映射可以是你做如下:
1.区分全文string字段和确切值string字段
2.使用特定语言分析器
3.优化一个字段,用作部分匹配
4.指定自定义的数据类型
5.以及更多
一个字段最重要的属性是type,对于除string字段外的大部分字段,除了type,你很少做其他的映射map.
{ "number_of_clicks": { "type": "integer" } }因为对于string字段,需要考虑全文文本,它们的值在被索引前会先经过分析器,一个全文查询,查询字符串也需要经过分析器.
对于string字段,两个重要的属性是index和analyzer.
index
index属性控制字符串以何种方式被索引,有三种取值:
值 | 解释 |
---|---|
analyzed |
首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。 |
not_analyzed |
索引这个字段,使之可以被搜索,但是精确索引指定值。不分析此字段。 |
no |
不索引这个字段。这个字段不能为搜索到。 |
{ "tag": { "type": "string", "index": "not_analyzed" } }其他的简单类型,如long,data等也接受index参数,但是其值只能是not_analyzed和no,因为它们的值不能被分析.
analyzer
对于analyzed的string字段,可以通过analyzer参数指定使用哪种分析器,在搜索和索引的时候都会使用.默认的,ES使用standard分析器,但可以指定一个内置的分析器来更改它,例如whitesapce,simple和english
{ "tweet": { "type": "string", "analyzer": "english" } }在自定义分析器章节,会详细的讲解如何定义以及使用自定义分析器.
更新映射
可以在第一次创建索引时,为一个类型指定映射,也可以在后边为一个新类型添加映射,或者为已存在的类型更新映射.
注意,尽管可以增加一个字段映射,但是不能改变一个已经存在的字段映射.如果一个字段的映射已经存在,那个字段的数据很可能已经被索引,如果改变字段的映射,那么被索引的数据就会出现错误,不能被正确的搜索.
我们可以更新映射,来增加一个字段,但是不能改变一个已经存在的字段从analyzd转为not_analyzed
演示两种指定映射的方式:
创建新的索引,并指定tweet字段使用english分析器:
PUT /gb { "mappings": { "tweet" : { "properties" : { "tweet" : { "type" : "string", "analyzer": "english" }, "date" : { "type" : "date" }, "name" : { "type" : "string" }, "user_id" : { "type" : "long" } } } } }创建索引,在请求体中定义了映射.响应:
{ "acknowledged": true }
接下来,我们加一个新的not_analyzed文本字段tag在类型tweet的映射上.使用_maping后缀
PUT /gb/_mapping/tweet { "properties" : { "tag" : { "type" : "string", "index": "not_analyzed" } } }注意到我们不需要再列出已经存在的字段,因为我们不能再改变它们,新的字段已经被加入到存在的映射中.
使用GET /gb/_mapping/tweet获取看:
{ "gb": { "mappings": { "tweet": { "properties": { "date": { "type": "date", "format": "strict_date_optional_time||epoch_millis" }, "name": { "type": "string" }, "tag": { "type": "string", "index": "not_analyzed" }, "tweet": { "type": "string", "analyzer": "english" }, "user_id": { "type": "long" } } } } } }
基于tweet类型现在的映射,可以通过analyze API测试字符串字段tweet和tag的映射不同,对比两个请求:
GET /gb/_analyze?field=tweet&text=Black-cats响应:
{ "tokens": [ { "token": "black", "start_offset": 0, "end_offset": 5, "type": "<ALPHANUM>", "position": 0 }, { "token": "cat", "start_offset": 6, "end_offset": 10, "type": "<ALPHANUM>", "position": 1 } ] }
GET /gb/_analyze?field=tag&text=Black-cats响应:
{ "tokens": [ { "token": "Black-cats", "start_offset": 0, "end_offset": 10, "type": "word", "position": 0 } ] }tag字段是不能分析的,tweet字段是english分析器分析的.
可是这里有个疑问,当使用如下两个请求时,只是和上边相比,文本写到了请求体中,结果就不一样呢?
对tweet字段的请求是和上边一样的:
GET /gb/_analyze?field=tweet { "text": "Black-cats" }但是对tag的请求就不一样了:
GET /gb/_analyze?filed=tag { "text": "Black-cats" }响应:
{ "tokens": [ { "token": "black", "start_offset": 0, "end_offset": 5, "type": "<ALPHANUM>", "position": 0 }, { "token": "cats", "start_offset": 6, "end_offset": 10, "type": "<ALPHANUM>", "position": 1 } ] }不知为什么,什么原因呢?导致分析了,而且是cats
5. 复杂核心字段类型
除了前边提到的简单类型,JSON还有null,数组,对象,这些数据类型ES也都支持.
多值字段multivalue field
很有可能,我们希望tag字段包含不止一个标签,我们可以索引一个标签数组,来代替单一字符串.
{ "tag": [ "search", "nosql" ]}对于数组,没有特殊的映射要求,任何字段可以包含零个,一个,或多个值,像全文文本一样可以被分析为多个词.
一个数组的所有值必须有相同的数据类型,如果通过索引数组创建一个新的字段,ES将使用数组的第一个值的数据类型决定新字段的type属性.
当从ES中获取一个文档,所有数组的顺序与索引文档时的一致,_source字段精确包括了索引的JSON文档.
然而数组被索引为多值字段类型,用于被查询,它们是无序的,在搜索的时候不能指定"第一个元素","最后一个元素"等.而是把数组看为一个值集合(bag of value).
空字段empty field
数组可以是为空的,这与有零个值是等价的,事实上在lucene中没有存储null的方式,所以一个存有null的字段被认为是空字段.
这三个字段都被认为是空字段,不被索引.
"null_value": null, "empty_array": [], "array_with_null_value": [ null ]多层对象multilevel objects
最后讨论的自然的JSON数据类型是对象object.
内置对象Inner Objects经常被嵌入到一个实体,或者另外一个对象中,比如,我们可以不使用user_name,user_id这样的字段,而是如下:
{ "tweet": "Elasticsearch is very flexible", "user": { "id": "@johnsmith", "gender": "male", "age": 26, "name": { "full": "John Smith", "first": "John", "last": "Smith" } } }
映射内置对象
ES可以动态的发现新的对象字段,并且映射它们的类型为object,并在properties下使用一个内置字段列表:
{ "gb": { "tweet": { "properties": { "tweet": { "type": "string" }, "user": { "type": "object", "properties": { "id": { "type": "string" }, "gender": { "type": "string" }, "age": { "type": "long" }, "name": { "type": "object", "properties": { "full": { "type": "string" }, "first": { "type": "string" }, "last": { "type": "string" } } } } } } } } }在实际操作时,没有返回对象字段的type属性,不知原因:
{ "gb": { "mappings": { "tw": { "properties": { "tweet": { "type": "string", "analyzer": "english" }, "user": { "properties": { "age": { "type": "long" }, "gender": { "type": "string" }, "id": { "type": "string" }, "name": { "properties": { "first": { "type": "string" }, "full": { "type": "string" }, "last": { "type": "string" } } } } } } } } } }
内部对象是怎样被索引的:
Lucene不理解内置对象,一个lucene文档包含键值对的一个扁平化列表,以便于ES索引内置对象,它把文档转换为类似这样:
{ "tweet": [elasticsearch, flexible, very], "user.id": [@johnsmith], "user.gender": [male], "user.age": [26], "user.name.full": [john, smith], "user.name.first": [john], "user.name.last": [smith] }内置字段与名字相关,区分两个字段中相同的名字,可以使用全路径,例如user.name.first,甚至类型的名字加在前边:tweet.user.name.first
内置对象数组
最后考虑一个包含内置对象的数组被索引,例如followers数组:
{ "followers": [ { "age": 35, "name": "Mary White"}, { "age": 26, "name": "Alex Jones"}, { "age": 19, "name": "Lisa Smith"} ] }这个文档也会被扁平化,但是结果类似:
{ "followers.age": [19, 26, 35], "followers.name": [alex, jones, lisa, smith, mary, white] }{age:35}与{Mary White}的相关性已经丢失,因为每个多值字段只是一个数值集,不是排序的数组.
对于问题:"Is there a follower who is 26 years old?"
我们不能得到精确的回答.
关联Correlated内置数组有能力回答这样的问题,我们称之为嵌套对象nested objects,后续会学习到.
以上是关于ElasticSearch--映射和分析的主要内容,如果未能解决你的问题,请参考以下文章
python 接受带有字段名称,类型和分析器的YAML文件,并生成Elasticsearch JSON映射文件