ElasticSearch 索引设计

在MySQL中数据库设计十分重要,同样在ES中数据库设计也是十分重要的

概述

咱们创立索引就像创立表构造一样,必须十分谨慎的,索引如果创立不好前面会呈现各种各样的问题

索引设计的重要性

索引创立后,索引的分片只能通过_split_shrink接口对其进行成倍的减少和缩减

次要是因为es的数据是通过_routing调配到各个分片下面的,所以实质上是不举荐去扭转索引的分片数量的,因为这样都会对数据进行从新的挪动。

还有就是索引只能新增字段,不能对字段进行批改和删除,不足灵活性,所以每次都只能通过_reindex重建索引了,还有就是一个分片的大小以及所以分片数量的多少重大影响到了索引的查问和写入性能,所以可想而知,设计一个好的索引可能缩小前期的运维治理和进步不少性能,所以后期对索引的设计是相当的重要的。

基于工夫的Index设计

Index设计时要思考的第一件事,就是基于工夫对Index进行宰割,即每隔一段时间产生一个新的Index
这样设计的目标
因为事实世界的数据是随着工夫的变动而一直产生的,切分治理能够取得足够的灵活性和更好的性能

如果数据都存储在一个Index中,很难进行扩大和调整,因为Elasticsearch中Index的某些设置在创立时就设定好了,是不能更改的,比方Primary Shard的个数。

而依据工夫来切分Index,则能够实现肯定的灵活性,既能够在数据量过大时及时调整Shard个数,也能够及时响应新的业务需要。

大多数业务场景下,客户对数据的申请都会命中在最近一段时间上,通过切分Index,能够尽可能的防止扫描不必要的数据,进步性能。

工夫距离
依据下面的剖析,天然是工夫越短越能放弃灵活性,然而这样做就会导致产生大量的Index,而每个Index都会耗费资源来保护其元信息的,因而须要在灵活性、资源和性能上做衡量
  • 常见的距离有小时、天、周和月:先思考总共要存储多久的数据,而后选一个既不会产生大量Index又可能满足肯定灵活性的距离,比方你须要存储6个月的数据,那么一开始抉择“周”这个距离就会比拟适合。
  • 思考业务增长速度:如果业务增长的特地快,比方上周产生了1亿数据,这周就增长到了10亿,那么就须要调低这个距离来保障有足够的弹性能应答变动。
如何实现宰割
切分行为是由客户端(数据的写入端)发动的,依据工夫距离与数据产生工夫将数据写入不同的Index中,为了易于辨别,会在Index的名字中加上对应的工夫标识

创立新Index这件事,能够是客户端被动发动一个创立的申请,带上具体的Settings、Mappings等信息,然而可能会有一个工夫错位,即有新数据写入时新的Index还没有建好,Elasticsearch提供了更优雅的形式来实现这个动作,即Index Template

分片设计

所谓分片设计,就是如何设定主分片的个数

看上去只是一个数字而已,兴许在很多场景下,即便不设定也不会有问题(ES7默认是1个主分片一个正本分片),然而如果不提前思考,一旦出问题就可能导致系统性能降落、不可拜访、甚至无奈复原,换句话说,即便应用默认值,也应该是通过足够的评估后作出的决定,而非拍脑袋定的。

限度分片大小
单个Shard的存储大小不超过30GB

Elastic专家依据经验总结进去大家普遍认为30GB是个适合的上限值,实际中发现单个Shard过大(超过30GB)会导致系统不稳固。

其次,为什么不能超过30GB?次要是思考Shard Relocate过程的负载,咱们晓得,如果Shard不平衡或者局部节点故障,Elasticsearch会做Shard Relocate,在这个过程中会搬移Shard,如果单个Shard过大,会导致CPU、IO负载过高进而影响零碎性能与稳定性。

评估分片数量
单个Index的Primary Shard个数 = k * 数据节点个数

在保障第一点的前提下,单个Index的Primary Shard个数不宜过多,否则相干的元信息与缓存会耗费过多的系统资源,这里的k,为一个较小的整数值,倡议取值为1,2等,整数倍的关系能够让Shard更好地均匀分布,能够充沛的将申请扩散到不同节点上。

小索引设计
对于很小的Index,能够只调配1~2个Primary Shard的

有些状况下,Index很小,兴许只有几十、几百MB左右,那么就不必依照第二点来调配了,只调配1~2个Primary Shard是能够,不必纠结。

应用索引模板

就是把曾经创立好的某个索引的参数设置(settings)和索引映射(mapping)保留下来作为模板,在创立新索引时,指定要应用的模板名,就能够间接重用曾经定义好的模板中的设置和映射

