(1) Lucene查询上只能提供近实时而非实时查询,原因是Segment在被flush或commit之前,数据保存在内存中,是不可被搜索的。
(2) IndexWriter提供的核心接口都是线程安全的,并且内部做了特殊的并发优化来优化多线程写入的性能。IndexWriter内部为每个线程都会单独开辟一个空间来写入,这块空间由DocumentsWriterPerThread(简称DWPT)来控制。整个多线程数据处理流程为:
1. 多线程并发调用IndexWriter的写接口,在IndexWriter内部具体请求会由DocumentsWriter来执行。DocumentsWriter内部在处理请求之前,会先根据当前执行操作的Thread来分配DocumentsWriterPerThread。
2. 每个线程在其独立的DocumentsWriterPerThread空间内部进行数据处理,包括分词、相关性计算、索引构建等。
3. 数据处理完毕后,在DocumentsWriter层面执行一些后续动作,例如触发FlushPolicy的判定等。
引入DWPT后,Lucene内部在处理数据时,整个处理步骤只需要对以上第一步和第三步进行加锁,第二步完全不用加锁,每个线程都在自己独立的空间内处理数据。而通常来说,第一步和第三步都是非常轻量级的,而第二步是对计算和内存资源消耗最大的。所以这样做之后,能够将加锁的时间大大缩短,提高并发的效率。每个DWPT内单独包含一个In-memory buffer,这个buffer最终会flush成不同的独立的segment文件。
(3) flush:flush是将DWPT内In-memory buffer里的数据持久化到文件的过程,flush会在每次新增文档后由FlushPolicy判定自动触发,也可以通过IndexWriter的flush接口手动触发。每个DWPT会flush成一个segment文件,flush完成后这个segment文件是不可被搜索的,只有在commit之后,所有commit之前flush的文件才可被搜索。
(4) commit:commit时会触发数据的一次强制flush,commit完成后再此之前flush的数据才可被搜索。commit动作会触发生成一个commit point,commit point是一个文件。Commit point会由IndexDeletionPolicy管理,lucene默认配置的策略只会保留last commit point,当然lucene提供其他多种不同的策略供选择。
(5) merge:merge是对segment文件合并的动作,合并的好处是能够提高查询的效率以及回收一些被删除的文档。Merge会在segment文件flush时触发MergePolicy来判定自动触发,也可通过IndexWriter进行一次force merge。
(6) close:close = commit + flush + merge
(7) 单线程内,相同的IndexWriter对象,一并commit或先后commit都没有问题。
(8) 单线程内,不同的IndexWriter对象,如果对象A还未close就操作对象B,结果抛出异常(LockObtainFailedException),如果对象A close后再操作对象B则没有问题。
(9) 多线程环境下,相同的IndexWriter对象,先后commit没有问题(线程安全)。
(10) 多线程环境下,不同的IndexWriter对象,道理同(7),close一个才能操作另外一个。
(11) 单线程内,IndexWriter操作完成后commit才能使用IndexReader,多线程环境下则没有问题。
(12) 在Web环境下,IndexReader(IndexSearcher)和IndexWriter都推荐使用单例模式(消耗较大,线程安全)。下面是一个标准例子。
1 package XXX; 2 3 import lombok.extern.slf4j.Slf4j; 4 import org.apache.lucene.analysis.Analyzer; 5 import org.apache.lucene.analysis.standard.StandardAnalyzer; 6 import org.apache.lucene.index.DirectoryReader; 7 import org.apache.lucene.index.IndexReader; 8 import org.apache.lucene.index.IndexWriter; 9 import org.apache.lucene.index.IndexWriterConfig; 10 import org.apache.lucene.search.IndexSearcher; 11 import org.apache.lucene.store.Directory; 12 import org.apache.lucene.store.FSDirectory; 13 import org.springframework.beans.factory.annotation.Value; 14 import org.springframework.stereotype.Service; 15 import org.springframework.transaction.annotation.Transactional; 16 17 import java.io.IOException; 18 import java.nio.file.Paths; 19 20 @Service 21 @Slf4j 22 public class LuceneService { 23 24 @Value("${app.property.index-dir}") 25 private String indexDir; 26 27 private Directory directory; 28 29 private IndexReader indexReader; 30 31 private IndexWriter indexWriter; 32 33 /** 34 * Get Directory. 35 * 36 * @return Directory 37 */ 38 private synchronized Directory getDirectory() { 39 if (directory == null) { 40 try { 41 directory = FSDirectory.open(Paths.get(indexDir)); 42 } catch (IOException e) { 43 throw new RuntimeException("Create Directory failed!", e); 44 } 45 } 46 return directory; 47 } 48 49 /** 50 * Get IndexReader/IndexSearcher. 51 * 52 * @return IndexReader 53 */ 54 public synchronized IndexReader getIndexReader() { 55 try { 56 if (indexReader == null) { 57 indexReader = DirectoryReader.open(getDirectory()); 58 } else { 59 IndexReader newReader = DirectoryReader.openIfChanged((DirectoryReader) indexReader); 60 if (newReader != null) { 61 // close the old indexReader 62 indexReader.close(); 63 indexReader = newReader; 64 } 65 } 66 return indexReader; 67 } catch (IOException e) { 68 throw new RuntimeException("Create IndexReader failed!", e); 69 } 70 } 71 72 /** 73 * Get IndexSearcher. 74 * Recommend instead of IndexReader. 75 * 76 * @return IndexReader 77 */ 78 public IndexSearcher getIndexSearcher() { 79 return new IndexSearcher(getIndexReader()); 80 } 81 82 /** 83 * Get IndexWriter. 84 * 85 * @return IndexWriter 86 */ 87 public synchronized IndexWriter getIndexWriter() { 88 if (indexWriter == null) { 89 Analyzer analyzer = new StandardAnalyzer(); 90 IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer); 91 try { 92 indexWriter = new IndexWriter(getDirectory(), indexWriterConfig); 93 } catch (IOException e) { 94 throw new RuntimeException("Create IndexWriter failed!", e); 95 } 96 } 97 return indexWriter; 98 } 99 }