ElasticSearch 的运行原理(图解)

Java大联盟

共 4164字,需浏览 9分钟

 ·

2020-12-13 07:46


  Java大联盟

  帮助万千Java学习者持续成长

关注



来源|Richaaaard


B 站搜索:楠哥教你学Java

获取更多优质视频教程


Elasticsearch 是一款功能强大的开源分布式搜索与数据分析引擎,目前国内诸多互联网大厂都在使用,包括携程、滴滴、今日头条、饿了么、360 安全、小米、vivo 等。

除了搜索之外,结合 Kibana、Logstash、Beats,Elastic Stack 还被广泛运用在大数据近实时分析领域,包括日志分析、指标监控、信息安全等多个领域。

它可以帮助你探索海量结构化、非结构化数据,按需创建可视化报表,对监控数据设置报警阈值,甚至通过使用机器学习技术,自动识别异常状况。

今天,我们先自上而下,后自底向上的介绍ElasticSearch的底层工作原理,并试图回答以下问题:

  • 为什么我的搜索 *foo-bar* 无法匹配 foo-bar ?

  • 为什么增加更多的文件会压缩索引(Index)?

  • 为什么 ElasticSearch 占用很多内存?


elasticsearch 版本: elasticsearch-2.2.0。

①云上的集群,如下图:

②集群里的盒子,云里面的每个白色正方形的盒子代表一个节点——Node。

③节点之间,在一个或者多个节点直接,多个绿色小方块组合在一起形成一个 ElasticSearch 的索引。

④索引里的小方块,在一个索引下,分布在多个节点里的绿色小方块称为分片——Shard。

⑤Shard=Lucene Index,一个 ElasticSearch 的 Shard 本质上是一个 Lucene Index。

Lucene 是一个 Full Text 搜索库(也有很多其他形式的搜索库),ElasticSearch 是建立在 Lucene 之上的。

接下来的故事要说的大部分内容实际上是 ElasticSearch 如何基于 Lucene 工作的。Mini 索引:Segment
在 Lucene 里面有很多小的 Segment,我们可以把它们看成 Lucene 内部的 mini-index。
Segment 内部

Segment 内部有着许多数据结构,如上图:

  • Inverted Index

  • Stored Fields

  • Document Values

  • Cache

最最重要的 Inverted Index如下图:

Inverted Index 主要包括两部分:

  • 一个有序的数据字典 Dictionary(包括单词 Term 和它出现的频率)。

  • 与单词 Term 对应的 Postings(即存在这个单词的文件)。

当我们搜索的时候,首先将搜索的内容分解,然后在字典里找到对应 Term,从而查找到与搜索相关的文件内容。
①查询“the fury”,如下图:
②自动补全(AutoCompletion-Prefix)
如果想要查找以字母“c”开头的字母,可以简单的通过二分查找(Binary Search)在 Inverted Index 表中找到例如“choice”、“coming”这样的词(Term)。
③昂贵的查找
如果想要查找所有包含“our”字母的单词,那么系统会扫描整个 Inverted Index,这是非常昂贵的。

在此种情况下,如果想要做优化,那么我们面对的问题是如何生成合适的 Term。

④问题的转化,如下图:

对于以上诸如此类的问题,我们可能会有几种可行的解决方案:

  • * suffix→xiffus *,如果我们想以后缀作为搜索条件,可以为 Term 做反向处理。

  • (60.6384, 6.5017)→ u4u8gyykk,对于 GEO 位置信息,可以将它转换为 GEO Hash。

  • 123→{1-hundreds, 12-tens, 123},对于简单的数字,可以为它生成多重形式的 Term。


⑤解决拼写错误
一个 Python 库为单词生成了一个包含错误拼写信息的树形状态机,解决拼写错误的问题。

⑥Stored Field 字段查找

当我们想要查找包含某个特定标题内容的文件时,Inverted Index 就不能很好的解决这个问题,所以 Lucene 提供了另外一种数据结构 Stored Fields 来解决这个问题。

本质上,Stored Fields 是一个简单的键值对 key-value。默认情况下,ElasticSearch 会存储整个文件的 JSON source。

⑦Document Values 为了排序,聚合

即使这样,我们发现以上结构仍然无法解决诸如:排序、聚合、facet,因为我们可能会要读取大量不需要的信息。

所以,另一种数据结构解决了此种问题:Document Values。这种结构本质上就是一个列式的存储,它高度优化了具有相同类型的数据的存储结构。

为了提高效率,ElasticSearch 可以将索引下某一个 Document Value 全部读取到内存中进行操作,这大大提升访问速度,但是也同时会消耗掉大量的内存空间。

