如何设计一个高性能Elasticsearch mapping

Posted 双子孤狼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何设计一个高性能Elasticsearch mapping相关的知识,希望对你有一定的参考价值。

前言

在关系型数据库设计当中,表的设计尤其重要,然而关系型数据库更关注的表与表之间的关系,以及表的划分是否合理,而 Elasticsearch 中却更加关注字段类型的设计,一个好的字段类型设计可以更好的利用 Elasticsearch 的搜索分析特性。

mapping

如果说我们想要用好 Elasticsearch,那么就必须要先了解 mapping 什么是 mapping。一句话:mapping是定义如何存储和索引文档及其包含的字段的过程。

mapping 能做什么

前面我们提到,在 Elasticsearch 中,mapping 类似于传统关系型数据库的表结构定义,主要做以下几件事:

  • 定义字段名称和字段类型。
  • 定义倒排索引相关的配置,比如是否被索引,是否可以被分词等。

mapping 可以分为两种:Dynamic mappingExplicit mapping

Dynamic mapping

Dynamic mapping 即:动态映射。动态映射顾名思义就是 mapping 会被动态创建,也就是说我们不需要定义 mapping 就可以往一个索引插入数据,插入索引数据之后,Elasticsearch 会根据插入的数据自动推测数据类型,进而动创建 mapping

比如下面就是往一个不存在的索引 index_001 插入一条数据:

PUT index_001/_doc/1
{
  "name":"lonely wolf",
  "age": 18,
  "create_date":"2021-05-19 20:45:11",
  "update_date":"2021-05-23"
}

插入数据之后,执行 GET index_001 来查询一下索引信息:

在这里插入图片描述

可以发现,这时候索引已经被自动创建了,而且 age 字段被 Elasticsearch 定义为 long 类型,update_date 被定义为 data 类型,其他两个字段则被推测为 text 类型。

Elasticsearch 中自动映射类型规则可以通过 dynamic 参数进行配置,dynamic 类型有 4 种:

dynamic=true

默认值。当设置为 true 时,一旦有新字段插入文档,则 mapping 会被同步更新。

我们在上面的文档中再插入一个新文档,新文档新增一个 address 字段:

PUT index_001/_doc/2
{
  "name":"lonely wolf2",
  "age": 20,
  "create_date":"2021-05-23 11:37:11",
  "update_date":"2021-05-23",
  "address":"广东深圳"
}

然后再查看一下 mapping,可以看到 mapping 已经新增了一个 address 字段,mapping 字段被更新意味着该字段会加入索引:

在这里插入图片描述

dynamic=runtime

这个类型和 true 类型非常相似,但是有一个非常大的区别就是,虽然加入新字段也会更新 mapping,但是新加入的字段不会被索引,也就是不会使得索引变大,不过虽然不被索引,但是新加入的字段依然可以被查询,只是查询的代价会更大。所以这种类型一般不建议用在经常查询的条件字段上,而更适合用在一些不确定数据结构的日志类索引中。

修改 dynamic 类型:

PUT index_001/_mapping
{
  "dynamic":  "runtime"
}

新增一个文档,并加入一个新字段:

PUT index_001/_doc/3
{
  "email":"123@qq.com"
}

最后询一下 mapping,可以看到字段属性是 runtime,而且类型是 keyword

在这里插入图片描述

下表就是自动创建 mapping 时,Elasticsearch 的映射关系:

插入数据类型dynamic=truedynamic=runtime
null不会添加任何字段不会添加任何字段
true 或 falsebooleanboolean
doublefloatdouble
integerlonglong
objectobjectobject
string(通过 date 校验)datedate
string(通过 numreic 校验)float 或 longdouble 或 long
string(没有通过 date 或 numreic 校验)text ,并且同时会创建一个 keyword 子域keyword
array取决于数组中第一个非 null 值取决于数组中第一个非 null 值

PS:keyword 表示 不参与分词。

dynamic=false

当设置为 false 时,新加入的字段不会被更新到 mapping,也就是说新字段不会被索引,故以这个字段为条件进行搜索时,无法被搜索到(这一点要注意和 runtime 类型进行区分),不过虽然无法被索引,但是该字段会出现在 _source 中。也就是说该字段不能作为查询条件,但是能被查询出来

接下来我们将 dynamic 修改为 false,并新增一个字段来验证,可以发现新增的字段会出现在 _source 中,但是无法作为条件被查询出来:

在这里插入图片描述

dynamic=strict

这种类型最为严格,表示不允许新增一个不在 mapping 中的字段,一旦新增的字段不在 mapping 定义中,则直接报错:

在这里插入图片描述

是否可以修改 mapping 中的数据类型

Elasticsearch 中,一旦一个字段被定义在了 mapping 中,是无法被修改的,因为一旦字段被修改了,就会无法被索引(新增字段除外),所以一般我们需要修改索引的话,都会重建索引,并采用 reindex 操作来迁移数据。

