清茶豆奶 · 2019年12月27日

Elasticsearch系列---定制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

我们可以在创建索引时指定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架构社区
Java架构社区

推荐阅读
关注数
3
文章数
51
公众号[Java架构社区],专注高并发架构技术干货,持续学习,持续分享
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息