Elasticsearch基于与索引名称匹配的通配符模式将模板利用于新索引,也就是说通过索引进行匹配,看看新建的索引是否合乎索引模板,如果合乎,就将索引模板的相干设置利用到新的索引,如果同时合乎多个索引模板呢,这里须要对参数priority进行比拟,这样会抉择priority大的那个模板进行创立索引。

在创立索引模板时,如果匹配有蕴含的关系,或者雷同,则必须设置priority为不同的值,否则会报错,索引模板也是只有在新创建的时候起到作用,批改索引模板对现有的索引没有影响,同样如果在索引中设置了一些设置或者mapping都会笼罩索引模板中雷同的设置或者mapping

索引模板的用处
索引模板个别用在工夫序列相干的索引中。

也就是说, 如果你须要每距离肯定的工夫就建设一次索引,你只须要配置好索引模板,当前就能够间接应用这个模板中的设置,不必每次都设置settings和mappings.

创立索引模板
COPYPUT _index_template/logstash-village{  "index_patterns": [    "logstash-village-*"  // 能够通过"logstash-village-*"来适配创立的索引  ],  "template": {    "settings": {      "number_of_shards": "3", //指定模板分片数量      "number_of_replicas": "2"  //指定模板正本数量    },    "aliases": {      "logstash-village": {}  //指定模板索引别名    },    "mappings": {   //设置映射      "dynamic": "strict", //禁用动静映射      "properties": {        "@timestamp": {          "type": "date",           "format": "strict_date_optional_time||epoch_millis||yyyy-MM-dd HH:mm:ss"        },        "@version": {          "doc_values": false,          "index": "false",          "type": "integer"        },        "name": {          "type": "keyword"        },        "province": {          "type": "keyword"        },        "city": {          "type": "keyword"        },        "area": {          "type": "keyword"        },        "addr": {          "type": "text",          "analyzer": "ik_smart"        },        "location": {          "type": "geo_point"        },        "property_type": {          "type": "keyword"        },        "property_company": {          "type": "text",          "analyzer": "ik_smart"        },        "property_cost": {          "type": "float"        },        "floorage": {          "type": "float"        },        "houses": {          "type": "integer"        },        "built_year": {          "type": "integer"        },        "parkings": {          "type": "integer"        },        "volume": {          "type": "float"        },        "greening": {          "type": "float"        },        "producer": {          "type": "keyword"        },        "school": {          "type": "keyword"        },        "info": {          "type": "text",          "analyzer": "ik_smart"        }      }    }  }}
模板参数
上面是创立索引模板的一些参数
参数名称参数介绍
index_patterns必须配置,用于在创立期间匹配索引名称的通配符(*)表达式数组
template可选配置,能够抉择包含别名、映射或设置配置
composed_of可选配置,组件模板名称的有序列表。组件模板按指定的程序合并,这意味着最初指定的组件模板具备最高的优先级
priority可选配置,创立新索引时确定索引模板优先级的优先级。抉择具备最高优先级的索引模板。如果未指定优先级,则将模板视为优先级为0(最低优先级)
version可选配置,用于内部治理索引模板的版本号
_meta可选配置,对于索引模板的可选用户元数据,可能有任何内容

映射配置

下面咱们配置了映射模板,然而咱们用到了映射,上面咱们说下映射

什么是映射

在创立索引时,能够事后定义字段的类型(映射类型)及相干属性

数据库建表的时候,咱们DDL根据个别都会指定每个字段的存储类型,例如:varchar、int、datetime等,目标很明确,就是更准确的存储数据,避免数据类型格局凌乱,在Elasticsearch中也是这样,创立索引的时候个别也须要指定索引的字段类型,这种形式称为映射(Mapping)

被动创立(动静映射)

此时字段和映射类型不须要当时定义,只须要存在文档的索引,当向此索引增加数据的时候当遇到不存在的映射字段,ES会依据数据内容主动增加映射字段定义。
动静映射规定
应用动静映射的时候,依据传递申请数据的不同会创立对应的数据类型
数据类型Elasticsearch 数据类型
null不增加任何字段
true或者falseboolean类型
浮点数据float类型
integer数据long类型
objectobject类型
array取决于数组中的第一个非空值的类型。
string如果此内容通过了日期格局检测,则会被认为是date数据类型 如果此值通过了数值类型检测则被认为是double或者long数据类型 带有关键字子字段会被认为一个text字段
禁止动静映射
个别生产环境下须要禁用动静映射,应用动静映射可能呈现以下问题
  1. 造成集群元数据始终变更,导致不稳固;
  2. 可能造成数据类型与理论类型不统一;
如何禁用动静映射,动静mappingdynamic字段进行配置,可选值及含意如下
  • true:反对动静扩大,新增数据有新的字段属性时,主动增加对于的mapping,数据写入胜利
  • false:不反对动静扩大,新增数据有新的字段属性时,间接疏忽,数据写入胜利
  • strict:不反对动静扩大,新增数据有新的字段时,报错,数据写入失败

被动创立(显示映射)

动静映射只能保障最根底的数据结构的映射

所以很多时候咱们须要对字段除了数据结构定义更多的限度的时候,动静映射创立的内容很可能不合乎咱们的需要,所以能够应用PUT {index}/mapping来更新指定索引的映射内容。

映射类型

咱们要创立映射必须还要晓得映射类型,否则就会走默认的映射类型,上面咱们看看罕用的映射类型

筹备工作

咱们先创立一个用于测试映射类型的索引
COPYPUT mapping_demo

字符串类型

字符串类型是咱们最罕用的类型之一,咱们操作的时候字符串类型能够被设置为以下几种类型
text
当一个字段是要被全文搜寻的,比方Email内容、产品描述,应该应用text类型,text类型会被分词

设置text类型当前,字段内容会被分词,在生成倒排索引以前,字符串会被分析器分成一个一个词项,text类型的字段不用于排序,很少用于聚合

keyword
keyword类型不会被分词,罕用于关键字搜寻,比方姓名、email地址、主机名、状态码和标签等

如果字段须要进行过滤(比方查姓名是张三公布的博客)、排序、聚合,keyword类型的字段只能通过准确值搜寻到,经常被用来过滤、排序和聚合

两者区别
它们的区别在于text会对字段进行分词解决而keyword则不会进行分词

也就是说如果字段是text类型,存入的数据会先进行分词,而后将分完词的词组存入索引,而keyword则不会进行分词,间接存储,这样划分数据更加节俭内存。

应用案例
咱们先创立一个映射,name是keyword类型,形容是text类型的
COPYPUT mapping_demo/_mapping{  "properties": {    "name": {        "type": "keyword"     },      "city": {        "type": "text",        "analyzer": "ik_smart"     }  }}
插入数据
COPYPUT mapping_demo/_doc/1{  "name":"北京小区",  "city":"北京市昌平区回龙观街道"}
对于keyword的name字段进行准确查问
COPYGET mapping_demo/_search{  "query": {    "term": {      "name": "北京小区"    }  }}
对于text的city进行含糊查问
COPYGET mapping_demo/_search{  "query": {    "term": {      "city": "北京市"    }  }}

数字类型

数字类型也是咱们最罕用的类型之一,上面咱们看下数字类型的应用
类型取值范畴
long-263 ~ 263
integer-231 ~ 231
short-215 ~ 215
byte-27 ~ 27
double64位的双精度 IEEE754 浮点类型
float32位的双精度 IEEE754 浮点类型
half_float16位的双精度 IEEE754 浮点类型
scaled_float缩放类型的浮点类型
注意事项
  • 在满足需要的状况下,优先应用范畴小的字段,字段长度越小,索引和搜寻的效率越高。

日期类型

JSON示意日期
JSON没有表白日期的数据类型,所以在ES外面日期只能是上面其中之一
  • 格式化的日期字符串,比方:"2015-01-01" or "2015/01/01 12:10:30"
  • 用数字示意的从新纪元开始的毫秒数
  • 用数字示意的从新纪元开始的秒数(epoch_second)
留神点:毫秒数的值是不能为正数的,如果工夫在1970年以前,须要应用格式化的日期表白
ES如何解决日期

在ES的外部,工夫会被转换为UTC工夫(如果申明了时区)并应用从新纪元开始的毫秒数的长整形数字类型的进行存储,在日期字段上的查问,外部将会转换为应用长整形的毫秒进行范畴查问,依据与字段关联的日期格局,聚合和存储字段的后果将转换回字符串

留神点:日期最终都会作为字符串出现,即便最开始初始化的时候是利用JSON文档的long申明的
默认日期格局
日期的格局能够被定制化的,如果没有申明日期的格局,它将会应用默认的格局:
COPY"strict_date_optional_time||epoch_millis"

这意味着它将会接管带工夫戳的日期,它将恪守strict_date_optional_time限定的格局(yyyy-MM-dd'T'HH:mm:ss.SSSZ 或者 yyyy-MM-dd)或者毫秒数

日期格局示例
COPYPUT mapping_demo/_mapping{  "properties": {    "datetime": {        "type": "date",        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"     }  }}# 增加数据PUT mapping_demo/_doc/2{  "name":"河北区",  "city":"河北省小区",  "datetime":"2022-02-21 11:35:42"}
日期类型参数
上面表格里的参数能够用在date字段下面
参数阐明
doc_values该字段是否依照列式存储在磁盘上以便于后续进行排序、聚合和脚本操作,可配置 true(默认)或 false
format日期的格局
locale解析日期中时应用了本地语言示意月份时的名称和/或缩写,默认是 ROOT locale
ignore_malformed如果设置为true,则奇怪的数字就会被疏忽,如果是false(默认)奇怪的数字就会导致异样并且该文档将会被回绝写入。须要留神的是,如果在脚本参数中应用则该属性不能被设置
index该字段是否能疾速的被查问,默认是true。date类型的字段只有在doc_values设置为true时能力被查问,只管很慢。
null_value代替null的值,默认是null
on_script_error定义在脚本中如何解决抛出的异样,fail(默认)则整个文档会被回绝索引,continue:持续索引
script如果该字段被设置,则字段的值将会应用该脚本产生,而不是间接从source外面读取。
storetrue or false(默认)是否在 _source 之外在独立存储一份

布尔类型

boolean类型用于存储文档中的true/false

范畴类型

顾名思义,范畴类型字段中存储的内容就是一段范畴,例如年龄30-55岁,日期在2020-12-28到2021-01-01之间等
类型范畴
es中有六种范畴类型:
  • integer_range
  • float_range
  • long_range
  • double_range
  • date_range
  • ip_range
应用实例
COPYPUT mapping_demo/_mapping{  "properties": {    "age_range": {        "type": "integer_range"     }  }}# 指定年龄范畴,能够应用 gt、gte、lt、lte。PUT mapping_demo/_doc/3{  "name":"张三",  "age_range":{    "gt":20,    "lt":30  }}

分词器

什么是分词器

分词器的次要作用将用户输出的一段文本,依照肯定逻辑,剖析成多个词语的一种工具

顾名思义,文本剖析就是把全文本转换成一系列单词(term/token)的过程,也叫分词,在 ES 中,Analysis 是通过分词器(Analyzer) 来实现的,可应用 ES 内置的分析器或者按需定制化分析器。

举一个分词简略的例子:比方你输出 Mastering Elasticsearch,会主动帮你分成两个单词,一个是 mastering,另一个是 elasticsearch,能够看出单词也被转化成了小写的。

分词器形成

分词器是专门解决分词的组件,分词器由以下三局部组成:
character filter
接管原字符流,通过增加、删除或者替换操作扭转原字符流

例如:去除文本中的html标签,或者将罗马数字转换成阿拉伯数字等,一个字符过滤器能够有零个或者多个

tokenizer
简略的说就是将一整段文本拆分成一个个的词

例如拆分英文,通过空格能将句子拆分成一个个的词,然而对于中文来说,无奈应用这种形式来实现,在一个分词器中,有且只有一个tokenizeer

token filters
将切分的单词增加、删除或者扭转

例如将所有英文单词小写,或者将英文中的停词a删除等,在token filters中,不容许将token(分出的词)position或者offset扭转,同时,在一个分词器中,能够有零个或者多个token filters

分词程序

同时 Analyzer 三个局部也是有程序的,从图中能够看出,从上到下顺次通过 Character FiltersTokenizer 以及 Token Filters,这个程序比拟好了解,一个文本进来必定要先对文本数据进行解决,再去分词,最初对分词的后果进行过滤。

测试分词

能够通过_analyzerAPI来测试分词的成果,咱们应用上面的html过滤分词
COPYPOST _analyze{    "text":"<b>hello world<b>"  # 输出的文本    "char_filter":["html_strip"], # 过滤html标签    "tokenizer":"keyword", #原样输入}

什么时候分词

文本分词会产生在两个中央:
  • 创立索引:当索引文档字符类型为text时,在建设索引时将会对该字段进行分词。
  • 搜寻:当对一个text类型的字段进行全文检索时,会对用户输出的文本进行分词。

创立索引时指定分词器

如果设置手动设置了分词器,ES将依照上面程序来确定应用哪个分词器
  • 先判断字段是否有设置分词器,如果有,则应用字段属性上的分词器设置
  • 如果设置了analysis.analyzer.default,则应用该设置的分词器
  • 如果下面两个都未设置,则应用默认的standard分词器
字段指定分词器
为addr属性指定分词器,这里咱们应用的是中文分词器
COPYPUT my_index{  "mappings": {    "properties": {     "info": {        "type": "text",        "analyzer": "ik_smart"       }    }  }}
设置默认分词器
COPYPUT my_index{  "settings": {    "analysis": {      "analyzer": {        "default":{          "type":"simple"        }      }    }  }}

搜寻时指定分词器

在搜寻时,通过上面参数顺次查看搜寻时应用的分词器,这样咱们的搜寻语句就会先分词,而后再来进行搜寻
  • 搜寻时指定analyzer参数
  • 创立mapping时指定字段的search_analyzer属性
  • 创立索引时指定settinganalysis.analyzer.default_search
  • 查看创立索引时字段指定的analyzer属性
  • 如果下面几种都未设置,则应用默认的standard分词器。
指定analyzer
搜寻时指定analyzer查问参数
COPYGET my_index/_search{  "query": {    "match": {      "message": {        "query": "Quick foxes",        "analyzer": "stop"      }    }  }}
指定字段analyzer
COPYPUT my_index{  "mappings": {    "properties": {      "title":{        "type":"text",        "analyzer": "whitespace",        "search_analyzer": "simple"      }    }  }}
指定默认default_seach
COPYPUT my_index{  "settings": {    "analysis": {      "analyzer": {        "default":{          "type":"simple"        },        "default_seach":{          "type":"whitespace"        }      }    }  }}

内置分词器

es在索引文档时,会通过各种类型 Analyzer 对text类型字段做剖析

不同的 Analyzer 会有不同的分词后果,内置的分词器有以下几种,基本上内置的 Analyzer 包含 Language Analyzers 在内,对中文的分词都不够敌对,中文分词须要装置其它 Analyzer

分析器形容分词对象后果
standard规范分析器是默认的分析器,如果没有指定,则应用该分析器。它提供了基于文法的标记化(基于 Unicode 文本宰割算法,如 Unicode 规范附件 # 29所规定) ,并且对大多数语言都无效。The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog’s, bone ]
simple简略分析器将文本合成为任何非字母字符的标记,如数字、空格、连字符和撇号、放弃非字母字符,并将大写字母更改为小写字母。The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.[ the, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]
whitespace空格分析器在遇到空白字符时将文本合成为术语The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.[ The, 2, QUICK, Brown-Foxes, jumped, over, the, lazy, dog’s, bone. ]
stop进行分析器与简略分析器雷同,但减少了删除进行字的反对。默认应用的是 _english_ 进行词。The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.[ quick, brown, foxes, jumped, over, lazy, dog, s, bone ]
keyword不分词,把整个字段当做一个整体返回The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.[The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.]
pattern模式分析器应用正则表达式将文本拆分为术语。正则表达式应该匹配令牌分隔符,而不是令牌自身。正则表达式默认为 w+ (或所有非单词字符)。The 2 QUICK Brown-Foxes jumped over the lazy dog’s bone.[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog, s, bone ]
多种西语系 arabic, armenian, basque, bengali, brazilian, bulgarian, catalan, cjk, czech, danish, dutch, english等等一组旨在剖析特定语言文本的分析程序。

IK中文分词器

IKAnalyzer

IKAnalyzer是一个开源的,基于java的语言开发的轻量级的中文分词工具包

从2006年12月推出1.0版开始,IKAnalyzer曾经推出了3个大版本,在 2012 版本中,IK 实现了简略的分词歧义排除算法,标记着 IK 分词器从单纯的词典分词向模仿语义分词衍化

中文分词器算法

中文分词器最简略的是ik分词器,还有jieba分词,哈工大分词器等
分词器形容分词对象后果
ik_smartik分词器中的简略分词器,反对自定义字典,近程字典学如逆水行舟,逆水行舟[学如逆水行舟,逆水行舟]
ik_max_wordik_分词器的全量分词器,反对自定义字典,近程字典学如逆水行舟,逆水行舟[学如逆水行舟,学如逆水,逆水行舟,顺水,行舟,逆水行舟,不进,则,退]

ik_smart

原始内容
COPY传智教育的教学质量是杠杠的
测试分词
COPYGET _analyze{  "analyzer": "ik_smart",  "text": "传智教育的教学质量是杠杠的"}

ik_max_word

原始内容
COPY传智教育的教学质量是杠杠的
测试分词
COPYGET _analyze{  "analyzer": "ik_max_word",  "text": "传智教育的教学质量是杠杠的"}

本文由传智教育博学谷狂野架构师教研团队公布。

如果本文对您有帮忙,欢送关注点赞;如果您有任何倡议也可留言评论私信,您的反对是我保持创作的能源。

转载请注明出处!