ELK

Elasticsearch之分片内部原理

2017-11-04  本文已影响0人  techhow

使文本可被搜索

最好的支持 一个字段多个值 需求的数据结构是我们在 倒排索引 章节中介绍过的 倒排索引 。 倒排索引包含一个有序列表,列表包含所有文档出现过的不重复个体,或称为 词项 ,对于每一个词项,包含了它所有曾出现过文档的列表。

倒排索引被写入磁盘后是 不可改变 的:它永远不会修改。 不变性有重要的价值:

当然,一个不变的索引也有不好的地方。主要事实是它是不可变的! 你不能修改它。如果你需要让一个新的文档 可被搜索,你需要重建整个索引。这要么对一个索引所能包含的数据量造成了很大的限制,要么对索引可被更新的频率造成了很大的限制

动态更新索引

下一个需要被解决的问题是怎样在保留不变性的前提下实现倒排索引的更新? 答案是: 用更多的索引。

通过增加新的补充索引来反映新近的修改,而不是直接重写整个倒排索引。每一个倒排索引都会被轮流查询到--从最早的开始--查询完后再对结果进行合并。

Elasticsearch 基于 Lucene, 这个 java 库引入了 按段搜索 的概念。 每一 段 本身都是一个倒排索引, 但 索引 在 Lucene 中除表示所有 段 的集合外, 还增加了 提交点 的概念 — 一个列出了所有已知段的文件。新的文档首先被添加到内存索引缓存中,然后写入到一个基于磁盘的段。

逐段搜索会以如下流程进行工作:

  1. 新文档被收集到内存索引缓存,
  2. 不时地, 缓存被 提交 :
    • 一个新的段--一个追加的倒排索引--被写入磁盘。
    • 一个新的包含新段名字的 提交点 被写入磁盘。
    • 磁盘进行 同步 — 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入物理文件。
  3. 新的段被开启,让它包含的文档可3见以被搜索。
  4. 内存缓存被清空,等待接收新的文档。

一个 Lucene 索引包含一个提交点和三个段

一个在内存缓存中包含新文档的 Lucene 索引

在一次提交后,一个新的段被添加到提交点而且缓存被清空

当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添加到索引。

近实时搜索

随着按段(per-segment)搜索的发展, 一个新的文档从索引到可被搜索的延迟显著降低了。新文档在几分钟之内即可被检索,但这样还是不够快。

磁盘在这里成为了瓶颈。 提交(Commiting)一个新的段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。 但是 fsync 操作代价很大; 如果每次索引一个文档都去执行一次的话会造成很大的性能问题。

我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着 fsync 要从整个过程中被移除。

在Elasticsearch和磁盘之间是文件系统缓存。 像之前描述的一样, 在内存索引缓冲区中的文档会被写入到一个新的段中。 但是这里新段会被先写入到文件系统缓存--这一步代价会比较低,稍后再被刷新到磁盘--这一步代价比较高。不过只要文件已经在缓存中, 就可以像其它文件一样被打开和读取了。

Lucene 允许新段被写入和打开--使其包含的文档在未进行一次完整提交时便对搜索可见。 这种方式比进行一次提交代价要小得多,并且在不影响性能的前提下可以被频繁地执行。

  • 每隔一段时间--例如 translog 变得越来越大--索引被刷新(flush);一个新的 translog 被创建,并且一个全量提交被执行。
    • 所有在内存缓冲区的文档都被写入一个新的段。
    • 缓冲区被清空。
    • 一个提交点被写入硬盘。
    • 文件系统缓存通过 fsync 被刷新(flush)。
    • 老的 translog 被删除。
  • translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。

    translog 也被用来提供实时 CRUD 。当你试着通过ID查询、更新、删除一个文档,它会在尝试从相应的段中检索之前, 首先检查 translog 任何最近的变更。这意味着它总是能够实时地获取到文档的最新版本。

    这个执行一个提交并且截断 translog 的行为在 Elasticsearch 被称作一次 flush 。 分片每30分钟被自动刷新(flush),或者在 translog 太大的时候也会刷新。请查看 translog 文档 来设置,它可以用来 控制这些阈值:

    flush API 可以 被用来执行一个手工的刷新(flush)

    POST /blogs/_flush 
    

    段合并

    由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和cpu运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。

    Elasticsearch通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大的段再被合并到更大的段。

    段合并的时候会将那些旧的已删除文档 从文件系统中清除。 被删除的文档(或被更新文档的旧版本)不会被拷贝到新的大段中

    启动段合并不需要你做任何事。进行索引和搜索时会自动进行:

    1. 当索引的时候,刷新(refresh)操作会创建新的段并将段打开以供搜索使用;
    2. 合并进程选择一小部分大小相似的段,并且在后台将它们合并到更大的段中。这并不会中断索引和搜索;
    1. 一旦合并结束,老的段被删除。
      • 新的段被刷新(flush)到了磁盘。 写入一个包含新段且排除旧的和较小的段的新提交点
      • 新的段被打开用来搜索。
      • 老的段被删除。

    合并大的段需要消耗大量的I/O和CPU资源,如果任其发展会影响搜索性能。Elasticsearch在默认情况下会对合并流程进行资源限制,所以搜索仍然 有足够的资源很好地执行

    在这种情况下,使用optimize优化老的索引,将每一个分片合并为一个单独的段就很有用了;这样既可以节省资源,也可以使搜索更加快速:

    POST /logstash-2014-10/_optimize?max_num_segments=1 
    

    参考资料

    Elasticsearch:权威指南

    上一篇下一篇

    猜你喜欢

    热点阅读