1、对于按创建时间的排序可以使用doc.id的方式
new SortField(null, SortField.DOC, reverse)排序方式尽量使用INT类型的字段
也就是按照写入索引的顺序排序
2、对于时间字符串的排序可以转换成整数进行排序
3、去掉不必要的parse
使用TermQuery替换
4、TermQuery和Term可以只保留一个实例
createTerm(text)
5、减少Doc到model的转换
索引出来String到Date的转换多余而且费时
直接使用Doc对象包装成JSONObject
6、MultiFieldQueryParser改成自己用boolean查询重构
7、减少请求参数的包装类
8、搜索排序方法可以作为常量
将sort参数变成int型,使用swich进行判断
10、使用HitCollector类来适应不同情况下,Hits的大小
新、旧接口
相关搜索接口
11、使用尽可能快的磁盘IO
12、日志,先写文件,每天批量入库
13、增量索引使用reopen
新的reopen()方法只会加载那些变更过的索引片断,而不是重新加载完整的索引。
14、setMergeFactor 在做实时索引的时候,可以设置的小一点
这样就会及时索引进去
增量更新 (只需将 create 参数设为 false,即可往现有索引库添加新数据。
IndexWriter writer = new IndexWriter(directory, analyzer, false);
1,使用indexreader创建indexsearcher.
2,indexsearcher在使用完了以后不要关闭.
3.使用indexreader.isCurrent()判断索引是否被indexwriter改动.
4,如果索引被改动,indexsearcher.close()先前那个,然后new indexsearcher(indexreader).
增量BUILD的解决安案情:
1。
其中在每个检索方法里构建代码如下:
Directory directory = FSDirectory.getDirectory(DocumentIndexerImpl.getIndexSavePath());
IndexSearcher isearcher = new IndexSearcher(directory);
IndexReader reader = IndexReader.open(directory);
在检索完之后有:
isearcher.close();
reader.close();
可会导致每次检索进入这个方法都要进行一次IO操作。
2.
传string给searcher,searcher会维护一个内部的reader,当本次搜索结束后reader会被关掉.如果使用reader构造,reader在本次搜索结束后不会被关掉,除非你自己调用reader.close();所以用reader去构造searcher,然后通过searcher.getIndexReader()获取当前的reader,用reader.iscurrent()判断索引文件是否变化了,如果索引文件已经更改,则把当前的searcher关闭,然后再利用reader.reopen()方法获取新的reader,再创建一个searcher,像这样,new IndexSearcher(reader.reopen());
代码:
一、indexSearcher=new IndexSearcher(IndexReader.open(indexPath));
二、/*
* 先获取reader,如果索引文件已经变化,关闭当前indexSearcher,然后以重新获取的indexReader
* 作为参数new一个新的indexSearcher实例
*/
IndexReader indexReader=indexSearcher.getIndexReader();//获取当前的indexReader
if(!indexReader.isCurrent()){//判断是否有索引更新
// 如果有索引更新,先关闭当前的indexSearcher
indexSearcher.close();
//利用indexReader.reopen()获取新的indexReader,并作为IndexSearcher的参数创建一个新的indexSearcher
indexSearcher=new IndexSearcher(indexReader.reopen());
}
一个索引只能有一个indexreader。
首先判断 lastmodify时间,如果被更新了则, new 一个IndexReader, 否则就直接返回null,
而操作方法都扔给了用户. 所以官方所谓的reopen能减少消耗说法并不准确,它做的只是在改变时重新连接,
但这个他官方文档所说的只更新更新部分有很大不同,现在只是选择性地降低了io,
但是如果每次探测时索引都变了就变得没有意义,这样的好处是,索引的跟踪可以变得更频繁,也不再需要程序过多干预了.
Lucene2.3.2的变更
Author: Jeremy Chow([email protected])
Last Modified: Aug 1st, 2008
一、运行时
1. IndexWriter最大化地提高了现有的索引速度。第一,IndexWriter现在由内存的使用情况来决定刷新索引至磁盘,而不是之前用IndexWriter.setMaxBufferedDocs规定缓存的Docuement数量来决定刷新行为。第二,ConcurrentMergeScheduler使用后方(backgroud)线程(调用IndexWriter.setMergeScheduler (new SerialMergeScheduler())保持向下兼容) 来进行合并操作,由此得到的好处是合并行为不会阻塞IndexWriter.addDocument操作。第三,现在对segment的合并操作是依据每个segment大小,而不再是依据它们所包含Document的数量。
2. SortField.AUTO对long型起作用。在之前的lucene版本中,为排序自动检测Field类型时,数字首先被解释成int,如果解释失败,则认为是float。现在则先检测是否int,然后是否long,最后是float。
二、API 的变更
1. 新加了IndexWriter.setRAMBufferSizeMB(...),IndexWriter当缓存的Documents容量大于指定内存大小时进行刷新。同时,Token也添加了新的API,使用户使用char[]结合偏移量与长度来表示token (避免为每个Token调用一次new String ())。
三、新特性
1. 新加了IndexReader.reopen() 方法用来重新打开已经存在的IndexReader。
2. 新加了IndexWriter.optimize(int maxNumSegments) 用来局部优化值小于maxNumSegments 的片段
3. 新加了IndexWriter.optimize(int maxNumSegments) 局部优化索引
4. 新加了IndexCommit.isOptimized()。
5. 新加了TokenFilter.reset()。
四、优化
1. CachingTokenFilter 现在使用一个迭代器来访问缓存在LinkedList内的Tokens。显著地提升了性能,特别是在Tokens数目比较大的情况。
2. 从实质上优化了IndexWriter怎么使用RAM来缓存Documents,使索引速度加快到之前的2~8倍。现在使用了一个简单的共享散列表记录在内存中的Postings。一个Term对应一个posting。这个表直接写入到一个segment中。
3. 消除了偶尔会发生在复合文件(compound files)上的缓存到缓存拷贝(buffer to buffer copy)。
4. 去掉了Document中的同步机制,实际就是把存储Fields的Vector改成了ArrayList。
5. StandardTokenizer (StandardAnalyzer)使用JFlex代替JavaCC生成Tokenizer,使其速度是以前的6倍。
6. 使用bulk-coping技术拷贝原始字节,加速邻接的未删除文档的合并。
7.加速了 ISOLatin1AccentFilter 。
五、重新打开索引文件
IndexReader .reopen()
如果IndexReader实例调用此方法时,索引有变化,则生成一个新的IndexReader实例。
打开索引是非常昂贵的操作。此方法可以用来刷新已经存在的IndexReader,减少代价。此方法尝试只加载已经变化或新建立的片段(segement)。
如果索引自从打开后没有变化,则返回原来的IndexReader对象,否则会返回一个新的IndexReader对象。此时旧的IndexReader对象没有 关闭,只是不可用。
注意: 新打开的IndexReader对象可能会与旧的对象共享资源,据此,千万不要调用这些对象对索引的任何修改操作(例如:deleteDocument(int) , setNorm(int, String, byte) ),除非旧IndexReader对象已经关闭。否则,其行为将是不可确定的。
你可以通过比较返回的IndexReader与原来的IndexReader来判断其是否真的重新打开了:
IndexReader reader = ...
...
IndexReader new = r.reopen();
if (new != reader) {
... // reader was reopened
reader.close();
}
reader = new;
...
六、建立索引优化原理
以DocumentsWriter代替了之前的DocumentWriter。前者接受多个文档(Documents)的添加,并且直接把它们写入到一个单一的片段(segment)中。它比lucene2.2.0中用DocumentWriter为每个文档建立一个片段,然后将这些片段合并的方法更高效。
当加一篇文档时,它所存储的属性(Field)和词条向量(Term Vector)立即被写入到磁盘目录(假设不用RAM)。词频freq/prox 等词条信息(Posting),以词条为键值加入到Posting散列中。Posting散列表的每项维护着一个词频和prox组成的独立字节流。它包含多个文档的Posting数据。如果启用了向量,则为每个文档的每条词条都分配一个Posting向量(Posting Vector)。向量的每个元素记录词条的偏移量、位置等信息。
当Posting散列已满(假设使用RAM)或者加入到RAM的文档数目足够大(在此,我们用文档数目,而不是RAM使用量来刷新索引)时,我们会建立一个真实的片段,把它刷新到磁盘,然后重置Posting散列。
加入文档时,我们首先由属性(Field)名来组织文档所有的属性,并为每个属性记录其Posting散列。处理完每个属性后,我们刷新它的词条向量。当到整个片段的时候,我们首先通过名称将属性(fields)排序,然后依次排序每个属性的Postings。
线程:
多条线程可以同时调用addDocument方法。DocumentsWriter内部有一个getThreadState的同步调用,它为当前线程式分配一个ThreadState对象。无论何时,同一线程将会得到同一ThreadState。因此,如果各线程为各自不同的内容建立索引,则可以更有效地使用RAM。之后,processDocument会在那个ThreadState之上被调用,它不用作同步(很多消耗在这一步)。这是lucene2.3.0最重要的改进。 最后,同步调用finishDocument刷新变化至磁盘目录。
每个ThreadState实例都有属于其自身的Postng散列。当我们使用过多RAM时,会通过合并多个线程状态(ThreadSate)中相同词条的Posting表中的文档ID(docIDs),刷新所有线程的Posting散列至一个片段中。
当IndexWriter调用刷新操作(flush),且autoCommit=false时,会执行刷新。此时,我们强制所有线程空闲,且确定它们都空闲后才刷新。这意味着,你可以由一个给定的线程调用刷新操作。
七、建索引速度测试
CPU: Intel Dual CPU 2.00GHz,
内存: 1G DDR400
文本材料大小 : : 43.9 MB
文件数目 : 19997 个
分析器 : lucene 标准 StandardAnalyzer
Lucene 版本: 2.2.0
生成索引大小 : : 16,901,638 字节
耗时:
158094 微秒
110437 微秒
106328 微秒
Lucene 版本: 2.3.2
生成索引大小 : 16,177,772 字节
默认 16M 缓存耗时 :
15407 微秒
15500 微秒
设置 64M 缓存耗时 :
13578 微秒
13984 微秒
13359 微秒
15500 微秒
可以看出 2.3 比 2.2 建索引的速度要快 5~10 倍。 (不过现在看来是因为StandardAnalyzer变快了. )
索引过程中的任意时刻、任意进程都能对索引文件进行优化,
而且这样做也不会损坏索引文件或使其不能被搜索,
但是在索引过程中对索引进行优化的做法并不值得提倡
。优化操作的最佳时机是在索引过程结束之后,
且当你确认在此后的一段时间内不会对索引文件进行更改的时候。在索引过程中进行优化只会使优化操作耗费更多的时间。
索引优化过程需要两倍于索引文件磁盘空间。
使用 IndexWriter.addIndexes(IndexReader[]) 和 IndexWriter.addIndexes(Directory[]) 合并索引库有什么不同?
使用 Directory[] 参数所需的文件句柄和内存较小,索引文件仅需打开一次,而使用 IndexReader[] 参数则需要打开所有的索引库。