Elasticsearch系列---定制mapping

枕上从妨一夜睡,灯前读尽十年诗。这篇文章主要讲述Elasticsearch系列---定制mapping相关的知识,希望能为你提供帮助。
继续介绍mapping信息,重点倾向于自定义mapping、自定义对象以及数组集合类的底层结构概要
本篇接着前一篇内容,继续介绍mapping信息,重点倾向于自定义mapping、自定义对象以及数组集合类的底层结构。
自定义mapping
上一篇文章介绍的都是Elasticsearch的自动mapping,我们在创建索引时,可以先指定好mapping的信息,还是以music索引为例:

PUT /music { "mappings": { "children": { "properties": { "content": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "language": { "type": "keyword" }, "length": { "type": "long" }, "likes": { "type": "long" }, "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } }

共包含name,content,language,length,likes 5个field,这里我们把language field的type指定为keyword,这个field无需分词,精确匹配即可。
修改mapping
【Elasticsearch系列---定制mapping】我们可以在创建索引时指定mapping信息,也可以为索引增加新的field时指定mapping信息,但是已经存在的field,我们不能修改其mapping,否则会报错,例如:
我们为music索引增加一个author field,并指定其type为text,analyzer、search_analyzer均为english
PUT /music/_mapping/children { "properties" : { "author" : { "type" : "text", "index": true, "analyzer": "english", "search_analyzer": "english" } } }

如果尝试修改一下已经存在的field,如name字段,则会提示报错:
请求:
PUT /music/_mapping/children { "properties" : { "name" : { "type" : "text", "index": true, "analyzer": "english", "search_analyzer": "english" } } }

报错信息:
{ "error": { "root_cause": [ { "type": "illegal_argument_exception", "reason": "Mapper for [name] conflicts with existing mapping in other types:\\n[mapper [name] has different [analyzer]]" } ], "type": "illegal_argument_exception", "reason": "Mapper for [name] conflicts with existing mapping in other types:\\n[mapper [name] has different [analyzer]]" }, "status": 400 }

复杂数据类型底层结构
前面提及的mapping信息,都是针对基础数据类型的,我们知道Elasticsearch是面向对象的分布式存储文档系统,肯定会遇到各种各样的自定义对象、集合数组、嵌套对象等数据结构,ES是如何支持、处理这些复杂对象类型的呢?
  1. 数组、集合类
集合在JSON串中与数组等同,均可以认为是数组类型,在Elasticsearch中对数组没有特殊的映射需求,建立索引时与text是一样的,也是分词处理后得到多个词条,建立倒排索引。
有一点要注意的是数组内的元素,数据类型要一致,比如都是字符器,都是日期,或都是数值,不能混搭,自动mapping是以第1位元素来决定type的,如果混搭,数组内与第1位元素类型不一致的元素,索引时会报错。
  1. Null类型
    底层的Lucene不能存储null值,null类型的不会被索引:
"null_value":null, "empty_array":[], "array_with_null_value":[ null ]

  1. 复杂对象
    复杂对象一般有一层或多层嵌套,即对象中的属性,类型也是对象,有时候还混搭着数组一类的,举个例子:
{ "address": { "country": "CN", "province": "GD", "city": "SZ" }, "name": "Herry", "age": 28, "birth_date": "1992-04-29" }

人员信息除了姓名年龄外,还有地址信息address,而address属性本身也是个对象,里面包含了country、province、city三个属性,查看它的mapping信息,也是呈嵌套结构(内容有删除,只保留体现层级的属性):
{ "person": { "mappings": { "info": { "properties": { "address": { "properties": { "city": { "type": "text" }, "country": { "type": "text" }, "province": { "type": "text" } } } } } } } }

Elasticsearch在存储层级结构的数据对象时,会做一些扁平化处理,Luence文档是一组KV结构的列表,把上述的数据存储成这样:
{ "address.country": "CN", "address.province": "GD", "address.city": "SZ", "name": "Herry", "age": 28, "birth_date": "1992-04-29" }

所以最终想要根据address下的country作为条件进行查询,应该这么写:
GET /person/info/_search { "query": { "match": { "address.country": "CN" } } }

  1. 数组内的复杂对象
    数组内的元素如果是基础数据类型还好说,只要求数组内所有元素类型一致即可,如果里面的元素是一个对象,又是如何索引的呢?
    假如一个数组结构是这样的:
{ "likes": [ { "name": "Three Zhang", "datetime": "2019-12-01 08:58:12"}, { "name": "Four Lee", "datetime": "2019-12-01 09:12:23"}, { "name": "Five Wang", "datetime": "2019-12-01 09:15:58"} ] }

经过扁平化处理(由列变成行),得到的数据将会是这样:
{ "likes.name": ["Three Zhang","Four Lee","Five Wang"], "likes.datetime": ["2019-12-01 08:58:12","2019-12-01 09:12:23","2019-12-01 09:15:58"], }

发现什么问题没?这样处理完了之后,原本对象之间的关联性就没有了,数组内只是一堆无序的元素,如果要查询"Three Zhang在2019-12-01 09:00:00之后有没有给我点过赞"这样的组合条件查询,是会出现一条查询记录的,这跟预期的结果不同,显然是错了。
留意一下这个问题,数组内的元素是对象类型时,需要声明为nested类型,才能得到预期的查询效果,nested类型在后续会有介绍。
小结
本篇主要对mapping的内容进行一些补充,并简单描述了各种复杂对象的底层存储结构,需要注意的是数组内的对象处理,需要声明为nested才能得到正确的结果。
专注java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区
Elasticsearch系列---定制mapping

文章图片


    推荐阅读