type
status
date
slug
summary
tags
category
icon
password
1、索引介绍
1.1 索引结构
Elasticsearch 索引是是具有类似特性的文档的集合,一个索引可以视作关系数据库中的一张表。索引结构主要包含
mappings
与 settings
两部分。- settings:定义 index 分片数量、副本数量、解析器等相关的配置信息。
- mappings:定义 document 及其包含的字段类型和索引方式相关的信息。
Elasticearch 7.0 以后版本新建一个索引的语法如下:
- 定义 3 个主分片,主分片在设定后不能修改。
- 定义每个主分片有 1 个分片副本,分片副本可以在运行过程中根据业务需要动态调整。
- 定义了 name、age、address 三个字段,其中 name 和 address 的字段类型为 text,age 的字段类型为 integer。
1.2 索引分片介绍
分片(shard)是 Elasticsearch 中存储数据的最小单元。一个索引被分成多个分片,分散到多台服务器上,每个分片存储一部分数据。分片分为
shard
和 replica
。- shard:只有一个,为了解决数据在单一节点存储的问题而做的水平扩展,将数据分片后存放在不同的节点上可以提高对于数据操作的吞吐量及可扩展性。此参数一旦在索引(Index)创建完成后,则不可以进行修改。
- replica:是对 shard 的备份,为了解决数据可用性问题,通常在分片数据丢失后,如果有副本数据在的话可以保证数据的完整性。并且在高吞吐量场景时,增加副本数量可以提供服务的读取吞吐量。此参数可以在索引创建时指定,也可以在创建后进行调整。replica 值越大搜索效率越高,但写入性能越低(一条数据写入操作需要做 1 + replicas 遍),具体值与集群 data 节点数量相关,不宜超过(data 节点数 - 1)
比如有一个索引,shard 设置为 5,replica 设置为1,那么总的切片数为:shard(5) + shard(5) * replicas(1) = total(10)。
每次修改的时候都会先直接修改主分片 shard,再同步其他所有副本分片 replica。查询的时候则在 shard 和 replica 中随机选择一个。
1.3 索引分片的分配机制
当我们定义好主分片和副本分片的数量之后,集群的主节点将索引的各个分片分配到集群的各个节点上,这个过程就叫分片的分配。可以使用下面的请求查看索引 my-index 的各个分片的分配状态。
返回结果如下:
在返回的结果中,可以看到主分片被成功分配在节点 node-2 上,副本分片被分配在节点 node-1 上。
索引分片分配的几个原则:
- 当集群的节点数目发生变化、索引的副本分片数目发生变化时,也会触发分片的分配。在这个过程中,需要把节点现有的分片移动到其他的节点上。如果在同一时刻需要移动的分片数太多且分片容量较大,则这个过程会比较耗时。
- 主节点为每个节点分配索引分片的时候,默认情况下,它会尽可能把同一个索引的分片分配到更多的节点上,这样在读写索引数据的时候就可以利用更多的硬件资源,可提升读写的效率。
- 在分配分片的过程中,永远不可以把同一个索引的某个主分片和它的副本分片分配到同一个节点上,也不可以把某个主分片的多个副本分片分配到同一个节点上。否则,一旦该节点“挂掉”整个集群就可能会丢失数据。因此,如果一个索引的副本分片数较多而集群节点数较少,则可能会导致某些副本分片没有得到分配而不起作用。
- 对于一个总分片数为 N 的索引而言,当集群节点的数目达到 N 以后,由于没有多余的分片可以分配到新的节点上,继续添加新节点无法再提升该索引的读写性能。
1.4 索引分片的路由规则
当主分片分配完毕之后,我们写入一条数据的时候,这条数据会被路由到其中一个主分片进行保存。那么如何决定数据应该被保存到哪个分片上的呢?默认情况下,Elasticsearch 会使用以下公式来决定数据被写入的分片的编号。
routing 默认是文档的
_id
。routing 通过 hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数 。这个分布在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置。如果允许主分片数量发生改变,就意味着所有的数据需要重新路由,因此 Elassticsearch 禁止在索引建立以后修改主分片的数量。
虽然手动指定路由值可以减少查询使用的分片数,但是这有可能引发大量的数据被路由到少数几个分片,而其余的很多分片数据量太少,使得分片的大小不均匀。Elasticsearch 为了缓解这个问题,提供了索引分区的配置,允许使用同一路由的数据被分发到多个分片而不是一个分片,该配置需要在索引的
index.routing_partition_size
中进行设置。当你给索引配置
index.routing_partition_size
以后,数据分片编号的计算公式就变成了以下形式。这时候,分片编号的计算结果改为由路由值和主键共同决定。对于同一个_routing,hash(_id)%routing_partition_size的可能结果有routing_partition_size种,routing_partition_size值越大,数据在分片上的分发就越均匀,代价是搜索时需要查找更多的分片。这样一来,同一种路由的数据会被分发到routing_partition_size种不同的分片上,从而缓解了同一路由的数据全部分发到某一个分片引起数据存储过于集中的问题。但是routing_partition_size值一旦大于1,由于 join 字段要求同一路由值的文档必须写入同一个分片,支导致join字段在索引中不可使用。
我们也可以自定义 routing 字段来完成数据分发。首先新建一个索引如下。
这里创建了一个带有 3 个主分片的索引,使用到了元字段 _routing,规定了对该索引数据进行增删改查时必须提供路由值。
新增一条索引数据,需要带上路由值 routing,否则会报错。
查询索引数据,也要带上路由值 routing。
带上 routing 值之后,只到该路由值对应的分片上去搜索数据;如果不提供路由值时,会检索全部主分片。可见自定义路由能够减少查询的分片数量,从而加快查询速度。在实际项目中巧妙地使用这个功能,对提升查询性能是大有好处的。
1.5 重新索引机制
由上面可知主分片的数量在索引创建完之后不能修改。但在生产环境中,某些情况我们必须修改主分片 shard 的数量,例如数据量过大,而主分片数量不足,导致单分片的承载压力过大,这时候我们必须增加主分片的数量。
为了解决主分片 shard 数量不能被直接修改这个问题,ElasticSearch 中设置了重新索引机制。重新索引简单来说就是创建一个和原索引库结构属性都基本一样的新的索引库,然后将原索引库中的数据复制到新的索引库当中。在新的索引库中,除了需要变更的地方,比如某些字段的数据类型和分片数,其他的所有属性都一样。
比如我们新建一个索引,和 my-index 的其他属性一致,只有主分片 shard 数量从 1 变成 2,如下所示
执行下面命令,会把索引 my-index 的内容复制到索引 my-index-new 中。
2、索引别名
想象这样一种场景,假如有一个索引只有一个主分片,但是时间久了以后数据量越来越大,你决定为索引扩容,可是又不愿意重建索引。一个解决问题的办法就是,你可以创建一个新的索引保存新的数据,然后取一个别名同时指向这两个索引,这样在检索时使用别名就可以同时检索到两个索引的数据。
2.1 创建索引别名
先来新建两个索引logs-1和logs-2并添加数据。
添加一个索引别名logs指向这两个索引。
这个别名 logs 可以当作普通的索引使用,例如:
查看刚才创建的别名 logs 包含哪些索引,可以用下面的代码。
如果要删除索引logs-1的别名,可以使用以下代码。
你也可以使用通配符匹配一组索引,给所有以logs为前缀的索引添加别名logs,可以用下面的代码。
2.2 别名配合数据过滤
配合使用别名和数据过滤可以达到类似于数据库视图的效果,可以把查询条件放入别名,这样在搜索别名时会自动带有查询条件,能起到数据自动过滤的作用。例如:
上面的请求在别名 logs 中配置了一个range过滤器,它表示只查询 clicknum 大于等于10的数据。下面来进行简单的别名查询以查看效果。
以下查询结果中只有一条数据,这说明别名配置的过滤器起作用了。
2.3 滚动索引
当有一个索引数据量太大时,如果继续写入数据可能会导致分片容量过大,查询时会因内存不足引起集群崩溃。为了避免所有的数据都写入同一个索引,可以考虑使用滚动索引。滚动索引需要配合索引别名一起使用,可实现把原先写入一个索引的数据自动分发到多个索引中。
先创建一个索引 log1,它有一个别名 logs-all。
现在来为别名 logs-all 指定一个滚动索引,如果条件成立,就把新数据写入 log2。
上面的滚动索引配置的条件是,如果往别名 logs-all 中写入的索引数据量大于等于1,或者主分片总大小超过 5GB,或者创建索引的时间长度超过 7 天,就把新的数据写入新索引 log2。该请求会返回滚动索引的执行结果,结果如下。
从请求返回的结果可以看出,此时max_docs条件已成立,一个新的索引log2已经创建出来了,此时别名logs-all已经指向了log2,log1的别名已经被删除。因此,如果继续往别名logs-all中写数据,数据会被写入log2。
3、索引原理
3.1 什么是倒排索引
Elasticsearch 的倒排索引,本质就是 Lucene 的倒排索引。Elasticsearch 索引和 Lucene 索引的关系如下:
- 一个 Elasticsearch Index 都是由多个 Shard (primary & replica)构成的。
- 一个 Shard 本质对应一个 Lucene Index。
- 一个 Lucene Index 包含多个 Segment。
- 每一个 Segment 都是一个倒排索引。
倒排索引就是通过 Value 查找 Key:一段文本经过分析器分析以后就会输出一串单词,这一个一个的就叫做 Term。文档中所有不重复 Term 组成一个列表,对于其中每个 Term,有一个包含它的文档列表。Lucene 倒排索引中包含的概念如下:
索引(Index)
:类似于数据库的表。在 Lucene 中一个索引是放在一个文件夹中的。所以可以理解索引为整个文件夹的内容。
段(Segment)
:类似于表的分区。一个索引下可以多个 Segment。
文档(Document)
:类似于表的一行数据。Document 是索引的基本单位。一个段可以有多个 Document。
域(Field)
:类似于表的字段。Doument 里可以有多个 Field。Lucene 提供多种不同类型的 Field,例如 StringField、TextField、LongFiled 或NumericDocValuesField等。
词(Term)
:Term 是索引的最小单位。Term 是由 Field 经过Analyzer(分词)产生。
Lucene 倒排索引的内部结构如下:
Term Index
:字典树,用于快速查找Term。使用FST结构,只存储Term前缀,通过字典树找到Term所在的块,也就是Term的在字典表中的大概位置,再在块里二分查找,找到对应的Term。
Term Dictionary
:字典表,用于存储Analyzer分割 后所有Term。使用跳表结构。
Posting List
:记录表,记录了出现过某个单词的所有文档的文档列表及单词在该文档中出现的位置信息,每条记录称为一个倒排项(Posting)。使用Frame Of Reference(FOR)和Roaring bitmaps技术。它不仅仅为文档 id 信息,可能包含以下信息:- 文档 id(DocId, Document Id),包含单词的所有文档唯一 id,用于去正排索引中查询原始数据。
- 词频(TF,Term Frequency),记录 Term 在每篇文档中出现的次数,用于后续相关性算分。
- 位置(Position),记录 Term 在每篇文档中的分词位置(多个),用于做词语搜索(Phrase Query)。
- 偏移(Offset),记录 Term 在每篇文档的开始和结束位置,用于高亮显示等。
3.2 构建倒排索引的过程
构建倒排索引的过程:Elasticsearch 会使用多种分析器将文本拆分成用于搜索的词条(Term),然后将这些词条统一化以提高“可搜索性”,整个分析算法称为分析器(Analyzer)。
Analyzer 包含三部分内容:
字符过滤器(Character Filter)
:在分词前进行预处理,如去除HTML,字符转换如&转为and。
分词器(Tokenizer)
:按照规则切割文档并提取词元(Token),例如遇到空格和标点将文本拆分成词元。
Token过滤器(Token Filter
):将切分的词元进行加工,如将大小写统一,增加词条(如jump、leap等同义词)。
- Author:mcbilla
- URL:http://mcbilla.com/article/5d97e20c-be45-426c-b1c4-2dcbea284b52
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts