MyBlog


ELK跳不过的ES,图解助你掌握内部模型及数据结构

<p>[TOC]</p> <p>本文我们将会先自上而下,后自底向上地介绍ElasticSearch的底层工作原理,试图回答以下问题:</p> <ul> <li>为什么我的搜索<em>foo-bar</em>无法匹配<em>foo-bar</em>?</li> <li>为什么增加更多的文件会压缩索引(Index)?</li> <li>为什么ElasticSearch占用很多内存?</li> </ul> <h1>1 图解ElasticSearch</h1> <p>集群 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/cfc7a1e14bf7c07d1bced69ae3bed783?showdoc=.jpg" alt="" /> 云里面的每个白色正方形的盒子代表一个节点——Node。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/c09ae53fcc99c9f7c8b7f93084ac1558?showdoc=.jpg" alt="" /> 节点之间</p> <p>在一个或者多个节点直接,多个绿色小方块组合在一起形成一个ElasticSearch的索引。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/281f488591539f974f2a7fa5cf277a6c?showdoc=.jpg" alt="" /> 索引里的小方块</p> <p>在一个索引下,分布在多个节点里的绿色小方块称为分片——Shard。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/0eab1e90d1c8b74b41d0ad06a95f7d44?showdoc=.jpg" alt="" /></p> <p>Shard=Lucene Index 一个ElasticSearch的Shard本质上是一个Lucene Index。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/0637859f5dbac5641d2100315804eca7?showdoc=.jpg" alt="" /> Lucene是一个Full Text搜索库(也有很多其他形式的搜索库),ElasticSearch是建 立在Lucene之上的。接下来的故事要说的大部分内容实际上是ElasticSearch如何基 于Lucene工作的。</p> <h1>2 图解Lucene</h1> <p>Mini索引——segment 在Lucene里面有很多小的segment,我们可以把它们看成Lucene内部的miniindex。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/1f9578df6a8c24b9facd81b7b1971416?showdoc=.jpg" alt="" /> Segment内部 有着许多数据结构</p> <ul> <li>Inverted Index</li> <li>Stored Fields</li> <li>Document Values</li> <li>Cache <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/568977be601211feff2aca63ecb06f63?showdoc=.jpg" alt="" /></li> </ul> <p>最最重要的Inverted Index</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/ee7f311c07459e50a88e8d6caf86de3d?showdoc=.jpg" alt="" /></p> <p>Inverted Index主要包括两部分:</p> <ul> <li>一个有序的数据字典Dictionary(包括单词Term和它出现的频率)。</li> <li>与单词Term对应的Postings(即存在这个单词的文件)。</li> </ul> <p>当我们搜索的时候,首先将搜索的内容分解,然后在字典里找到对应Term,从而查 找到与搜索相关的文件内容。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/aa3e81f623e7e67dc6af0eeb6f1bfdaa?showdoc=.jpg" alt="" /></p> <p>查询“the fury”</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/fb80b43e97a5286bdaa1c7d4f595688b?showdoc=.jpg" alt="" /></p> <p>自动补全(AutoCompletion-Prefix)</p> <p>自动补全(AutoCompletion-Prefix) 如果想要查找以字母“c”开头的字母,可以简单的通过二分查找(Binary Search) 在Inverted Index表中找到例如“choice”、“coming”这样的词(Term)。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/ccfe4308290d7ed643554285f0086f76?showdoc=.jpg" alt="" /></p> <p>昂贵的查找</p> <p>如果想要查找所有包含“our”字母的单词,那么系统会扫描整个Inverted Index, 这是非常昂贵的。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/2ebdb3e305c0e0e35e4efa962e5812b8?showdoc=.jpg" alt="" /></p> <p>在此种情况下,如果想要做优化,那么我们面对的问题是如何生成合适的Term。</p> <p>问题的转化</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/83cf6d7dbf3417a6e3f34f23f6102a44?showdoc=.jpg" alt="" /></p> <p>对于以上诸如此类的问题,我们可能会有几种可行的解决方案:</p> <ul> <li>suffix -&gt; xiffus *:如果我们想以后缀作为搜索条件,可以为Term做反 向处理。</li> <li>(60.6384, 6.5017) -&gt; u4u8gyykk:对于GEO位置信息,可以将它转换 为GEO Hash。</li> <li>123 -&gt; {1-hundreds, 12-tens, 123}:对于简单的数字,可以为它生成 多重形式的Term。</li> </ul> <p>解决拼写错误</p> <p>一个Python库:为单词生成了一个包含错误拼写信息的树形状态机,解决拼写错误的 问题。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/ac09345d3662416461959a258624db50?showdoc=.jpg" alt="" /></p> <p>Stored Field字段查找</p> <p>当我们想要查找包含某个特定标题内容的文件时,Inverted Index就不能很好的解决 这个问题,所以Lucene提供了另外一种数据结构Stored Fields来解决这个问题。本 质上,Stored Fields是一个简单的键值对key-value。默认情况下,ElasticSearch会 存储整个文件的JSON source。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/8f59d158a50191dd58ed13dbb69fe1a4?showdoc=.jpg" alt="" /></p> <p>Document Values为了排序,聚合</p> <p>即使这样,我们发现以上结构仍然无法解决诸如:排序、聚合、facet,因为我们可能 会要读取大量不需要的信息。 所以,另一种数据结构解决了此种问题:Document Values。这种结构本质上就是 一个列式的存储,它高度优化了具有相同类型的数据的存储结构。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/4eed0df90d7f07e4d13d5143849a47a3?showdoc=.jpg" alt="" /></p> <p>为了提高效率,ElasticSearch可以将索引下某一个Document Value全部读取到内存 中进行操作,这大大提升访问速度,但是也同时会消耗掉大量的内存空间。 总之,这些数据结构Inverted Index、Stored Fields、Document Values及其缓 存,都在segment内部</p> <h1>3、搜索发生时</h1> <p>搜索时,Lucene会搜索所有的segment然后将每个segment的搜索结果返回,最后 合并呈现给客户。 Lucene的一些特性使得这个过程非常重要:</p> <ul> <li>Segments是不可变的(immutable)。</li> <li>Delete?当删除发生时,Lucene做的只是将其标志位置删除,但是文件还 是会在它原来的地方,不会发生改变。</li> <li>Update? 所以对于更新来说,本质上它做的工作是:先删除,然后重新索 引(Re-index)。</li> <li>随处可见的压缩:Lucene非常擅长压缩数据,基本上所有教科书上的压缩 方式,都能在Lucene中找到。</li> <li>缓存所有的所有:Lucene也会将所有的信息做缓存,这大大提高了它的查 询效率。</li> </ul> <h1>4、缓存的故事</h1> <p>当ElasticSearch索引一个文件的时候,会为文件建立相应的缓存,并且会定期(每 秒)刷新这些数据,然后这些文件就可以被搜索到。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/d6a6fa48f4bc97127778d9e0e22848ee?showdoc=.jpg" alt="" /></p> <p>随着时间的增加,我们会有很多segments:</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/1511c80ef8c6a856cc8cb060087ce06e?showdoc=.jpg" alt="" /></p> <p>所以ElasticSearch会将这些segment合并,在这个过程中,segment会最终被删除掉: <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/b43a2102f01291023837bc860c5df078?showdoc=.jpg" alt="" /></p> <p>这就是为什么增加文件可能会使索引所占空间变小,它会引起merge,从而可能会有更多的压缩。 举个例子 有两个segment将会merge: <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/e18e0e71eea32eea17ec0859e221d441?showdoc=.jpg" alt="" /></p> <p>这两个segment最终会被删除,然后合并成一个新的segment: <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/aa3fd881919d3c901efff77e4225e9e2?showdoc=.jpg" alt="" /></p> <p>这时这个新的segment在缓存中处于cold状态,但是大多数segment仍然保持不变, 处于warm状态。</p> <p>以上场景经常在Lucene Index内部发生的。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/f832cfbc779cd609e7b38a497af78a75?showdoc=.jpg" alt="" /></p> <h1>5、在Shard中搜索</h1> <p>ElasticSearch从Shard中搜索的过程与Lucene Segment中搜索的过程类似。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/d697debdcdadeb4f1efdafa459ac8336?showdoc=.jpg" alt="" /></p> <p>与在Lucene Segment中搜索不同的是,Shard可能是分布在不同Node上的,所以在 搜索与返回结果时,所有的信息都会通过网络传输。 需要注意的是:1次搜索查找2个Shard=2次分别搜索Shard。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/75c4c1e8940390ab6ceb0e7c208c27bf?showdoc=.jpg" alt="" /></p> <p>对于日志文件的处理</p> <p>当我们想搜索特定日期产生的日志时,通过根据时间戳对日志文件进行分块与索引, 会极大提高搜索效率。 当我们想要删除旧的数据时也非常方便,只需删除老的索引即可。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/ad5646053065e3f919eec0c1c58e8c53?showdoc=.jpg" alt="" /></p> <p>在上种情况下,每个Index有两个shards。</p> <h1>6、如何Scale</h1> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/f334431bb3d41aa0a9773947d2cd3474?showdoc=.jpg" alt="" /></p> <p>Shard不会进行更进一步的拆分,但是Shard可能会被转移到不同节点上: <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/8404baa2ef81ff953b9d0d8683790165?showdoc=.jpg" alt="" /></p> <p>所以,如果当集群节点压力增长到一定的程度,我们可能会考虑增加新的节点,这就会要求我们对所有数据进行重新索引,这是我们不太希望看到的,所以我们需要在规划的时候就考虑清楚,如何去平衡足够多的节点与不足节点之间的关系。</p> <h1>7 节点分配与Shard优化</h1> <ul> <li>为更重要的数据索引节点,分配性能更好的机器。</li> <li>确保每个Shard都有副本信息replica <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/c7107234a4609fd6e10c495580b43d21?showdoc=.jpg" alt="" /></li> </ul> <h2>路由Routing</h2> <p>每个节点,每个都存留一份路由表,所以当请求到任何一个节点时,ElasticSearch都有能力将请求转发到期望节点的Shard进一步处理。 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/76c3e98e386e272d852f5e7087e462bc?showdoc=.jpg" alt="" /></p> <h2>一个真实的请求</h2> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/7c0410be33d5eeb6e0dc713f5e0450b4?showdoc=.jpg" alt="" /></p> <h2>Query</h2> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/d1dfe7d0556e4aab3f2bd6ac14f46d6d?showdoc=.jpg" alt="" /></p> <p>Query有一个类型filtered,以及一个multi_match的查询。</p> <p>Aggregation</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/fa776d6651320a85fa4c1b12cc18c4d4?showdoc=.jpg" alt="" /></p> <p>根据作者进行聚合,得到top10的hits的top10作者的信息。</p> <h2>请求分发</h2> <p>这个请求可能被分发到集群里的任意一个节点。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/1f251d970009399a3b9594ade6c2e130?showdoc=.jpg" alt="" /></p> <p>上帝节点</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/d1eb3c89cecd4cddd814446603b7e0ca?showdoc=.jpg" alt="" /></p> <p>这时这个节点就成为当前请求的协调者(Coordinator),它决定:</p> <ul> <li>根据索引信息,判断请求会被路由到哪个核心节点。</li> <li>以及哪个副本是可用的。</li> <li>等等。</li> </ul> <p>路由</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/00963f4ae883538630053640569fca45?showdoc=.jpg" alt="" /></p> <p>在真实搜索之前 ElasticSearch会将Query转换成Lucene Query:</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/b54e34f4e67eec76a361b78b91769828?showdoc=.jpg" alt="" /></p> <p>然后在所有的segment中执行计算:</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/5e42c6a51840e104263451b2394b58c4?showdoc=.jpg" alt="" /></p> <p>对于Filter条件本身也会有缓存:</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/a0033b9aeaf67b8cfdcc733d14dadc9c?showdoc=.jpg" alt="" /></p> <p>但Queries不会被缓存,所以如果相同的Query重复执行,应用程序自己需要做缓存:</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/058dd9debeacd9d34e2cee47df74cf41?showdoc=.jpg" alt="" /></p> <p>所以,</p> <ul> <li>Filters可以在任何时候使用;</li> <li>Query只有在需要score的时候才使用。</li> </ul> <p>返回</p> <p>搜索结束之后,结果会沿着下行的路径向上逐层返回。</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/45ff5decfeb1cc86c2168647b52ca0bd?showdoc=.jpg" alt="" /></p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/6b7544ad0d41f89362588498350d332a?showdoc=.jpg" alt="" /></p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/67835b1f1f72d85cbff4beac59290486?showdoc=.jpg" alt="" /></p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/c352d87a3bbdf0c85c9bf91e2445bea8?showdoc=.jpg" alt="" /></p>

页面列表

ITEM_HTML