索引的”表结构”

类比关系型数据库,新建一张表时,需要先进行表结构设计(字段类型、默认值、是否非空、索引…)。
对于 ES 来说,这种 “表结构” 的概念就叫做 “映射(Mapping)”。

Mapping 类似于数据库中 schema 的概念,主要是对索引的一些设置信息,作用如下:

  • 确认索引分片个数,副本分片个数(主分片个数不可更改
    • 7.x 版本默认 主分片 和 副本分片 数目都是1
    • 主分片数涉及文档定位具体分片的路由算法,所以被设定成不可更改
  • 确认索引的映射模式
    • 新写入文档字段与Mapping的同步模式
  • 定义索引中字段的信息,如字段名称、字段类型(不可更改)
    • Lucene 特性,写入的倒排索引不可更改
  • 定义字段倒排索引的设置(分析器、是否索引、是否储存)

参考:Kibana代码演示:Mapping创建索引

问题:索引部分信息无法更改,那有更改的需求咋办?

解答:是无法直接在原索引上进行上述的更改,可以按照新要求的结构,新建一个索引,然后将旧索引的数据,复制过去,这个复制索引数据的操作,叫做 reindex。

ES 数据类型

  • 简单类型
    • String:
      • Text:用作全文检索
      • Keyword:用作精确精索
    • Date
    • Integer / Byte / Long / Floating
    • Boolean
    • IPv4 & IPv6
  • 复杂类型
    • 对象类型(object)
    • 嵌套对象类型(nested)
  • 特殊类型
    • geo_point & geo_shape
    • percolator

O2 目前字段类类型选择策略:

  • 字符串
    • 需要全文分词检索:Text,并指定分词器,一般手动选择 ik_max_word
    • 精确搜索:Keyword,默认是索引,如果不需要索引设置 index: false
  • 状态值:
    • Byte (1-是,0-否)
    • Boolean
  • 时间
    • Long : 统一转成毫秒数存储,避免转化时区问题
  • 金额:
    • 用 Floating 里面的 scaled_float,弹性因子2位,就是 2 位小数的浮点数
  • 对象 / 数组
    • 嵌套对象

问题:数组是怎么回事?不是有 对象类型 吗,这个 嵌套类型 是干嘛的?

先说明数组问题: ElasticSearch 没有数组类型,任意的字段都可以包含0个或者多个值,就是说任意的字段,只要写入多个类型相同的值,就自动转成数组存储,开箱即用。 在上述数组的特性之下,对象类型的数组,数组内每个对象在底层都不是分开的,对象间的关系丢失,所以才有嵌套对象。对象仅适用于一对一的关系,当提升成对象数组时,会出现处理结果与预期不合的现象,所以会有嵌套类型来解决对象类型的这种缺陷。

对象数组 vs 嵌套对象数组数据举例:

  1. {
  2. "name_object":[
  3. {
  4. "first" : "John",
  5. "last" : "Smith"
  6. },
  7. {
  8. "first" : "Alice",
  9. "last" : "White"
  10. }
  11. ],
  12. "name_nested":[
  13. {
  14. "first" : "John",
  15. "last" : "Smith"
  16. },
  17. {
  18. "first" : "Alice",
  19. "last" : "White"
  20. }
  21. ]
  22. }

两者在底层的存储结构:
对于 object 数组,ES底层会将其展开扁平的结构,此时 “john” 与 “smith” 的一对一关系丢失,转而变成多对多的关系

  1. {
  2. "name_object": {
  3. "name_object.first": [
  4. "alice",
  5. "john"
  6. ],
  7. "name_object.last": [
  8. "smith",
  9. "white"
  10. ]
  11. },
  12. "name_nested": [
  13. {
  14. "first": "john",
  15. "last": "smith"
  16. },
  17. {
  18. "first": "alice",
  19. "last": "white"
  20. }
  21. ]
  22. }

参考:Kibana演示代码:object与nested数组演示

映射模式(Dynamic Mapping)

描述文档可能具有的字段或者属性。如:字段类型、默认值、分析器、是否被索引等。
某个字段的 Mapping 属性不可改,比如某个字段指定类型为 byte,无法更新 Mapping 改成 其他类型。

动态模式

“dynamic” : “true” ,也是默认模式。
创建索引时可以不指定 Mapping,每次添加文档时,ES会对文档的字段类型进行猜测,并将信息动态更新到 Mapping 上。

类型自动识别:

JSON 类型 ElasticSearch 类型
字符串
- 匹配日期格式,设置成 Date
- 匹配数字设置为 float 或者 long,该选项默认关闭
- 设置为 Text,并且增加 keyword 子字段,默认索引字符长度 256
布尔值 boolean
浮点数 float
整数 long
对象 Object
数组 由第一个非空数值的类型所决定
空值 忽略

参考:Kibana演示代码:自动映射演示

静态模式

“dynamic” : “false” ,o2现在用的模式。
创建索引时需要指定 Mapping,添加文档时,不存在 Mapping 里面的字段, ES 不会进行猜测然后动态更新到 Mapping里。

参考:Kibana演示代码:静态模式映射演示

严格模式

“dynamic” : “strict”
创建索引时需要指定 Mapping,添加文档时,存在 Mapping 里面没有的字段,会直接添加失败。

参考:Kibana演示代码:严格模式映射演示

问题:三种模式,选哪个?

O2经过一些踩坑经验,从一开始的默认动态模式,转型到现在的静态模式。 对于 ES 里面的数据,大概分为两种,被搜索/聚合的或者用来展示的。 拿商品一样,商品名称 肯定要被搜索的,那它绝对要设置在Mapping里,为这个字段建立倒排索引。但是像 商品标签 这种仅仅只是作为商品搜索的一个展示结果,它并没有被 搜索/聚合 的场景(截止1.5.2版本)
在动态模式下,不需要被搜索/聚合的 商品标签 也会被自动映射到 Mapping中,从而使得 Mapping 冗余,而且还可能出现类型自动识别不精准的场景。 在严格模式下,如果Mapping没有为 商品标签 这个字段设置好Mapping,那插入带有这个字段的商品就会报错,鲁棒性欠缺,而且每有一个新的字段就要重新设置 Mapping 然后 reindex,整体维护成本增大。 在静态模式下,我只需要在 Mapping 中设置需要被 搜索/聚合 的字段即可,新加入的字段既不会被写入 Mapping 造成冗余,也不会出现插入报错。


能够更改 Mapping 的字段类型

  • 两种情况
    • 新增字段
      • Dynamic 设为 true 时,一旦有新增字段的文档写入, Mapping 也同时被更新
      • Dynamic 设置为 false,Mapping 不会被更新,新增字段的数据无法被索引,但是信息会出现在 _source 中
      • Dynamic 设置成 Strict,文档写入失败
    • 对已有字段,一旦已经有数据写入,就不再支持修改字段定义
      • Lucene 实现的倒排索引,一旦生成后,就不允许修改
    • 如果希望改变字段类型,必须 Reindex API,重建索引
  • 原因
    • 如果修改了字段的数据类型,会导致已被索引的字段无法被搜索
    • 但是如果是新增的字段,就不会有这样的影响

别名机制

别名就是索引的一个代理。

问题引出:
当需要 更改索引 分片数量 / 更改字段属性(类型/分词器),是无法直接更新 Mapping 的,只能创建新的索引,然后将旧索引的数据通过 Reindex 迁移过去。

这里举例索引名称为:test_index

需要新索引生效,有两个方向:

  • 不停机方向
  1. 代码是指向了索引 test_index,我们新建一个符合新需求的索引 test_index_1,然后将 test_index 的索引通过 Reindex 方式迁移到新索引 test_index_1中
  2. 然后删除 test_index 索引
  3. 再按照新需求新建索引 test_index,将 test_index_1 的数据通过 Reindex 方式迁移过来。

问题:删除 test_index 索引期间,服务访问该索引会报错,因为找不到该索引,又或者按照动态模式自动建立了一个索引。

  • 停机方向
  1. 代码是指向了索引 test_index,我们新建一个符合新需求的索引 test_index_1,然后将 test_index 的索引通过 Reindex 方式迁移到新索引 test_index_1中
  2. 修改源码,让其指向 test_index_1,重启服务

问题:需要改变代码,重启服务。

参考:Kibana演示代码:别名

应用 ES 别名实现零停机索引切换:

  • 在索引创建时,就使用别名机制。
  • 创建索引 test_index_1 ,然后给该索引添加别名 test_index
  • 程序中依赖的索引是 test_index
  • 当 test_index_1 索引需要重建,就新建索引 test_index_2,然后将数据 Reindex 过去
  • 给 test_index_2 添加索引别名 test_index,给 test_index_1 删除别名 test_index

索引别名.png

O2的索引管理

入口:中台->O2-搜索管理->搜索配置->索引配置
搜索模块的种子数据,就是整个项目的 4个索引的表结构(截止V1.5.2版本),在环境初始化、多租户初始化,进

行系统 ES 索引的创建。