转载:
https://blog.csdn.net/guoyuguang0/article/details/76769184
https://www.jianshu.com/p/28fb017be7a7/
https://www.infoq.cn/article/UotLIglvj6TcUE2vxC5X
https://zhuanlan.zhihu.com/p/48429223?from_voters_page=true
https://zhuanlan.zhihu.com/p/91816296
ES索引存储原理
不变性
写到磁盘的倒序索引是不变的:自从写到磁盘就再也不变。
这会有很多好处:
- 不需要添加锁。不存在写操作,因此不存在多线程更改数据。
- 提高读性能。一旦索引被内核的文件系统做了Cache,绝大多数的读操作会直接从内存而不需要经过磁盘。
- 提升其他缓存(例如fiter cache)的性能。其他的缓存在该索引的生命周期内保持有效,减少磁盘I/O和计算消耗。
当然,索引的不变性也有缺点。如果你想让新修改过的文档可以被搜索到,你必须重新构建整个索引。这在一个index可以容纳的数据量和一个索引可以更新的频率上都是一个限制。
如何在不丢失不变形的好处下让倒序索引可以更改?答案是:使用不只一个的索引。 新添额外的索引来反映新的更改来替代重写所有倒序索引的方案。
Lucene引进了per-segment搜索的概念。一个segment是一个完整的倒序索引的子集,所以现在index在Lucene中的含义就是一个segments的集合,每个segment都包含一些提交点(commit point)。
segment工作流程
1.新的文档在内存中组织。
2.每隔一段时间,buffer将会被提交: 生成一个新的segment(一个额外的新的倒序索引)并被写到磁盘,同时一个
新的提交点(commit point)被写入磁盘,包含新的segment的名称。 磁盘fsync,所有在内核文件系统中的数据等待被写入到磁盘,来保障它们被物理写入。
3.新的segment被打开,使它包含的文档可以被索引。
4.内存中的buffer将被清理,准备接收新的文档。
当一个新的请求来时,会遍历所有的segments。词条分析程序会聚合所有的segments来保障每个文档和词条相关性的准确。通过这种方式,新的文档轻量的可以被添加到对应的索引中。
删除和更新
segments是不变的,所以文档不能从旧的segments中删除,也不能在旧的segments中更新来映射一个新的文档版本。取之的是,每一个提交点都会包含一个.del文件,列举了哪一个segmen的哪一个文档已经被删除了。 当一个文档被”删除”了,它仅仅是在.del文件里被标记了一下。被”删除”的文档依旧可以被索引到,但是它将会在最终结果返回时被移除掉。
文档的更新同理:当文档更新时,旧版本的文档将会被标记为删除,新版本的文档在新的segment中建立索引。也许新旧版本的文档都会本检索到,但是旧版本的文档会在最终结果返回时被移除。
实时索引
在上述的per-segment搜索的机制下,新的文档会在分钟级内被索引,但是还不够快。 瓶颈在磁盘。将新的segment提交到磁盘需要fsync来保障物理写入。但是fsync是很耗时的。它不能在每次文档更新时就被调用,否则性能会很低。 现在需要一种轻便的方式能使新的文档可以被索引,这就意味着不能使用fsync来保障。 在ES和物理磁盘之间是内核的文件系统缓存。之前的描述中,在内存中索引的文档会被写入到一个新的segment。但是现在我们将segment首先写入到内核的文件系统缓存,这个过程很轻量,然后再flush到磁盘,这个过程很耗时。但是一旦一个segment文件在内核的缓存中,它可以被打开被读取。
更新持久化
不使用fsync将数据flush到磁盘,我们不能保障在断电后或者进程死掉后数据不丢失。ES是可靠的,它可以保障数据被持久化到磁盘。一个完全的提交会将segments写入到磁盘,并且写一个提交点,列出所有已知的segments。当ES启动或者重新打开一个index时,它会利用这个提交点来决定哪些segments属于当前的shard。 如果在提交点时,文档被修改会怎么样?
translog日志提供了一个所有还未被flush到磁盘的操作的持久化记录。当ES启动的时候,它会使用最新的commit point从磁盘恢复所有已有的segments,然后将重现所有在translog里面的操作来添加更新,这些更新发生在最新的一次commit的记录之后还未被fsync。
translog日志也可以用来提供实时的CRUD。当你试图通过文档ID来读取、更新、删除一个文档时,它会首先检查translog日志看看有没有最新的更新,然后再从响应的segment中获得文档。这意味着它每次都会对最新版本的文档做操作,并且是实时的。
Segment合并
通过每隔一秒的自动刷新机制会创建一个新的segment,用不了多久就会有很多的segment。segment会消耗系统的文件句柄,内存,CPU时钟。最重要的是,每一次请求都会依次检查所有的segment。segment越多,检索就会越慢。
ES通过在后台merge这些segment的方式解决这个问题。小的segment merge到大的,大的merge到更大的。。。
这个过程也是那些被“删除”的文档真正被清除出文件系统的过程,因为被标记为删除的文档不会被拷贝到大的segment中。
Segment
- Inverted Index
Inverted Index主要包括两部分:
一个有序的数据字典Dictionary。
与单词Term对应的Postings(即存在这个单词的文件)。
- Document Values
即使这样,我们发现以上结构仍然无法解决诸如:排序、聚合、facet,因为我们可能会要读取大量不需要的信息。
所以,另一种数据结构解决了此种问题:Document Values。这种结构本质上就是一个列式的存储,它高度优化了具有相同类型的数据的存储结构。
为了提高效率,ElasticSearch可以将索引下某一个Document Value全部读取到内存中进行操作,这大大提升访问速度,但是也同时会消耗掉大量的内存空间。
- Stored Fields
当我们想要查找包含某个特定标题内容的文件时,Inverted Index就不能很好的解决这个问题,所以Lucene提供了另外一种数据结构Stored Fields来解决这个问题。本质上,StoredFields是一个简单的键值对key-value。默认情况下,ElasticSearch会存储整个文件的JSON source。
总之,这些数据结构Inverted Index
、Stored Fields
、Document Values
及其缓存,都在segment内部。
- Cache
当一个真实请求来的时候,如下所示:
存储索引的流程
从数据到索引,数据的流向如下:
总结
索引过程:
-
有一系列被索引文件
-
被索引文件经过语法分析和语言处理形成一系列词(Term) 。
-
经过索引创建形成词典和反向索引表。
-
通过索引存储将索引写入硬盘。
搜索过程:
-
用户输入查询语句。
-
对查询语句经过语法分析和语言分析得到一系列词(Term) 。
-
通过语法分析得到一个查询树。
-
通过索引存储将索引读入到内存。
-
利用查询树搜索索引,从而得到每个词(Term) 的文档链表,对文档链表进行交,差,并得到结果文档。
-
将搜索到的结果文档对查询的相关性进行排序。
-
返回查询结果给用户。