Elasticsearch存储一条数据的背后原理
你知道当我们向Elasticsearch索引一条数据的时候,Elasticsearch都做了什么吗?
你可能会说:
- 判断你要索引数据的index是否存在,如果你没有则创建
- 判断指定type是否存在,没有则创建
- 判断你要索引的数据的字段是否设置了mapping,没有则智能识别并创建
- 计算数据要存入哪个分片
- 将数据存入,并创建倒排索引
那你知道:
- 是否可以禁止index字段创建吗?index创建时会创建几个主分片几个副本分片?
- 一个index可以有几个type?
- index、type、mappings是什么关系? mapping中text和keyword有什么区别?
- 数据要存储的分片是如何计算的?是否可以指定?分片数可以动态修改吗?
- 倒排索引是如何创建的、有几个、可以修改吗?Doc Values 是什么?Elasticsearch和Lucene的关系是什么?一个Elasticsearch实例中有几个Lucene实例?
不知道的自己去查🤪哈哈,或者之后有时间我再写。本文将主要介绍下倒排索引相关的原理。
本文主要是我看官方文档的一些总结,有不准确的地方还望多多指点。
倒排索引
- Elasticsearch通过一种称为倒排索引的结构,来实现快速的全文检索。
- 一个倒排索引由文档中所有不重复的词列表组成,列表中每个词都有一个包含该词的文档列表。
分析器
如果我们希望一篇文档能够通过关键字被搜索到,首先肯定需要一个工具,能够分析出文档中哪些是可能被搜索的关键字。这个工具就是分析器。
比如,‘床前明月光’这句文本:
- 分析器可能就会分析出关键字为,‘床’、‘床前’、‘明月’、‘月光’、‘明月光’。
- Elasticsearch就会将这些关键字添加到倒排索引中。
- 当我们搜索‘明月’的时候就能从倒排索引中找到,然后获取到其对应的文本‘床前明月光’。
- 而如果我们搜索‘前明’,因为倒排索引中没有该词,自然就找不到文本‘床前明月光’。
- 所以分析器的作用非常重要,它能决定你的搜索质量。
分析器实际上包含三个部分:字符过滤器、分词器和词元过滤器。
字符过滤器
可以过滤掉字符串中的特殊字符等,如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秒),或调用refresh API
- 提交到内存,同时写入translog,(此时已可以被搜索到)
- 当translog累计到一定大小,或每隔一段时间(默认30分钟),或调用flush API
- 将translog中的内容写入磁盘,同时清空translog