Elasticsearch系列索引管理

编程

索引的CRUD

为了更好地贴切我们的业务数据需求,我们开始更精细的管理我们的索引。

创建索引

创建索引的语法示例如下:

PUT /music

{

"settings": {

"number_of_shards": 3,

"number_of_replicas": 1

},

"mappings": {

"children": {

"properties": {

"name": {

"type": "text",

"fields": {

"keyword": {

"type": "keyword",

"ignore_above": 256

}

}

}

}

}

}

}

settings内的参数

  • number_of_shards:每个索引的primary shard数量,索引创建后不可修改。
  • number_of_replicas: 每个索引的replica shard的数量,可以随时修改。

mappings内的参数

  • type: 6.3.1版本只允许设置一个type
  • properties:类型映射具体信息,索引文档的字段名称,类型,分词器都在里面指定。

默认Elasticsearch是允许自动创建索引的,生产环境上为了避免自动索引可能出现的隐患,可以禁止自动创建索引,修改elasticsearch.yml配置文件即可:

action.auto_create_index: false

修改索引

可以单独修改setting部分和mapping部分,修改setting部分示例如下:

PUT /music/_settings

{

"number_of_replicas": 2

}

如果要修改mapping信息,如给索引新增字段length、likes、content,示例如下:

PUT /music/_mapping/children

{

"properties": {

"length": {

"type": "long"

},

"likes": {

"type": "long"

},

"content": {

"type": "text",

"fields": {

"keyword": {

"type": "keyword",

"ignore_above": 256

}

}

}

}

}

删除索引

DELETE /music

DELETE /music,content

DELETE /music*

DELETE /_all