总之,这些数据结构 Inverted Index、Stored Fields、Document Values 及其缓存,都在 segment 内部。

搜索时,Lucene 会搜索所有的 Segment 然后将每个 Segment 的搜索结果返回,最后合并呈现给客户。

Lucene 的一些特性使得这个过程非常重要:

  • Segments 是不可变的(immutable):Delete?当删除发生时,Lucene 做的只是将其标志位置为删除,但是文件还是会在它原来的地方,不会发生改变。

    Update?所以对于更新来说,本质上它做的工作是:先删除,然后重新索引(Re-index)。

  • 随处可见的压缩:Lucene 非常擅长压缩数据,基本上所有教科书上的压缩方式,都能在 Lucene 中找到。

  • 缓存所有的所有:Lucene 也会将所有的信息做缓存,这大大提高了它的查询效率。

当 ElasticSearch 索引一个文件的时候,会为文件建立相应的缓存,并且会定期(每秒)刷新这些数据,然后这些文件就可以被搜索到。

随着时间的增加,我们会有很多 Segments,如下图:

所以 ElasticSearch 会将这些 Segment 合并,在这个过程中,Segment 会最终被删除掉。

这就是为什么增加文件可能会使索引所占空间变小,它会引起 Merge,从而可能会有更多的压缩。

有两个 Segment 将会 Merge:

这两个 Segment 最终会被删除,然后合并成一个新的 Segment,如下图:

这时这个新的 Segment 在缓存中处于 Cold 状态,但是大多数 Segment 仍然保持不变,处于 Warm 状态。

以上场景经常在 Lucene Index 内部发生的,如下图:

ElasticSearch 从 Shard 中搜索的过程与 Lucene Segment 中搜索的过程类似。

与在 Lucene Segment 中搜索不同的是,Shard 可能是分布在不同 Node 上的,所以在搜索与返回结果时,所有的信息都会通过网络传输。

需要注意的是:1 次搜索查找 2 个 Shard=2 次分别搜索 Shard。

对于日志文件的处理:当我们想搜索特定日期产生的日志时,通过根据时间戳对日志文件进行分块与索引,会极大提高搜索效率。

当我们想要删除旧的数据时也非常方便,只需删除老的索引即可。

在上种情况下,每个 Index 有两个 Shards。
如何 Scale,如下图:

Shard 不会进行更进一步的拆分,但是 Shard 可能会被转移到不同节点上。

所以,如果当集群节点压力增长到一定的程度,我们可能会考虑增加新的节点,这就会要求我们对所有数据进行重新索引,这是我们不太希望看到的。
所以我们需要在规划的时候就考虑清楚,如何去平衡足够多的节点与不足节点之间的关系。

节点分配与 Shard 优化:

  • 为更重要的数据索引节点,分配性能更好的机器。

  • 确保每个 Shard 都有副本信息 Replica。

路由 Routing:每个节点,每个都存留一份路由表,所以当请求到任何一个节点时,ElasticSearch 都有能力将请求转发到期望节点的 Shard 进一步处理。

一个真实的请求,如下图:

①Query,如下图:

Query 有一个类型 filtered,以及一个 multi_match 的查询。

②Aggregation,如下图:

根据作者进行聚合,得到 top10 的 hits 的 top10 作者的信息。

③请求分发,这个请求可能被分发到集群里的任意一个节点,如下图:

④上帝节点,如下图:


这时这个节点就成为当前请求的协调者(Coordinator),它决定:
  • 根据索引信息,判断请求会被路由到哪个核心节点。

  • 以及哪个副本是可用的。

  • 等等。

⑤路由,如下图:

⑥在真实搜索之前,ElasticSearch 会将 Query 转换成 Lucene Query,如下图:

然后在所有的 Segment 中执行计算,如下图:

对于 Filter 条件本身也会有缓存,如下图:

但 Queries 不会被缓存,所以如果相同的 Query 重复执行,应用程序自己需要做缓存。

所以:
  • Filters 可以在任何时候使用。

  • Query 只有在需要 Score 的时候才使用。


⑦返回,搜索结束之后,结果会沿着下行的路径向上逐层返回,如下图:


推荐阅读

1、Spring Boot+Vue项目实战

2、B站:4小时上手MyBatis Plus

3、一文搞懂前后端分离

4、快速上手Spring Boot+Vue前后端分离


楠哥简介

资深 Java 工程师,微信号 southwindss

《Java零基础实战》一书作者

腾讯课程官方 Java 面试官今日头条认证大V

GitChat认证作者,B站认证UP主(楠哥教你学Java)

致力于帮助万千 Java 学习者持续成长。




有收获,就在看 
浏览 13
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报