微笑着面对每天,善待自己……

Lucene性能优化

上一篇 / 下一篇  2011-04-26 19:09:20 / 个人分类:技术

9 性能优化
 
一直到这里,我们还是在讨论怎么样使lucene跑起来,完成指定任务。利用前面说的也确实能完成大部分功能。但是测试表明 lucene的性能并不是很好,在大数据量大并发的条件下甚至会有半分钟返回的情况。另外大数据量的数据初始化建立索引也是一个十分耗时的过程。那么如何提高lucene的性能呢?下面从优化创建索引性能和优化搜索性能两方面介绍。
 
9.1 优化创建索引性能
 
这方面的优化途径比较有限,IndexWriter提供了一些接口可以控制建立索引的操作,另外我们可以先将索引写入RAMDirectory,再批量写入 FSDirectory,不管怎样,目的都是尽量少的文件IO,因为创建索引的最大瓶颈在于磁盘IO。另外选择一个较好的分析器也能提高一些性能。
 
9.1.1 通过设置IndexWriter的参数优化索引建立
 
setMaxBufferedDocs(int maxBufferedDocs)
 
控制写入一个新的segment前内存中保存的document的数目,设置较大的数目可以加快建索引速度,默认为10。
 
setMaxMergeDocs(int maxMergeDocs)
 
控制一个segment中可以保存的最大document数目,值较小有利于追加索引的速度,默认Integer.MAX_VALUE,无需修改。
 
setMergeFactor(int mergeFactor)
 
控制多个segment合并的频率,值较大时建立索引速度较快,默认是10,可以在建立索引时设置为100。
 
9.1.2 通过RAMDirectory缓写提高性能
 
我们可以先把索引写入RAMDirectory,达到一定数量时再批量写进FSDirectory,减少磁盘IO次数。
 
FSDirectory fsDir = FSDirectory.getDirectory("/data/index", true);
 
RAMDirectory ramDir = new RAMDirectory();
 
IndexWriter fsWriter = new IndexWriter(fsDir, new StandardAnalyzer(), true);
 
IndexWriter ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);
 
while (there are documents to index)
 
{
 
... create Document ...
 
ramWriter.addDocument(doc);
 
if (condition for flushing memory to disk has been met)
 
{
 
fsWriter.addIndexes(new Directory[] { ramDir });
 
ramWriter.close();
 
ramWriter = new IndexWriter(ramDir, new StandardAnalyzer(), true);
 
}
 
}
 
9.1.3 选择较好的分析器
 
这个优化主要是对磁盘空间的优化,可以将索引文件减小将近一半,相同测试数据下由600M减少到380M。但是对时间并没有什么帮助,甚至会需要更长时间,因为较好的分析器需要匹配词库,会消耗更多cpu,测试数据用StandardAnalyzer耗时133分钟;用MMAnalyzer耗时150分钟。
 
9.2 优化搜索性能
 
虽然建立索引的操作非常耗时,但是那毕竟只在最初创建时才需要,平时只是少量的维护操作,更何况这些可以放到一个后台进程处理,并不影响用户搜索。我们创建索引的目的就是给用户搜索,所以搜索的性能才是我们最关心的。下面就来探讨一下如何提高搜索性能。
 
9.2.1 将索引放入内存
 
这是一个最直观的想法,因为内存比磁盘快很多。Lucene提供了RAMDirectory可以在内存中容纳索引:
 
Directory fsDir = FSDirectory.getDirectory(“/data/index/”, false);
 
Directory ramDir = new RAMDirectory(fsDir);
 
Searcher searcher = new IndexSearcher(ramDir);
 
但是实践证明RAMDirectory和FSDirectory速度差不多,当数据量很小时两者都非常快,当数据量较大时(索引文件400M)RAMDirectory甚至比FSDirectory还要慢一点,这确实让人出乎意料。
 
而且lucene的搜索非常耗内存,即使将400M的索引文件载入内存,在运行一段时间后都会out of memory,所以个人认为载入内存的作用并不大。
 