DELETE /*

如上命令均可删除索引,但此操作一定要慎重,反复确认后再操作,误删的后果不可想像,建议删除操作一定要设置操作权限,另外Elasticsearch可以设置只限定索引名称进行删除,不允许通配符或_all删除大量的索引,作如下设置即可:

action.destructive_requires_name: true

误删索引的后果非常严重,请在操作权限上加把锁,宁可麻烦也不要误删。

查看索引信息

GET /music

GET /music/_settings

GET /music/_mapping

三条命令可以查看索引的完整信息,只查setting信息,只查mapping信息。

分词器设置

analysis是索引设置中非常重要的一部分,默认的分词器我们前面有介绍,有兴趣可以翻一下。我们可以为索引单独配置特有的分词器,或者自定义分词器。

修改分词器设置

例如,我们为music索引创建一个新的分词器,叫做music_std,启用英文停用词列表:

PUT /music

{

"settings": {

"analysis": {

"analyzer": {

"music_std": {

"type": "standard",

"stopwords": "_english_"

}

}

}

}

}

此命令只能在创建时候执行,已经存在的索引执行会报错。

我们对music索引进行分词器测试:

GET /music/_analyze

{

"analyzer": "music_std",

"text": "get up brightly early in the morning"

}

测试结果是"in","the"这两个词已经被正确的移除掉了。

自定义分词器

Elasticsearch对分词器的应用设置得非常灵活,用户可以根据自己的需求灵活定制字符过滤器、分词器、词单元过滤器来创建自定义的分词器。

文档的分词过程包含以下几步:

  • 字符过滤器

对字符串进行预处理,如HTML标签清洗<span>Love</span> --> Love,I & you --> I and you等等。

  • 分词器

把字符串切分成单个的词条,如英文的按空格和标点切分,中文的按词语切分,针对不同的语言,有不同的分词器,有相对简单的标准分词器,也有特别复杂的中文分词器,里面包含了非常复杂的切分逻辑如:

I Love you --> I/Love/you

我和我的祖国 --> 我/和/我的/祖国

  • Token过滤器

    将分词器得到的词条进一步的处理,如改变词条(英文词干提取loves --> love),删除无实际意义的词条(英文的a, and, this,中文的"的","了","吗"),增加词条(补充同义词)

如果我们自定义分词器,可以从这三个组件入手,可以自行替换。我们举一个示例:

PUT /music

{

"settings": {

"analysis": {

"char_filter": {

"&_to_and": {

"type": "mapping",

"mappings": ["&=> and"]

}

},

"filter": {

"my_stopwords": {

"type": "stop",

"stopwords": ["the", "a"]

}

},

"analyzer": {

"my_analyzer": {

"type": "custom",

"char_filter": ["html_strip", "&_to_and"],

"tokenizer": "standard",

"filter": ["lowercase", "my_stopwords"]

}

}

}

}

}

上面示例中我们自定义的分词器有如下特点:

  • 字符过滤器:把&转换成and,并加上html_strip处理html文本
  • token过滤器:将"the","a"作为停用词,全部改成小写

我们对这个分词器进行测试:

GET /music/_analyze

{

"text": "you & me the love, <a>, HAHA!!",

"analyzer": "my_analyzer"

}

响应的结果:

{

"tokens": [

{

"token": "you",

"start_offset": 0,

"end_offset": 3,

"type": "<ALPHANUM>",

"position": 0

},

{

"token": "and",

"start_offset": 4,

"end_offset": 5,

"type": "<ALPHANUM>",

"position": 1

},

{

"token": "me",

"start_offset": 6,

"end_offset": 8,

"type": "<ALPHANUM>",

"position": 2

},

{

"token": "love",

"start_offset": 13,

"end_offset": 17,

"type": "<ALPHANUM>",

"position": 4

},

{

"token": "haha",

"start_offset": 24,

"end_offset": 28,

"type": "<ALPHANUM>",

"position": 5

}

]

}

可以看到,"the"作为停用词被移除了,&变成了"and",html标签<a>移除了,HAHA小写处理后得到haha。

自定义分词器后,如果需要应用在索引上,需要将它绑定到具体的字段上:

PUT /music/_mapping/children

{

"properties": {

"content": {

"type": "text",

"analyzer": "my_analyzer"

}

}

}

后面只要有新的文档进行索引,在content字段上都会使用我们自定义的分词器。

映射对象

root object

映射对象信息是一组JSON结构,最顶层的叫根对象(root object),包括内容如下:

  • properties: 索引中每个字段的映射信息。
  • metadata:各种元数据信息,以下划线开头,如_id,_source,_type。
  • settings:设置项信息,如analyzer。
  • 其他settings:比如include_in_all

properties

主要是指文档字段和属性最重要的三个设置:

  • type: 数据类型,如text、date、long等。
  • index: 该字段是否需要全文搜索(analyzed),或精准搜索(not_analyzed)或是不支持搜索(no)。
  • analyzer: 文档索引和搜索时的分词器。

例如节选了以下properties信息:

{

"music": {

"mappings": {

"children": {

"properties": {

"author": {

"type": "text",

"analyzer": "english"

}

}

}

}

}

}

_source

_source字段存储的内容包含文档的JSON字符串,_source字段在写入磁盘前会被压缩。

_source存储的内容才是我们真正关心的数据,我们可以更加方便的完成这些事:

  • 查询的时候可以一次性拿到完整的document,不需要先拿document id,再发送一次请求拿document
  • partial update基于_source实现
  • reindex时,直接基于_source实现,不需要从数据库(或者其他外部存储)查询数据再修改
  • 可以基于_source定制返回field
  • debug query更容易,因为可以直接看到_source

_all

建立索引时将所有field拼接在一起,作为一个_all field ,没指定任何field进行搜索时,就是搜索_all field,一般轻量搜索中用得比较多。

如果不需要_all field,可以设置成禁用:

PUT /music/_mapping/children

{

"_all": {"enabled": false}

}

也可以指定某些field不加入_all field

PUT /music/_mapping/children

{

"properties": {

"author": {

"type": "text",

"include_in_all": false

}

}

}

metadata

文档标识主要的几个字段:

  • _id:文档ID
  • _type:类型名称,6.x以后一个索引只会有一个type
  • _index: 文档所在的索引名称

这三个字段是用来标识一个独一无二的文档所在的位置信息,从这三个字段我们基本上可以定位出来该文档存储在哪个shard中。

动态映射

dynamic属性

Elasticsearch索引文档时,如果JSON结构出现新的字段,Elasticsearch会根据dynamic mapping规则来识别字段的数据类型,并自动增加新的字段,如果我们对文档的JSON结构有较严格的规定,这种自动增加字段的行为,就不是我们期望的操作,我们可以为properties设置dynamic属性来决定这种行为:

  • true: 动态添加新的字段
  • false:忽略新的字段
  • strict: 遇到新的字段,抛出异常

这个dynamic参数可以在任何一层的object中使用,如:

PUT /music

{

"mappings": {

"children": {

"dynamic": "strict",

"properties": {

"name": {

"type": "text"

},

"address": {

"type": "object",

"dynamic": "true"

}

}

}

}

}

如果children下面遇到新字段,就会抛出异常

如果address内部对象中遇到新字段,会动态创建该字段

示例:

# address内部对象增加两个新字段

PUT /music/children/1

{

"name":"sunshine",

"address": {

"province": "gd",

"city": "sz"

}

}

创建成功,响应如下:

{

"_index": "music",

"_type": "children",

"_id": "1",

"_version": 1,

"result": "created",

"_shards": {

"total": 2,

"successful": 1,

"failed": 0

},

"_seq_no": 0,

"_primary_term": 1

}

# children下直接增加新字段author

PUT /music/children/1

{

"name":"sunshine",

"author":"Johnny Cash"

}

创建失败,报错响应如下:

{

"error": {

"root_cause": [

{

"type": "strict_dynamic_mapping_exception",

"reason": "mapping set to strict, dynamic introduction of [author] within [children] is not allowed"

}

],

"type": "strict_dynamic_mapping_exception",

"reason": "mapping set to strict, dynamic introduction of [author] within [children] is not allowed"

},

"status": 400

}

定制dynamic mapping策略

Elasticsearch在运行中遇到新增的字段时,会根据动态映射模板为新的字段定义类型,但字段类型是根据首次遇到的字段值来定义的,可能会出现误判的情况。

我们先举一个反例,假设我们有一个新增的字段remark,里面的内容是"2019-12-17",是一个日期格式的内容,Elasticsearch会把这个note字段设置成日期格式,但remark字段第二条数据过来的却是"Comment Submit",这只是一段文本,remark字段已经是日期格式了,第二条保存就会抛出异常。

针对日期检测,我们可以选择关闭,如下:

PUT /music

{

"mappings": {

"children": {

"date_detection": false

}

}

}

但我们针对Long类型,Boolean类型的,同样有这种情况,逐一关闭可行性不高,为此我们需要使用动态模板配置。

动态模板

使用动态模板(dynamic template),我们可以通过字段名称或数据类型来应用不同的映射,来定制自己的模板。

例如我们使用字段名称后缀的方式:

PUT /music

{

"mappings": {

"children": {

"dynamic_templates": [

{ "en": {

"match": "*_en",

"match_mapping_type": "string",

"mapping": {

"type": "text",

"analyzer": "english"

}

}}

]

}

}

}

这个含义是如果字段以_en结尾,那么类型为text,analyzer为english,否则类型为string,analyzer为standard。

测试内容:

PUT /music/children/1

{

"content": "you are my sunshine"

}

PUT /music/children/2

{

"content_en": "you are my sunshine"

}

理论上content使用standard分词器,4个单词均可被索引到,content_en字段使用english分词器,are作为停用词会被移除掉。

对索引进行搜索可知:

GET /music/children/_search

{

"query": {

"match": {

"content_en": "are"

}

}

}

结果是空

{

"took": 1,

"timed_out": false,

"_shards": {

"total": 5,

"successful": 5,

"skipped": 0,

"failed": 0

},

"hits": {

"total": 0,

"max_score": null,

"hits": []

}

}

使用其他的关键词,或使用content字段,搜索均能出结果,符合预期。

小建议

以上只是动态映射模板的一个小案例,真实生产环境中文档的复杂度远高于此,对文档的结构而言,优先手动创建索引,明确每个字段的含义和数据类型,其次再做通用的动态映射模板,但也需要定时检查索引下的数据类型,以防出现意外情况。

小结

本篇主要介绍索引的相关知识,包含索引的CRUD、自定义分词器、映射对象的知识,最后简单介绍了映射模板的配置,实际生产如果有乃至动态模板配置,肯定远比这个复杂,这里仅作抛砖引玉,谢谢。

专注Java高并发、分布式架构,更多技术干货分享与心得,请关注公众号:Java架构社区

可以扫左边二维码添加好友,邀请你加入Java架构社区微信群共同探讨技术

以上是 Elasticsearch系列索引管理 的全部内容, 来源链接: utcz.com/z/512834.html

回到顶部