背景

通常数据库进行分库分表后,目前比拟惯例的作法,是通过将数据异构到Elasticsearch来提供分页列表查问服务;在创立Elasticsearch索引时,根本都是会参考目前的业务需要、关系数据库中的类型以及对数据的相干布局来定义相干字段mapping的类型.
在Elasticsearch的mapping中的列(或则叫属性),有几个比拟重要的参数(更多参数参考官网文档)

  • 列类型:type

    指定了该列的数据类型,罕用的有text,keyword,date,long,double,boolean以及objectnested,不同的类型也有对应的不同查问形式,创立之后是不能批改的;

  • 是否可索引:index

    index选项管制字段值是否被索引。它承受trueorfalse,并且默认为true. 未索引的字段不可查问,当然也不能做为排序字段。

然而在理论的开发过程中,又会有需要对现有的mapping的type进行批改(相似对MySQL数据表的字段进行DDL操作)的诉求。比方商品上的价格price字段,按原来的业务剖析,只须要提供数据返回即可,在创立索引时类型定义了keyword了,并且index设置成了false,这时咱们须要依据价格的范畴查问或则进行排序操作,就心愿对mapping进行调整,将类型批改成数字类型,索引也须要加上;明天针对Elasticsearch的Mapping类型进行批改,探讨几个可行的计划

计划1:使用reindex

遇到问题第一工夫,咱们应该是查问官网文档是否有相干的操作阐明,在官网文档中,的确还能找到对已有mapping更新的相干apiput-mapping,通过这个文档,很快能够找到文档中对批改已有mapping的列的形式(参考官网文档),同时也提到的通过reindex的形式来批改已有类型的形式;

除了反对的mapping parameters外,您不能更改现有字段的映射或字段类型。更改现有字段可能会使已编制索引的数据有效。如果您须要更改字段的映射,请应用正确的映射创立一个新索引并将您的数据从新索引reindex到该索引中。

如原来索引的mapping如下

PUT /users{  "mappings" : {    "properties": {      "user_id": {        "type": "long"      }    }  }}//加一了两条数据POST /users/_doc?refresh=wait_for{    "user_id" : 12345}POST /users/_doc?refresh=wait_for{    "user_id" : 12346}

这时想批改user_id的类型为keyword,咱们间接是批改不了的。

//尝试间接批改type,行不通,会报错PUT /users/_mapping{    "properties": {        "user_id": {            "type": "keyword"        }    }}//报错信息{  "error": {    "root_cause": [      {        "type": "illegal_argument_exception",        "reason": "mapper [user_id] of different type, current_type [long], merged_type [keyword]"      }    ],    "type": "illegal_argument_exception",    "reason": "mapper [user_id] of different type, current_type [long], merged_type [keyword]"  },  "status": 400}

按官网文档说的reindex从新索引可按以下步骤操作

操作步骤

第一步:创立新的索引new_usersuser_id的类型定义成keyword

PUT /new_users{  "mappings" : {    "properties": {      "user_id": {        "type": "keyword"      }    }  }}

第二步:将原user索引标记为只读

管制咱们的利用零碎,数据停写不再向老索引中写数据,并且最好对老索引进行只读操作设置,保障在reindex的过程中,不要生产新数据,导致新老索数据不统一;

//设置索引为读写的PUT /users/_settings{  "settings": {    "index.blocks.write": true  }}

第三步:将原user索引中的数据迁徙到new_users

POST /_reindex{  "source": {    "index": "users"  },  "dest": {    "index": "new_users"  }}

reindex还有很多的参数能够配置,包含从近程的一个集群迁徙数据都是能够的,具体可参考:Reindex API

如果新的索引的mapping的定义与原索引的定义有差别的,会按新索引定义的dynamic规定进行数据的迁徙,具体的,能够参考:dynamic

dynamic设置管制是否能够动静增加新字段。它承受三种设置:

阐明
true新检测到的字段被增加到映射中。(默认); 新增的数据类型的规定,能够参考:dynamic-mapping
false疏忽新检测到的字段。这些字段不会被编入索引,因而将无奈搜寻,但仍会呈现在_source返回的命中字段中。这些字段不会增加到映射中,必须明确增加新字段。
strict如果检测到新字段,则会抛出异样并回绝文档。必须将新字段显式增加到映射中。

同时将原user索引标记为可读写

//设置索引为可读写PUT /users/_settings{  "settings": {    "index.blocks.write": false  }}

第四步:切换到应用新的mapping

  1. 能够将利用零碎中的配置改成新索引
  2. 也能够通过索引的别名的形式为新索引减少原来老索引的别名来操作,为索引减少别名参考文档:Add index alias API,在减少别名前,须要删除原来的老索引;