关闭 dynamic mapping

可以通过以下两个配置来关闭 dynamic mapping,以下两个属性默认值均为 true,如果需要关闭,则需要修改为 false

action.auto_create_index: true
index.mapper.dynamic: true

Explicit mapping

Explicit mapping 即:显式映射。也就是说这时候我们需要显示的定义字段类型。

Elasticsearch 中支持的字段类型很多,在这里就举一些比较常用的字段类型:

text 类型

这是最常用的一种类型,存储字符串,用于全文索引。当字段被定义为 text 类型时,默认不能用于聚合,排序等操作:

在这里插入图片描述

可以看到,用 text 类型字段排序汇报凑,如果想要允许这些操作,可以通过设置 fielddata=true,如下

PUT my-index-011/_mapping
{
  "properties": {
    "my_field": { 
      "type":     "text",
      "fielddata": true
    }
  }
}

field 字段存储在堆内存中,因为其涉及到的计算比较消耗性能,所以一般不建议设置 fielddata=true,而是通过建立一个 keyword 子域来实现(默认方式):

PUT index_111
{
  "mappings": {
    "properties": {
      "my_field": { 
        "type": "text",
        "fields": {
          "keyword": { 
            "type": "keyword"
          }
        }
      }
    }
  }
}

这种定义方式我们可以将一个字段同时作为 textkeyword 类型使用,如果要用于聚合或者排序等操作则可以使用 字段名.keyword 来作为字段名来进行操作:

在这里插入图片描述

keyword 类型

这种类型也非常常用,该字段存储的数据表示一个整体,不可被分词,所以一般不会用来定义大本文的全文检索字段,而是用来存储一些结构化的字符串,比如:id,邮箱,标签等。

keyword 类型一般用于聚合,排序等操作。除此之外,该字段还有两种衍生类型:constant_keywordwildcard

  • constant_keyword:一般用于定义常量类型,比如一个索引中某一个字段全部为同一个值,可以定义为这种类型。
  • wildcard:一般用于模糊匹配查询或者正则匹配查询。

如下就是一个模糊匹配查询的示例(可以配合通配符使用,类似于关系型数据库的 like 操作):

GET index_112/_search
{
  "query": {
    "wildcard": {
      "my_wildcard": {
        "value": "*quite*lengthy"
      }
    }
  }
}

date 类型

用于定义日期类型,定义日期类型的同时,可以通过 format 来指定日期的格式:

PUT index_113
{
  "mappings": {
    "properties": {
      "date": {
        "type":   "date",
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
      }
    }
  }
}

numeric 类型

Elasticsearch 中提供了比较多的格式用来表示不同长度的数字类型:

数字类型长度
long64 位有符号整数。范围:-2 的 63 次方到 2 的 63 次方 -1
integer32 位有符号整数。范围:-2 的 31 次方到 2 的 31 次方 -1
short16 位有符号整数。范围:-32768 到 32767
byte8 位有符号整数。范围:-128 到 127
double64 位双精度小数
float32 位单精度小数
half_float16 位单精度小数
scaled_float带有缩放因子的浮点数,一般适用于存放金额之类的数据。比如 18.88 元,缩放因子是 100,那么在索引时会被索引为 1888(即:原值 * 缩放因子)
unsigned_long64 位无符号整数。范围:0 到 2 的 64 次方减 1

定义方式如下所示:

PUT index_002
{
  "mappings": {
    "properties": {
      "number_of_bytes": {
        "type": "integer"
      },
      "time_in_seconds": {
        "type": "float"
      },
      "price": {
        "type": "scaled_float",
        "scaling_factor": 100
      }
    }
  }
}

boolean 类型

布尔类型比较简单,只有 truefalse 两种:

PUT index_001
{
  "mappings": {
    "properties": {
      "is_published": {
        "type": "boolean"
      }
    }
  }
}

其他类型

除了上面介绍的一些比较常用的数据类型,Elasticsearch 中还有一些高级数据类型:如 Nested(嵌套类型),地理数据类型,ip 类型等。

总结

Elasticsearch 中支持动态 mapping 和显示 mapping 两种,在使用中有时候可以先插入一条数据到临时索引,等自动生成 mapping 之后,在对现有 mapping 进行修改调整,在字段上尤其要考虑好 text 类型和 keyword 类型的设置,如果需要支持全文搜索和分词搜索,则需要使用 text 类型,需要支持关键字模糊搜索或者聚合排序等操作可以考虑使用 keyword 字段。

以上是关于如何设计一个高性能Elasticsearch mapping的主要内容,如果未能解决你的问题,请参考以下文章

百亿规模下,Elasticsearch如何打出实时计算架构设计演进和性能优化的组合拳!

ElasticSearch高性能设计

ElasticSearch高性能设计

Elasticsearch性能优化

ElasticsearchElasticsearch 集群 运维 高性能 架构设计 高负载

突破性能瓶颈!ElasticSearch百亿级数据检索优化案例