Elasticsearch倒排索引深入

1,493 阅读8分钟

Elasticsearch存储一条数据的背后原理

你知道当我们向Elasticsearch索引一条数据的时候,Elasticsearch都做了什么吗?

你可能会说:

  1. 判断你要索引数据的index是否存在,如果你没有则创建
  2. 判断指定type是否存在,没有则创建
  3. 判断你要索引的数据的字段是否设置了mapping,没有则智能识别并创建
  4. 计算数据要存入哪个分片
  5. 将数据存入,并创建倒排索引

那你知道:

  1. 是否可以禁止index字段创建吗?index创建时会创建几个主分片几个副本分片?
  2. 一个index可以有几个type?
  3. index、type、mappings是什么关系? mapping中text和keyword有什么区别?
  4. 数据要存储的分片是如何计算的?是否可以指定?分片数可以动态修改吗?
  5. 倒排索引是如何创建的、有几个、可以修改吗?Doc Values 是什么?Elasticsearch和Lucene的关系是什么?一个Elasticsearch实例中有几个Lucene实例?

不知道的自己去查🤪哈哈,或者之后有时间我再写。本文将主要介绍下倒排索引相关的原理。

本文主要是我看官方文档的一些总结,有不准确的地方还望多多指点。

倒排索引

  • Elasticsearch通过一种称为倒排索引的结构,来实现快速的全文检索。
  • 一个倒排索引由文档中所有不重复的词列表组成,列表中每个词都有一个包含该词的文档列表。

分析器

如果我们希望一篇文档能够通过关键字被搜索到,首先肯定需要一个工具,能够分析出文档中哪些是可能被搜索的关键字。这个工具就是分析器。

比如,‘床前明月光’这句文本:

  1. 分析器可能就会分析出关键字为,‘床’、‘床前’、‘明月’、‘月光’、‘明月光’。
  2. Elasticsearch就会将这些关键字添加到倒排索引中。
  3. 当我们搜索‘明月’的时候就能从倒排索引中找到,然后获取到其对应的文本‘床前明月光’。
  4. 而如果我们搜索‘前明’,因为倒排索引中没有该词,自然就找不到文本‘床前明月光’。
  5. 所以分析器的作用非常重要,它能决定你的搜索质量。

分析器实际上包含三个部分:字符过滤器、分词器和词元过滤器。

字符过滤器

可以过滤掉字符串中的特殊字符等,如html标签。

一个分析器中可以有一个或多个字符过滤器。

分词器

可以将字符串分成词条,如将‘床前明月光’分成‘床前’‘明月’‘月光’。

词元过滤器

将拆分后的词条进行同义化处理等,如大写转小写、去除停用词、增加同义词。 一个分析器中可以有一个或多个词元过滤器。

Elasticsearch 提供了一些内置的字符过滤器、分词器和词元过滤器,也提供了组合好的分析器。 网上也有其他人实现的比较好的。也可以根据需求自己实现。

Doc Values

  • 当检索数据的时候通过倒排索引是非常快的,但如果我们要根据某个字段进行排序时,倒排索引就不能不能满足需求了。
  • Elasticsearch通过一种称为Doc Values的列式存储结构,来实现快速排序。
  • 在索引字段时,Elasticsearch会把字段值加入到倒排索引中,同时也会加入到该字段的Doc Values中。

Doc Values 一般用于字段排序、字段聚合和字段过滤等。

深入倒排索引

Elasticsearch 和 Lucene 关系

Elasticsearch是基于Lucene开发的。

Elasticsearch的每个分片中都有一个Lucene实例。

倒排索引中的一些概念(提交点、段等)都是从Lucene引入的。

倒排索引的创建

Elasticsearch中文档是有字段和值的结构化JSON文档。

在该JSON文档中每个被索引的字段都会有自己的倒排索引。