9.2.2 优化时间范围限制
 
既然载入内存并不能提高效率,一定有其它瓶颈,经过测试发现最大的瓶颈居然是时间范围限制,那么我们可以怎样使时间范围限制的代价最小呢?
 
当需要搜索指定时间范围内的结果时,可以:
 
1、用RangeQuery,设置范围,但是RangeQuery的实现实际上是将时间范围内的时间点展开,组成一个个BooleanClause加入到 BooleanQuery中查询,因此时间范围不可能设置太大,经测试,范围超过一个月就会抛 BooleanQuery.TooManyClauses,可以通过设置 BooleanQuery.setMaxClauseCount(int maxClauseCount)扩大,但是扩大也是有限的,并且随着 maxClauseCount扩大,占用内存也扩大
 
2、用RangeFilter代替RangeQuery,经测试速度不会比 RangeQuery慢,但是仍然有性能瓶颈,查询的90%以上时间耗费在 RangeFilter,研究其源码发现RangeFilter实际上是首先遍历所有索引,生成一个BitSet,标记每个document,在时间范围内的标记为true,不在的标记为false,然后将结果传递给 Searcher查找,这是十分耗时的。
 
3、进一步提高性能,这个又有两个思路:
 
a、缓存Filter结果。既然RangeFilter 的执行是在搜索之前,那么它的输入都是一定的,就是IndexReader,而 IndexReader是由Directory决定的,所以可以认为 RangeFilter的结果是由范围的上下限决定的,也就是由具体的 RangeFilter对象决定,所以我们只要以RangeFilter对象为键,将filter结果BitSet缓存起来即可。lucene API已经提供了一个CachingWrapperFilter类封装了Filter及其结果,所以具体实施起来我们可以cache CachingWrapperFilter对象,需要注意的是,不要被 CachingWrapperFilter的名字及其说明误导, CachingWrapperFilter看起来是有缓存功能,但的缓存是针对同一个 filter的,也就是在你用同一个filter过滤不同 IndexReader时,它可以帮你缓存不同IndexReader的结果,而我们的需求恰恰相反,我们是用不同filter过滤同一个 IndexReader,所以只能把它作为一个封装类。
 
b、降低时间精度。研究Filter的工作原理可以看出,它每次工作都是遍历整个索引的,所以时间粒度越大,对比越快,搜索时间越短,在不影响功能的情况下,时间精度越低越好,有时甚至牺牲一点精度也值得,当然最好的情况是根本不作时间限制。
 
下面针对上面的两个思路演示一下优化结果(都采用800线程随机关键词随即时间范围):
 
第一组,时间精度为秒:
 
方式 直接用RangeFilter 使用cache 不用filter
 
平均每个线程耗时 10s 1s 300ms
 
第二组,时间精度为天
 
方式 直接用RangeFilter 使用cache 不用filter
 
平均每个线程耗时 900ms 360ms 300ms
 
由以上数据可以得出结论:
 
1、 尽量降低时间精度,将精度由秒换成天带来的性能提高甚至比使用cache还好,最好不使用filter。
 
2、 在不能降低时间精度的情况下,使用cache能带了10倍左右的性能提高。
 
9.2.3 使用更好的分析器
 
这个跟创建索引优化道理差不多,索引文件小了搜索自然会加快。当然这个提高也是有限的。较好的分析器相对于最差的分析器对性能的提升在20%以下。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/danyajuan/archive/2009/03/11/3978177.aspx


TAG:

 

评分:0

我来说两句

evergreen_wang

evergreen_wang

测试因仔

日历

« 2024-04-20  
 123456
78910111213
14151617181920
21222324252627
282930    

数据统计

  • 访问量: 29013
  • 日志数: 52
  • 文件数: 6
  • 建立时间: 2009-06-17
  • 更新时间: 2011-05-31

RSS订阅

Open Toolbar