elasticsearch. concepts

Building Blocks

当我们要构建一个分布式接近实时的搜索引擎,并且要让lucene可伸缩可扩展,必须首先知道lucene的关键概念以及它们与我们要达成目标的一些局限性.

Directory

Lucene Directory 是一个抽象的文件系统的接口,用来允许你读写文件,不管lucene的索引是存放在内存中还是在物理磁盘上,它都是通过lucene的Directory抽象层来访问和维护的。

IndexWriter

IndexWriter 用来添加、删除和更新lucene里面的索引文档。这些操作是在内存中完成以保证更好的性能,但是如果要保证这些操作的持久化,这些操作是需要flush到磁盘的。并且,flush操作或者是显式的commit提交开销都是比较大的,因为这些操作通常需要处理很多的文件,而处理这些文件又涉及到大量的磁盘io。

此外, 每次只能有一个IndexWriter对象来对一个索引目录进行索引操作,并且创建这个对象的开销很大,所以必须尽可能去重用这个对象.

Index Segments

Lucene 索引被分解为很多段(segments)。每个索引段实际上是一个功能完整的lucene索引,一旦一个索引段创建完成,它将是不可变的,并且不能删除段里面的索引文档。commit提交操作用来往索引里面添加一个新段。lucene内部会来对这些段进行合并,所以我们必须要有策略来控制这些合并(MergePolisy, MergeScheuler, … etc)。Because segments need to be kept at bay they are being merged continuously by internal Lucene processes (MergePolisy, MergeScheuler, … etc).

因为段是不可变的,所以用来做缓存(caching)是一个很好的选择,你可以加载所有的term词条并且创建一个跳跃列表( skip lists ) ,或者用来构造FieldCache,如果段没有变化,你就不需要重新加载。

IndexReader

IndexReader 用来执行搜索索引。这个对象通过IndexWriter来提供,并且创建代价也是比较高。一旦IndexReader打开之后,它就不能够发现打开之后的索引变化,如果要知道这些由IndexWriter产生的索引变化,除非刷新IndexReader对象(当然前提需要flush操作)。搜索操作在内部其实是按段来进行的(每次一个段).

Near Real-Time Search

获取一个新的IndexReader开销很大,所以也是我们不能每次一有索引操作就真的去获取一个新的IndexReader,你可以隔一段时间去刷新一下,比如每隔一秒钟等等,这也是我们在这里称之为接近实时( near real-time )的原因.

可能用来伸缩Lucene的途径(Possible approach to Scale Lucene)

Distributed Directory

其中一个途径用来伸缩Lucene就是使用分布式文件系统,大文件会被拆分成chunks块并且会保存到分布式存储系统(比如 Coherence, Terracota, GigaSpaces or Infinispan等等)。这样IndexWriter和IndexReader都是工作在一个自定义的Directory分布式实现上,每个操作后面其实是分布了很多个节点,每个节点上面存储了索引文件的一部分.

但是这种方案有一些问题:

  • 首先,这种方案会产生密集的网络流量。尽管可以用一些高级的方法如本地缓存等,但仍然会产生大量的网络请求,因为最主要的原因是因为这种将文件拆分为块的想法与lucene索引文件构建方式和使用方式实在相隔太远,结论就是使用这种方式来做大规模索引和搜索是不切实际的。(ps:所以solandra这种玩意还是不要去考虑了).
  • 其次,大的索引必然会使IndexReader变的无法分布式。IndexReader是一个很重的对象,并且term词条越多,其消耗的内存也会越多。
  • 最后,索引操作也会变的非常困难,因为只有一个单一的IndexWriter能够写索引。

NEXT: 让我们再看看另外的一种选择: 分区.

 
Fork me on GitHub