倒排索引是一个包含该字段所有词的列表,而每个词又会包含以下内容(进行相关性得分计算时会用到):

  • 包含该词的文档总数
  • 包含该词的文档的列表
  • 每个文档中该词出现的次数
  • 该词在文档中的顺序
  • 每个文档的长度等

倒排索引的不变性

倒排索引是不可改变的。

如果有新的词要加入倒排索引,只能创建一个新的包含旧索引内容和要新加入内容的倒排索引,然后替换旧的。

倒排索引不变性的好处:

  • 不需要锁,不用担心并发冲突。
  • 会在内核的文件系统缓存,只要文件系统缓存中还有足够的空间,就不会命中磁盘,大幅提升性能。
  • 可以对filter等操作进行缓存
  • 可以对大的倒排索引进行数据压缩,减少磁盘I/O

不可变性带来的的问题:

  • 如果一个倒排索引很大,向其中添加词的时候需要重建整个倒排索引,会非常慢。添加频率很快的话,可能上一次添加词创建的的倒排索引还没创建完,新的又来了。

提交点和段

如何解决倒排索引不变性带来的问题。答案就是创建多个倒排索引。

通过把新近的修改放入一个新的倒排索引,而不是直接重写整个倒排索引,来解决重整个索引带来的性能问题。

当查询的时候,会从最早的开始轮流查询每一个倒排索引,查询完成后再对结果进行合并。

段:

  • 每一个段就是一个倒排索引。
  • 新的文档首先被写入内存索引缓存中,然后写入一个基于磁盘的段。

提交点:

  • 一个列出所有已知段和信息的文件

删除倒排索引上的某个词

因为倒排索引是不可改变的,所以不能把文档从倒排索引中删除,而如果要是重建一个倒排索引进行替换的话又影响性能。

其解决办法是:

  • 为每个提交点创建一个.del文件,文件中会列出每个倒排索引中被删除的文档的信息。
  • 之后会在某个时间点统一清理被标记为删除的文档

所以实际上我门进行搜索的时候,仍然可能匹配到被删除的文档,但他会被在最终结果返回前从结果集中删除。

文档更新

因为倒排索引是不可变的,所以文档实际上也是不可被修改的,只会被替换。

当修改文档中的每个字段的内容时,实际上是创建了一个旧文档的副本,对副本进行修改,然后将旧文档标记为删除状态,并把新的文档添加进去。

refresh 操作

创建一个新的倒排索引并使其可以被搜索到,需要一个过程,即:创建索引-写入磁盘。

因为写入磁盘操作必然会影响性能,所以Elasticsearch设计了一种机制,即:创建索引-写入文件系统缓存-写入磁盘。 这样当索引写入缓存后即可被搜索到。

这个把索引写入缓存的过程就叫做refresh,默认每个分片会1秒自动refresh一次。

索引Elasticsearch是近实时刷新,文档的变换并不少立即对搜索可见,但会在一秒之内变为可见

Elasticsearch 提供了 refresh API 调用可立即执行一次 refresh

translog 事务日志

如果索引已经写入到了缓存,但缓存还没写入磁盘,这个时候出现故障,则数据可能会丢失。

Elasticsearch为了避免数据丢失,增加了一个 translog

translog提供了所有还没有被写入磁盘的操作的一个持久化记录。

当将一个索引添加到内存缓存中的同时,也会追加到 translog 中。

当Elasticsearch启动的时候,会从translog中恢复没来得及写入磁盘的变更操作。

flush 操作

将translog中的操作刷新到磁盘,并清空translog,这个过程叫做 flush。

当translog达到一定大小,或者每隔一段时间(默认30秒),会自动执行一次 flush。

Elasticsearch 提供了 flush API 调用可立即执行一次 flush。

索引写入磁盘过程总结

  1. 创建索引
  2. 每个一段时间(默认1秒),或调用refresh API
  3. 提交到内存,同时写入translog,(此时已可以被搜索到)
  4. 当translog累计到一定大小,或每隔一段时间(默认30分钟),或调用flush API
  5. 将translog中的内容写入磁盘,同时清空translog