Mapping简介
mapping 是用来定义文档及其字段的存储方式、索引方式的策略,主要作用如下:
- 定义index下的字段名。
- 定义字段类型,比如数值型、浮点型、布尔型等。
- 定义倒排索引相关的设置,比如是否索引、记录position等。
也就是说映射决定了Elasticsearch在建立倒排索引、进行检索时对文档采取的相关策略,如数字类型、日期类型、文本类型等等。
需要注意的是:检索时用到的分析策略,要和建立索引时的分析策略相同,否则将导致数据不准确。
ES对不同的类型有不同的存储和检索策略
full text 全文检索
- 比如: 对full text型的数据类型(如text),在索引时,会经过各类处理 (包括分词、normalization(时态转换、单复数的转换、同义词转换、大小写转换、缩写)等处理),才会建立到索引数据中,更深入的话NPL自然语义处理。
exact value 精确匹配
- 再比如: 对exact value(如date),在索引的分词阶段,会将整个value作为一个关键词建立到倒排索引中。
Mapping Type
每个索引都拥有唯一的 mapping type
,用来决定文档将如何被索引。mapping type
由下面两部分组成
Meta-fields
元字段用于自定义如何处理文档的相关元数据。 元字段的示例包括文档的_index,_type,_id和_source字段。Fields or properties
映射类型包含与文档相关的字段或属性的列表。
数据类型
核心数据类型
- 字符串型:text、keyword
- 数值型:long、integer、short、byte、double、float、half_float、scaled_float
- 日期类型:date
- 布尔类型:boolean
- 二进制类型:binary
- 范围类型:integer_range、float_range、long_range、double_range、date_range
复杂数据类型
- 数组类型:array
- 对象类型:object
- 嵌套类型:nested object
地理位置数据类型
- geo_point
- geo_shape
专用类型
- 记录:ip
- 实现自动补全:completion
- 记录分次数:token_count
- 记录字符串hash值:murmur3
- percolator
- join
多字段特性 multi-fields
允许对同一个字段采用不同的配置,比如分词,常见例子如对人名实现拼音搜索,只需要在人名中新增一个字段为pinyin即可
详见:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
针对同一字段支持多种字段类型可以更好地满足我们的搜索需求,例如一个string类型的字段可以设置为text来支持全文检索,与此同时也可以让这个字段拥有keyword类型来做排序和聚合,另外我们也可以为字段单独配置分词方式,例如"analyzer": "ik_max_word"
text 类型
text类型的字段用来做全文检索,例如邮件的主题、淘宝京东中商品的描述等。这种字段在被索引存储前先进行分词,存储的是分词后的结果,而不是完整的字段。text字段不适合做排序和聚合。如果是一些结构化字段,分词后无意义的字段建议使用keyword类型,例如邮箱地址、主机名、商品标签等。
常有参数包含以下:
- analyzer:用来分词,包含索引存储阶段和搜索阶段(其中查询阶段可以被search_analyzer参数覆盖),该参数默认设置为index的analyzer设置或者standard analyzer。
- index:是否可以被搜索到,默认是true。
- fields:Multi-fields允许同一个字符串值同时被不同的方式索引,例如用不同的analyzer使一个field用来排序和聚类,另一个同样的string用来分析和全文检索。
- search_analyzer:这个字段用来指定搜索阶段时使用的分词器,默认使用analyzer的设置。
- search_quote_analyzer:搜索遇到短语时使用的分词器,默认使用search_analyzer的设置。
- store:是否在source之外存储,每个文档索引后会在ES中保存一份原始文档,存放在source中,一般情况下不需要设置store为true,因为在source中已经有一份原始文档了。
keyword 类型
keyword用于索引结构化内容(例如电子邮件地址,主机名,状态代码,邮政编码或标签)的字段,这些字段被拆分后不具有意义,所以在es中应索引完整的字段,而不是分词后的结果。
通常用于过滤(例如在博客中根据发布状态来查询所有已发布文章),排序和聚合。keyword只能按照字段精确搜索,例如根据文章id查询文章详情。如果想根据本字段进行全文检索相关词汇,可以使用text类型。
PUT my_index
{
"mappings": {
"properties": {
"tags": {
"type": "keyword"
}
}
}
}
常有参数包含以下:
- index:是否可以被搜索到,默认是true。
- fields:Multi-fields允许同一个字符串值同时被不同的方式索引,例如用不同的analyzer使一个field用来排序和聚类,另一个同样的string用来分析和全文检索。
- null_value:如果该字段为空,设置的默认值,默认为null。
- ignore_above:设置索引字段大小的阈值。该字段不会索引大小超过该属性设置的值,默认为2147483647,代表着可以接收任意大小的值。但是这一值可以被PUT Mapping Api中新设置的ignore_above来覆盖这一值。
date类型
支持排序,且可以通过format
字段对时间格式进行格式化。
json
中没有时间类型,所以在es在规定可以是以下的形式:
- 一段格式化的字符串,例如
"2015-01-01"
或者"2015/01/01 12:10:30"
- 一段
long
类型的数字,指距某个时间的毫秒数,例如1420070400001
- 一段
integer
类型的数字,指距某个时间的秒数
object类型
mapping
中不用特意指定field为object
类型,因为这是它的默认类型。
json
类型天生具有层级的概念,文档内部还可以包含object
类型进行嵌套。例如:
PUT my_index/_doc/1
{
"region": "US",
"manager": {
"age": 30,
"name": {
"first": "John",
"last": "Smith"
}
}
}
在es中上述对象会被按照以下的形式进行存储:
{
"region": "US",
"manager.age": 30,
"manager.name.first": "John",
"manager.name.last": "Smith"
}
对象数组:
{
"authors": [
{"age": 26 , "name": "jock white"},
{"age": 55 , "name": "tom jones"},
{"age": 39 , "name": "kitty smith"}
]
}
对象数组在es中的存储格式:
{
"authors.age": [26, 55, 39],
"authors.name": [jock, white, tom, jones, kitty, smith]
}
mapping可以对不同字段进行不同的设置
PUT my_index
{
"mappings": {
"properties": {
"region": {
"type": "keyword"
},
"manager": {
"properties": {
"age": { "type": "integer" },
"name": {
"properties": {
"first": { "type": "text" },
"last": { "type": "text" }
}
}
}
}
}
}
}
nest类型
nest
类型是一种特殊的object
类型,它允许object
可以以数组形式被索引,而且数组中的某一项都可以被独立检索。
而且es中没有内部类的概念,而是通过简单的列表来实现nest
效果,例如下列结构的文档:
PUT my_index/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
上面格式的对象会被按照下列格式进行索引,因此会发现一个user中的两个属性值不再匹配,alice和white失去了联系
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
}
range类型
支持以下范围类型:
类型 | 范围 |
---|---|
integer_range | -2的31次 到 2的31次-1 . |
float_range | 32位单精度浮点数 |
long_range | -2的63次 到 2的63次-1 . |
double_range | 64位双精度浮点数 |
date_range | unsigned 64-bit integer milliseconds |
ip_range | ipv4和ipv6或者两者的混合 |
使用范例为:
PUT range_index
{
"settings": {
"number_of_shards": 2
},
"mappings": {
"properties": {
"age_range": {
"type": "integer_range"
},
"time_frame": {
"type": "date_range",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
}
}
}
}
PUT range_index/_doc/1?refresh
{
"age_range" : {
"gte" : 10,
"lte" : 20
},
"time_frame" : {
"gte" : "2015-10-31 12:00:00",
"lte" : "2015-11-01"
}
}
注:term是查询时对关键字不分词,keyword是索引时不分词。
自定义Mapping
自定义Mapping的api如下:
- Mapping中的字段类型一旦设定后,禁止直接修改,原因是:
- Lucence实现的倒排索引生成后不允许修改,
- 除非重新建立新的索引,然后做reindex操作。
- 允许新增字段。
- 通过dynamic参数来控制字段的新增:
- true(默认)允许自动新增字段。
- false不允许自动新增字段,但是文档可以正常写入,但无法对字段进行查询等操作。
- strict 文档不能写入,报错。
copy_to
index
- 控制当前字段是否索引,默认为true,即记录索引,false不记录,即不可搜索。
- 当身份证号和手机号这样的敏感信息被搜索的时候,可以设置为false,这样就可以不用建立倒排索引节省空间。
index_options
-
index_options用于控制倒排索引记录的内容,有如下4中配置:
- docs:只记录doc id。
- freqs:记录doc id和term frequencies。
- positions:记录doc id、term frequencies和term position。
- offsets:记录doc id、term frequencies、term position和character offsets。
- text类型默认配置为positions,其他默认为docs。
记录内容越多,占用空间越大。
index_options的设定如下所示:
null_value
- 当字段遇到null值时的处理策略,默认为null,即空值,此时es会忽略该值。可以通过设定该值设定字段的默认值。
dynamic Mapping
es可以自动识别文档字段类型,从而降低用户使用成本,如下所示:
es是依靠json文档的字段类型来实现自动识别字段类型,支持的类型有:
JSON类型 | ES类型 |
---|---|
null | 忽略 |
boolean | boolean |
浮点类型 | float |
整数 | long |
object | object |
array | 由第一个非null值得类型决定 |
string | 匹配为日期,则设为date类型(默认开启) 匹配为数字的话设为float或long类型(默认关闭) 设为text类型,并附带keyword的子字段 |
日期的自动识别可以自行配置日期格式,以满足各种需求
- 默认是["strict_date_optional_time","yyyy/MM/dd HH:mm:ss Z||yyyy/MM/dd Z"]
- strict_date_optional_time是ISO datetime的格式,完整格式类似如下面:
- YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
- dynamic_date_formats 可以自定义日期类型
- date_detection可以关闭日期自动识别的机制
字符串是数字时,默认不会自动识别为整型,因为字符串中出现数字时完全合理的
-
numeric_detection可以开启字符串中数字的自动识别,如下所示:
Dynamic Template
允许根据es自动识别的数据类型、字段名等来动态设定字段类型,可以实现如下效果:
- 所有字符串类型都设定为keyword类型,即默认不分词
- 所有以message开头的字段都设定为text类型,即分词
- 所有以long_开头的字段都设定为long类型
- 所有自动匹配为double类型的都设定为float类型,以节省空间
匹配规则一般有如下几个参数:
- match_mapping_type匹配es自动识别的字段类型,如boolean,long,string等
- match,unmatch匹配字段名
- path_match,path_unmatch匹配路径
以message开头的字段都设定为text类型
double类型的都设定为float类型,节省空间
自定义mapping的建议
自定义Mapping的操作步骤如下:
- 1、写入一条文档到es的临时索引中,获取es自动生成的mapping
- 2、修改步骤1得到的mapping,自定义相关配置
- 3、使用步骤2的mapping创建实际所需索引
索引模板
英文为Index Template,主要用于在新建索引时自动应用预先设定的配置,简化索引创建的操作步骤。
- 可以设定索引的配置和mapping。
- 可以有多个模板,根据order设置,order大的覆盖小的配置。
type底层结构及弃用原因
type是什么
type,是一个index中用来区分类似的数据的,类似的数据,但是可能有不同的fields,而且有不同的属性来控制索引建立、分词器.
field的value,在底层的lucene中建立索引的时候,全部是opaque bytes类型,不区分类型的。
lucene是没有type的概念的,在document中,实际上将type作为一个document的field来存储,即type,es通过type来进行type的过滤和筛选。
es中不同type存储机制
一个index中的多个type,实际上是放在一起存储的,因此一个index下,不能有多个type重名,而类型或者其他设置不同的,因为那样是无法处理的。
{
"goods": {
"mappings": {
"electronic_goods": {
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "double"
},
"service_period": {
"type": "string"
}
}
},
"fresh_goods": {
"properties": {
"name": {
"type": "string"
},
"price": {
"type": "double"
},
"eat_period": {
"type": "string"
}
}
}
}
}
}
PUT /goods/electronic_goods/1
{
"name": "小米空调",
"price": 1999.0,
"service_period": "one year"
}
PUT /goods/fresh_goods/1
{
"name": "澳洲龙虾",
"price": 199.0,
"eat_period": "one week"
}
es文档在底层的存储是这样子的
{
"goods": {
"mappings": {
"_type": {
"type": "string",
"index": "false"
},
"name": {
"type": "string"
}
"price": {
"type": "double"
}
"service_period": {
"type": "string"
},
"eat_period": {
"type": "string"
}
}
}
}
底层数据存储格式
{
"_type": "electronic_goods",
"name": "小米空调",
"price": 1999.0,
"service_period": "one year",
"eat_period": ""
}
{
"_type": "fresh_goods",
"name": "澳洲龙虾",
"price": 199.0,
"service_period": "",
"eat_period": "one week"
}
type弃用原因
- 同一索引下,不同type的数据存储其他type的field 大量空值,造成资源浪费。
- 不同类型数据,要放到不同的索引中。
- es9中,将会彻底删除type。
分词器Analyzer
作用:切分词语,normalization提升recall召回率。
recall,召回率:搜索的时候,增加能够搜索到的结果的数量。
分词器是Elasticsearch中专门处理分词的组件,英文为Analyzer,其组成如下:
Character Filters
针对原始文本进行处理,比如去除html特殊标记符。Tokenizer
将原始文本按照一定规则切分为单词。Token Filters
针对Tokenizer处理的单词进行在加工,比如转小写,删除或新增等处理。
stop word,停用词:了、的、呢等。
一个分词器很重要,将一段文本进行各种处理,最后处理好的结果才会拿去建立倒排索引。
测试分词:
GET /_analyze
{
"analyzer":"standard",
"text":"Text to analyze 70"
}
返回值:
{
"tokens" : [
{
"token" : "text",
"start_offset" : 0,
"end_offset" : 4,
"type" : "",
"position" : 0
},
{
"token" : "to",
"start_offset" : 5,
"end_offset" : 7,
"type" : "",
"position" : 1
},
{
"token" : "analyze",
"start_offset" : 8,
"end_offset" : 15,
"type" : "",
"position" : 2
},
{
"token" : "70",
"start_offset" : 16,
"end_offset" : 18,
"type" : "",
"position" : 3
}
]
}
token:实际存储term关键字。
position:在此词条原文本中的位置。
start_offset/end_offset:字符在原始字符串中的位置。
分词器最佳实践
因为后续的keyword
和text
设计分词问题,这里给出分词最佳实践。即索引时用ik_max_word,搜索时分词器用ik_smart,这样索引时最大化的将内容分词,搜索时更精确的搜索到想要的结果。
例如我想搜索的是小米手机,我此时的想法是想搜索出小米手机的商品,而不是小米音响、小米洗衣机等其他产品,也就是说商品信息中必须只有小米手机这个词。
参考:
https://www.cnblogs.com/haixiang/p/12040272.html
https://www.cnblogs.com/shoufeng/p/10648835.html