(8)Elasticsearch-mapping详解

it2024-03-28  52

目录

 

1、Mapping简介

2、Mapping Type

2.1、什么是meta-fields

2.2、有哪些meta-fields

2.2.1 身份元数据

2.2.2 索引元数据

2.2.3 文档元数据

2.2.4 路由元数据

2.2.5 其他

3、分词器

4、字段类型

4.1、text 类型

4.2、keyword 类型

4.3、date类型

4.4、object类型

4.5、nest类型

4.6、range类型

5、案例-同时使用keyword和text类型

6、案例-格式化时间、以及按照时间排序


1、Mapping简介

mapping 是用来定义文档及其字段的存储方式、索引方式的手段,例如利用mapping 来定义以下内容:

哪些字段需要被定义为全文检索类型哪些字段包含number、date类型等格式化时间格式自定义规则,用于控制动态添加字段的映射

例子:

PUT /idx_item/ { "settings": { "index": { "number_of_shards" : "2", "number_of_replicas" : "0" } }, "mappings": { "properties": { "itemId" : { "type": "keyword", "ignore_above": 64 }, "title" : { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart", "fields": { "keyword" : {"ignore_above" : 256, "type" : "keyword"} } }, "desc" : {"type": "text", "analyzer": "ik_max_word"}, "num" : {"type": "integer"}, "price" : {"type": "long"} } } }

2、Mapping Type

每个索引都拥有唯一的 mapping type,用来决定文档将如何被索引。mapping type由下面两部分组成

Meta-fields 元字段用于自定义如何处理文档的相关元数据。 元字段的示例包括文档的_index,_type,_id和_source字段。

Fields or properties 映射类型包含与文档相关的字段或属性的列表。

2.1、什么是meta-fields

在Elasticsearch下,一个文档除了有数据之外,它还包含了元数据(Metadata)。每创建一条数据时,都会对元数据进行写入等操作,当然有些元数据是在创建mapping的时候就会设置,

它里面定义了每个添加的doc的处理方式。 类似于数据库的表结构数据。

2.2、有哪些meta-fields

ES中元数据大体分为五中类型:身份元数据、索引元数据、文档元数据、路由元数据以及其他类型的元数据。

2.2.1 身份元数据

_index:文档所属索引 , 自动被索引,可被查询,聚合,排序使用,或者脚本里访问

_type:文档所属类型,自动被索引,可被查询,聚合,排序使用,或者脚本里访问

_id:文档的唯一标识,

建索引时候传入 ,不被索引, 可通过_uid被查询,脚本里使用,不能参与聚合或排序

_uid:由_type和_id字段组成,自动被索引 ,可被查询,聚合,排序使用,或者脚本里访问

2.2.2 索引元数据

_all: 自动组合所有的字段值,以空格分割,可以指定分器词索引,但是整个值不被存储,所以此字段仅仅能被搜索,不能获取到具体的值_field_names:索引了每个字段的名字,可以包含null值,可以通过exists查询或missing查询方法来校验特定的字段_timestamp:可以手工指定时间戳值,也可以自动生成使用now()函数,除此之外还可以设置日期的格式化,忽略确实等功能_ttl:对于一些会话数据或者验证码失效时间,一般来说是有生命周期的,在es中可以很方便的通过这个ttl来设置存活时间,比如1小时,或者10分钟,在超时过后,这个doc会被自动删除,这种方式并不适合按周或按天删除历史数据,如果是这种需求,可考虑使用索引级别的管理方式

2.2.3 文档元数据

_source : 一个doc的原生的json数据,不会被索引,用于获取提取字段值 ,启动此字段,索引体积会变大,如果既想使用此字段

又想兼顾索引体积,可以开启索引压缩

https://www.elastic.co/guide/en/elasticsearch/reference/2.3/index-modules.html#index-codec

_size: 整个_source字段的字节数大小,需要单独安装一个插件才能展示,详情参见:https://www.elastic.co/guide/en/elasticsearch/plugins/5.4/mapper-size.html

_source是可以被禁用的,不过禁用之后部分功能再支持:

update api:https://www.elastic.co/guide/en/elasticsearch/reference/2.3/docs-update.html<u>highlighting</u>:https://www.elastic.co/guide/en/elasticsearch/reference/2.3/search-request-highlighting.html索引重建、修改mapping以及分词、索引升级debug查询或者聚合语句索引自动修复

2.2.4 路由元数据

_parent:在同一个索引中,可以通过_parent字段来给两个不同mapping type的数据建立父子关系,在查询时可以通过has_child, has_parent等查询,来聚合join数据,需要注意的是,父子type必须不能是一样的,否则会识别失败。_routing: 一个doc可以被路由到指定的shard上,通过下面的规则: shard_num = hash(_routing) % num_primary_shards 默认情况下,会使用doc的_id字段来参与路由规则,如果此doc有父子关系,则会以父亲的_id作为路由规则,以确保父子数据 必须处于同一个shard上,以提高join效率

需要注意的是如果指定了使用自己的路由规则,如果两个文档_id参数一样,但是路由规则值不一样,有可能造成这两条数据被分发到不同的shard,所以在使用自己的路由规则时

注意_id参数的唯一性。

2.2.5 其他

_meta:每个mapping type可以有不同的元数据类型,我们可以存储自己定义认为的元数据中,此字段支持查询和更新

参考:

https://www.elastic.co/guide/en/elasticsearch/reference/2.3/mapping-fields.html

http://m635674608.iteye.com/blog/2259528

http://www.111cn.net/jsp/J2EE-EJB/118565.htm

 

3、分词器

因为后续的keyword和text设计分词问题,这里给出分词最佳实践。即索引时用ik_max_word,搜索时分词器用ik_smart,这样索引时最大化的将内容分词,搜索时更精确的搜索到想要的结果。

例如我想搜索的是小米手机,我此时的想法是想搜索出小米手机的商品,而不是小米音响、小米洗衣机等其他产品,也就是说商品信息中必须只有小米手机这个词。

我们后续会使用"search_analyzer": "ik_smart"来实现这样的需求。

ik_max_word和ik_smart区别

ik_max_word

会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。

ik_smart

会做最粗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。


实践

两种分词器使用的最佳实践是:索引时用ik_max_word,在搜索时用ik_smart。 即:索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。

举个例子: 我是个用户,输入“华为手机”,我此时的想法是想搜索出“华为手机”的商品,而不是华为其它的商品,也就是商品信息中必须只有华为手机这个词。 此时使用ik_smart和ik_max_word都会将华为手机拆分为华为和手机两个词,那些只包括“华为”这个词的信息也被搜索出来了,我的目标是搜索只包含华为手机这个词的信息,这没有满足我的目标。

怎么解决呢? 我们可以将华为手机添加到自定义词库,添加后两个分词器的效果为:

ik_max_word 的分词效果:

{ "tokens": [ { "token": "华为手机", "start_offset": 0, "end_offset": 4, "type": "CN_WORD", "position": 0 }, { "token": "华为", "start_offset": 0, "end_offset": 2, "type": "CN_WORD", "position": 1 }, { "token": "手机", "start_offset": 2, "end_offset": 4, "type": "CN_WORD", "position": 2 } ] }

ik_smart的分词效果:

{ "tokens": [ { "token": "华为手机", "start_offset": 0, "end_offset": 4, "type": "CN_WORD", "position": 0 } ] }

看到两个分词器的区别了吧,因为华为手机是一个词,所以ik_smart不再细粒度分了。 此时,我们可以在索引时使用 ik_max_word,在搜索时用ik_smart。

当输入 华为手机 关键字,只搜索到 包含华为手机的信息,符合用户要求。 如果我想将包含华为 这个词的信息也搜索出来怎么办呢? 那就输入 “华为 华为手机”(注意华为后边有个空格),那就会将包含华为、华为手机的信息都搜索出来。

根据上边举的例子,可以思考下,我想搜索手机壳怎么办?用户肯定只想搜索出手机壳的信息,不想搜索出来一推手机。

根据上边举的例子,可以思考下,如果搜索时用ik_max_word会有什么结果。 跟传智燕青一起学Elasticsearch课程视频分享:https://blog.csdn.net/weixin_44062339/article/details/99052909

4、字段类型

一种简单的数据类型,例如text、keyword、double、boolean、long、date、ip类型。也可以是一种分层的json对象(支持属性嵌套)。也可以是一些不常用的特殊类型,例如geo_point、geo_shape、completion

针对同一字段支持多种字段类型可以更好地满足我们的搜索需求,例如一个string类型的字段可以设置为text来支持全文检索,与此同时也可以让这个字段拥有keyword类型来做排序和聚合,另外我们也可以为字段单独配置分词方式,例如"analyzer": "ik_max_word"

4.1、text 类型

text类型的字段用来做全文检索,例如邮件的主题、淘宝京东中商品的描述等。这种字段在被索引存储前先进行分词,存储的是分词后的结果,而不是完整的字段。text字段不适合做排序和聚合。如果是一些结构化字段,分词后无意义的字段建议使用keyword类型,例如邮箱地址、主机名、商品标签等。

常有参数包含以下

analyzer:用来分词,包含索引存储阶段和搜索阶段(其中查询阶段可以被search_analyzer参数覆盖),该参数默认设置为index的analyzer设置或者standard analyzerindex:是否可以被搜索到。默认是truefields:Multi-fields允许同一个字符串值同时被不同的方式索引,例如用不同的analyzer使一个field用来排序和聚类,另一个同样的string用来分析和全文检索。下面会做详细的说明search_analyzer:这个字段用来指定搜索阶段时使用的分词器,默认使用analyzer的设置search_quote_analyzer:搜索遇到短语时使用的分词器,默认使用search_analyzer的设置

4.2、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来覆盖这一值。

4.3、date类型

支持排序,且可以通过format字段对时间格式进行格式化。

json中没有时间类型,所以在es在规定可以是以下的形式:

一段格式化的字符串,例如"2015-01-01"或者"2015/01/01 12:10:30"

一段long类型的数字,指距某个时间的毫秒数,例如1420070400001

一段integer类型的数字,指距某个时间的秒数

4.4、object类型

apping中不用特意指定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" }

mapping可以对不同字段进行不同的设置

PUT my_index { "mappings": { "properties": { "region": { "type": "keyword" }, "manager": { "properties": { "age": { "type": "integer" }, "name": { "properties": { "first": { "type": "text" }, "last": { "type": "text" } } } } } } } }

4.5、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" ] }

4.6、range类型

支持以下范围类型:

类型范围integer_range-2的31次 到 2的31次-1.float_range32位单精度浮点数long_range-2的63次 到 2的63次-1.double_range64位双精度浮点数date_rangeunsigned 64-bit integer millisecondsip_rangeipv4和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" } }

 

5、案例-同时使用keyword和text类型

注:term是查询时对关键字不分词,keyword是索引时不分词

上述我们讲解过keyword和text一个不分词索引,一个是分词后索引,我们利用他们的fields属性来让当前字段同时具备keyword和text类型。

首先我们创建索引并指定mapping,为title同时设置keyword和text属性

PUT /idx_item/ { "settings": { "index": { "number_of_shards" : "2", "number_of_replicas" : "0" } }, "mappings": { "properties": { "itemId" : { "type": "keyword", "ignore_above": 64 }, "title" : { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart", "fields": { "keyword" : {"ignore_above" : 256, "type" : "keyword"} } }, "desc" : {"type": "text", "analyzer": "ik_max_word"}, "num" : {"type": "integer"}, "price" : {"type": "long"} } } }

我们已经往es中插入以下数据

_index_type_id_scoreitemIdtitledescnumPriceidx_item_docrvsX-W4Bo-iJGWqbQ8dk11苏泊尔煮饭SL3200让煮饭更简单,让生活更快乐100200idx_item_docsPsY-W4Bo-iJGWqbscfU13厨房能手威猛先生你煲粥,我洗锅10030idx_item_docr_sX-W4Bo-iJGWqbhMew12苏泊尔煲粥好能手型号SL322你煲粥,我煲粥,我们一起让煲粥更简单100190

title=”苏泊尔煮饭SL3200“ 根据text以及最细粒度分词设置"analyzer": "ik_max_word",在es中按照以下形式进行索引存储

{ "苏泊尔","煮饭", "sl3200", "sl","3200"}

title.keyword=”苏泊尔煮饭SL3200因为不分词,所以在es中索引存储形式为

苏泊尔煮饭SL3200

我们首先对title.keyword进行搜索,只能搜索到第一条数据,因为match搜索会将关键字分词然后去搜索,分词后的结果包含"苏泊尔煮饭SL3200"所以搜索成功,我们将搜索关键字改为苏泊尔、煮饭等都不会查到数据。

GET idx_item/_search { "query": { "bool": { "must": { "match": {"title.keyword": "苏泊尔煮饭SL3200"} } } } }

我们改用term搜索,他搜索不会分词,正好与es中的数据精准匹配,也只有第一条数据,我们将搜索关键字改为苏泊尔、煮饭等都不会查到数据。

GET idx_item/_search { "query": { "bool": { "must": { "term": {"title.keyword": "苏泊尔煮饭SL3200"} } } } }

我们继续对title使用match进行查询,结果查到了第一条和第三条数据,因为它们在es中被索引的数据包含苏泊尔关键字

GET idx_item/_search { "query": { "bool": { "must": {"match": {"title": "苏泊尔"} } } } }

我们如果搜索苏泊尔煮饭SL3200会发现没有返回数据,因为title在索引时没有苏泊尔煮饭SL3200这一项,而term时搜索关键字也不分词,所以无法匹配到数据。但是我们将内容改为苏泊尔时,就可以搜索到第一条和第三条内容,因为第一条和第三条的title被分词后的索引包含苏泊尔字段,所以可以查出第一三条。

"term": {"title": "苏泊尔煮饭SL3200"}

6、案例-格式化时间、以及按照时间排序

我们创建索引idx_pro,将mytimestamp和createTime字段分别格式化成两种时间格式

PUT /idx_pro/ { "settings": { "index": { "number_of_shards" : "2", "number_of_replicas" : "0" } }, "mappings": { "properties": { "proId" : { "type": "keyword", "ignore_above": 64 }, "name" : { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_smart", "fields": { "keyword" : {"ignore_above" : 256, "type" : "keyword"} } }, "mytimestamp" : { "type": "date", "format": "epoch_millis" }, "createTime" : { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" } } } }

插入四组样本数据

POST idx_pro/_doc { "proId" : "1", "name" : "冬日工装裤", "timestamp" : 1576312053946, "createTime" : "2019-12-12 12:56:56" } POST idx_pro/_doc { "proId" : "2", "name" : "冬日羽绒服", "timestamp" : 1576313210024, "createTime" : "2019-12-10 10:50:50" } POST idx_pro/_doc { "proId" : "3", "name" : "花花公子外套", "timestamp" : 1576313239816, "createTime" : "2019-12-19 12:50:50" } POST idx_pro/_doc { "proId" : "4", "name" : "花花公子羽绒服", "timestamp" : 1576313264391, "createTime" : "2019-12-12 11:56:56" }

我们可以使用sort参数来进行排序,并且支持数组形式,即同时使用多字段排序,只要改为[]就行

GET idx_pro/_search { "sort":{"createTime": {"order": "asc"}}, "query": { "bool": { "must": {"match_all": {}} } } }

我们也可以使用range参数来搜索指定时间范围内的数据,当然range也支持integer、long等类型

GET idx_pro/_search { "query": { "bool": { "must": { "range": { "timestamp": { "gt": "1576313210024", "lt": "1576313264391" } } } } } }

参考出处:https://www.cnblogs.com/haixiang/p/12040272.html

最新回复(0)