//为索引减少别名 根本格局PUT /<index>/_alias/<alias>POST /<index>/_alias/<alias>//为new_users索引减少别名usersPUT /new_users/_alias/users//没有删除老索引前,是减少不了别名的,须要先删除老别名{  "error": {    "root_cause": [      {        "type": "invalid_alias_name_exception",        "reason": "Invalid alias name [users], an index exists with the same name as the alias",        "index_uuid": "8Rbq_32BTHC4CoO_CqWdXA",        "index": "users"      }    ],    "type": "invalid_alias_name_exception",    "reason": "Invalid alias name [users], an index exists with the same name as the alias",    "index_uuid": "8Rbq_32BTHC4CoO_CqWdXA",    "index": "users"  },  "status": 400}

计划优劣剖析

【长处】操作简略,官网计划

该计划,不须要对原索引做操作,在线即可进行,并且操作步骤也简略;也是官网文档提供的计划。

【毛病】数据量大迁徙耗时长

当数据最大时,这个数据迁徙会比拟耗时

论断

当数据量小时,并且心愿mapping比拟规整难看,该计划是比拟举荐的。当数据量大时,可能该计划在数据迁徙过程中会比拟耗时,须要评估是否可行;

计划2:使用multi-fields

为不同的目标以不同的形式索引同一个字段通常很有用。这就是multi-fields的目标。例如,一个string字段能够映射为text用于全文搜寻的字段,也能够映射keyword为用于排序或聚合的字段;
在这个计划中,利用的是mapping参数fields来对同一个列,定义多种数据类型;具体[【官网文档】multi-fields] (https://www.elastic.co/guide/en/elasticsearch/reference/7.5/multi-fields.html)

操作步骤

第一步:为列减少fields属性

还是以下面的users这个索引为例,咱们还是想将user_id的类型定义成keyword

PUT /users/_mapping{    "properties":{        "user_id":{            "type":"long",            "fields":{                "raw":{                    "type":"keyword"                }            }        }    }}

操作实现后,在usersuser_id列下,就会多出一个raw的子属性;在咱们失常写数据user_id时,会主动生成这两个索引,一个是long类型的user_id,以及keyword类型的user_id.raw(留神这里有个点,跟子对象拜访形式一样);
在put mapping时,type参数必须给,并且须要跟原来的类型统一,fields中新定义的子属性能够多个;

【可选】第二步:历史数据更新

针对历史数据须要解决,能够借助_update_by_query来更新数据,只须要将原来的索引再写一次,即可将新加的字段写入数据。

POST /users/_update_by_query {   "query":{       "exists":{           "field":"user_id"       }   },   "script":{       "source":"ctx._source.user_id=ctx._source.user_id ",       "lang":"painless"   }}// query 局部为须要更新数据过滤条件,可依据业务规定写// script 更数据的逻辑,这个根本能够不改

计划优劣剖析

【长处】不影响原索引,同一列能够定义多种类型

通过这形式不会影响原来的索引数据,能够不必批改当初的应用程序的读写形式,对应用程序所有按原来逻辑执行,对利用方无感知,十分优化。只须要有应用新类型的场景应用即可,能够说影响是最小的;
同时只是做了一个定义,执行速度是十分快的,对Elasticsearch服务根本不会有太大影响;并且对于同一个列能够定义多个类型,比方商品名称,在多国多语言环境下能够依据不同语言定义多个列,对应应用不同的分词器;

【毛病】老数据不会主动创立子索引,多出额定的存储

老数据不会主动创立索引,因为须要多出新的索引来,会减少额定的存储;

论断

1、须要对多一列创立多个索引类型时,是一个十分举荐的计划;
2、对于新索引,只有新业务应用,对老数据没有诉求的,也十分举荐该计划;

计划3:使用copy_to

copy_to是将多个字段的值,合并到一个字段中,便于搜寻。然而也能够实现一个字段存在多个类型的需要。具体参考【官网文档】copy_to

操作步骤

还是用下面的users这个索引为例,为user_id创立一个copy列:user_id_raw类型定义成keyword

PUT /users/_mapping{    "properties":{        "user_id_raw":{          "type":"keyword",          "copy_to":"user_id"        }    }}

这个计划与计划2:multi-fields根本是一样的,只是创立列的形式不同,优缺点都一样;

参考资料

  • [1]【官网文档】Mapping parameters
  • [2]【官网文档】Mapping Field datatypes
  • [3] [【官网文档】multi-fields] (https://www.elastic.co/guide/en/elasticsearch/reference/7.5/multi-fields.html)
  • [4]Elasticsearch Rename Index
  • [5]elasticSearch7.x—mapping中的fields属性||copy_to配置(同一个字段两种类型)
  • [6]《Elasticsearch:权威指南》Mapping -- Mapping parameters -- fields(multi-fields)

作者:京东批发 周德东

起源:京东云开发者社区 转载请注明起源