2.1.6 截取索引(Indextruncate)
一些应用程序的所以文档的大小先前是不知道的。作为控制RAM和磁盘存储空间的使用数量的安全机制,你可能想要限制每个字段允许输入索引的输入数量。一个大的二进制文档偶尔被错误地划分为文本文档,或者包含嵌入在它中的二进制被你的过滤器错误地处理,这些都是可能的。
另外一些应用程序处理知道文档大小,但是你想索引仅仅是其中的一部分内容。例如,你可能想每个文档索引仅仅200个单词或者字数。为了支持反转的情况,IndexWriter允许你截取每个字段索引以便于仅仅首先N个terms用于analzyed字段来建立索引。通过传入参数MaxField-Length.UNLIMITED和MaxField-Length.LIMITED来截取还是不做截取操作,MaxField-Length.LIMITED意味着字段会被截取10,000terms条。
使用任何字段截取前,请仔细地考虑一下。它意味着仅仅首先N个terms能够被搜索到,任何超过第N term将被完全忽略掉。
2.1.7 Near-real-time搜索
Lucene 2.9 引入了一个重要的特性—接近实时搜索,它关注于所有搜索引擎频繁的受到的一个挑战:在建立索引后能够迅速地搜索到documents。许多应用程序有这样的要求,但是对于实现来说,它是一个挑战。幸运的是,现在Lucene通过提供这个方法IndexWriter使之很容易实现:
IndexReader getReader()
这个方法立刻刷新任何缓存中添加或者删除的documents,然后,创建一个新的只读的IndexWriter,它包括哪些documents。在它的实现内部,一个新的打开的reader以一种新高效的方式被实例化,以便于任何旧的段(segments)与先前reader一样被共享。这样,如果仅仅几个文档被添加,这个转向时间将通常会很快。注意到,调用getReader必然使你的索引吞吐量的速度降低,因为它引起IndexWriter立刻冲刷一个新的段(segment),代替了等待直到它的RAM缓存已经满了。
2.1.8 优化一个索引
当你搜索索引时,Lucene必须分开地地搜索任一segment,然后,结合这个结果。尽管这无暇地奏效,通过优化索引应用程序处理大的索引会发现搜索性能得到了提高,其原因是由于它合并了许多segments到一个或者几个segments。在优化的过程中,我们看看磁盘的消耗情况。
没有优化情况下,你得到极好的搜索吞吐量是完全有可能的。因此,确保首先测试是否拟需要考虑优化索引。IndexWriter显露了四个进行优化的方法:
- optimize() 使产生的索引到一个单一的段中,直到操作完成时才返回。
- optimize(int maxNumSegments) 众所周知,它作为部分的优化,减少索引到指定最大的段数 maxNumSegments。因为最终的合并到一个段是花费比较大,就优化而言,五个段应该是比优化到一个段中要 快一点。为了更慢的搜索速度,允许你折中一下得到更少的优化时间。
- optimize(boolean doWait) doWait的值为false时,立刻调用并返回,而必要的合并操作在后台进行。
- optimize(int maxNumSegments, boolean doWait) 合并以上两个方法的特性,运行优化操作。
索引优化消耗大量的CPU和输入/输出(I/0)资源。因此,谨慎地使用它。
2.1.9 其它directory实现
无论什么时候Lucene需要对索引进行读或者写时,它使用Directory方法实现这个操作。这些类,有具体的Directory实现从文件系统中读和写文件。它们的子类是基于抽象FSDirectory类。不幸的是,没有单一最好FSDirecotry实现。在某一个情形下,任何一个类都有潜在严重的限制。
Ø SimpleFSDirectory 使用访问java.io.* APIs,遗憾的是,在读取过程中这个Directory实现不能扩展,原因是当多个线程在使用是,它必须使用内部锁来避免在java.io.*中缺乏按位置读取。
Ø NIOFSDirectory 使用java.nio.* APIs按位置读取,当读取索引时,无内部锁以及使用多线程扩展性好。遗憾的是,由于长时间存在windows上的JREs问题,NIOFSDirectory变现不太理想,可能比SimpleFSDirectory更糟糕。
Ø MMapDirectory 使用是内存映射I/O,没有任何锁。因此,它扩展到线程上非常好。但是因为内存I/O消耗线程地址空间,相当于你的索引的大小,在64位JRE上使用它最佳(依赖于OS)。Java没有提供一个清理”unmap”映射内存的文件区域,那意味着垃圾收集器起作用时,内在的文件会关闭以及内存会被释放。这意味着你能够轻易地有许多剩余的maps,消耗大量线程地址空间块以及遗留底层索引文件打开的时间比你希望的更高。另外,32为的JREs, 你肯能会遇到OutOfMemoryError 内存溢出错误。MMapDirectory Directory提供了setMaxChunkSize 方法避免这种情况发生。
2.1.10 并发、线程安全和锁问题
在这一节,我们讨论三个相近且相关的话题:多JVMs访问一个索引、IndexReader和IndexWriter的线程安全以及锁机制。理解这些内容是有必要的,尤其是对于并行服务于多个用户或者通过并发处理一些操作来扩展应用程序。
2.1.10.1 线程和多虚拟机安全
Lucene的并发规则是很简单的:
ü 在一个单一索引上,任意数量仅仅只读IndexReader可能立刻打开。如果这些reader在相同的JVM还是多个JVMs,或者相同的计算机还是多个计算机。记住,在一个单一JVM中,对于给予索引资源的使用和性能的原因而分享单一的IndexReader实例使用多线程。例如,多线程或者多进程可能并行搜索相同的索引。
ü 仅仅一个单一的writer可能为索引所打开。Lucene使用一个写锁文件强制实施。只要一个IndexWriter创建了,一个写锁被获取。仅仅当写锁被关闭了,写锁就释放。既然这样,如果你使用IndexWriter对索引做了一些改动—例如,改变norms或者删除documents—然后IndexWriter充当一个writer:在做第一个改变前,它必须成功地获取写锁,仅仅释放它立刻关闭。
ü IndexReader可能打开了,甚至当一个IndexWriter正在做对索引一些改变。任一IndexWriter在它打开时将总是展示索引。IndexWriter不会看到任何改变直到writer提交和reader再次被打开。当一个IndexReader已经代开了,以create = true打开一个新的IndexWriter是比较好的。IndexReader将继续搜索它的索引视图点。
ü 任何数量线程能够共享一个单一IndexReader实例或者InexWriter。这些内不仅仅是线程安全的,也是线程有好的,意味着他们通常对于添加线程扩展性挺好的(假如你的硬件有并发能力,因为大量的同步代码在内部是保持一个最小量的)
正如你看到的,Lucene 与多线程和多JVMs工作挺好的。但是,如果你需要在一个远程文件系统上分享一个索引,有意思的挑战。
2.1.10.2 从远程文件系统中访问索引
如果你打算有多个JVMs在不同的计算机上,访问相同的索引,你将不得不显露地访问远程文件系统的索引。一个普通的配置就是有一个单一专门的计算机在本地写入索引文件系统到存储索引远程计算机,然后,多台计算机通过一个远程文件系统实现搜索索引的功能。这样一个配置能够起作用,但是,相对于在本地文件系统而言,这个性能经常很糟糕。最佳的性能就是重复一个索引重复(replication)到每台需要做搜索的计算机文件系统中。Solr,在Lucene之上构建的企业搜索服务,支持盒子之外的replication功能。如果你仍然打算从一个远程文件系统中访问索引,意识到这方面的限制是很重要的。
2.1.10.3 索引锁
在某一刻,增强一个单一writer意味着一个一个IndexWriter或者一个IndexReader做删除或者改版norms,Lucene使用一个基于一个文件锁:如果这个锁文件(默认writer.lock)存在于你的索引目录中,一个当前的writer有索引代开。任何尝试去在相同索引上创建writer都会得到一个LockObtainFailedException。这个是一个相当重要的保护机制,这是因为,如果writer偶然创建一个单一索引,那将快速导致索引混乱。
Lucene允许你改变你的锁实现:任何LockFactory的子类能够通过调用Directory.setLockFactory作为你的锁实现的集合。确保在调用这个方法前打开一个在那个Directory下的IndexWriter实例。正常地情况下,你没有必要担心你使用的是哪个锁。通常只有拥有多个计算机或者JVMs的高级应用程序操作索引才有需要个性化锁的实现。