乐趣区

关于java:6000字讲透ElasticSearch-索引设计

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 或者 false boolean 类型
浮点数据 float 类型
integer 数据 long 类型
object object 类型
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
double 64 位的双精度 IEEE754 浮点类型
float 32 位的双精度 IEEE754 浮点类型
half_float 16 位的双精度 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 外面读取。
store true 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_smart ik 分词器中的简略分词器,反对自定义字典,近程字典 学如逆水行舟,逆水行舟 [学如逆水行舟, 逆水行舟]
ik_max_word ik_分词器的全量分词器,反对自定义字典,近程字典 学如逆水行舟,逆水行舟 [学如逆水行舟, 学如逆水, 逆水行舟, 顺水, 行舟, 逆水行舟, 不进, 则, 退]

ik_smart

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

ik_max_word

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

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

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

转载请注明出处!

退出移动版