昨晚睡觉前把多线程创建索引demo写好了,今天早上7点多就起来,趁着劲头赶紧记录分享一下,这样对那些同样对Lucene感兴趣的童鞋也有所帮助。
我们都知道Lucene的IndexWriter在构造初始化的时候会去获取索引目录的写锁writerLock,加锁的目的就是保证同时只能有一个IndexWriter实例在往索引目录中写数据,具体看截图:
而在多线程环境下,光保证只有IndexWriter实例能得到锁还不行,还必须保证每次只能有一个线程能获取到writerLock,Lucene内部是如何实现的呢?请看源码:
indexWriter添加索引文档是通过addDocument方法实现的,下面是addDocument方法的截图:
我们发现内部实际调用的是updateDocument方法,继续跟进updateDocument方法,
updateDocument中ensureOpen();首先确保索引目录已经打开,然后通过docWriter.updateDocument(doc, analyzer, term)真正去更新索引,更新成功后触发索引merge事件processEvents(true, false);docWriter是DocumentsWriter类型,真正执行索引写操作的类是DocumentsWriter,IndexWriter只是内部维护了一个DocumentsWriter属性调用它的方法而已,继续跟进DocumentsWriter类的updateDocument方法,如图:
final ThreadState perThread = flushControl.obtainAndLock();会视图去获取Lock,因为索引写操作不能同时并发执行,没错这里的ThreadState就是NIO里的ReentrantLock,它跟synchronized作用类似,但它比synchronized控制粒度更小更灵活,能手动在方法内部的任意位置打开和解除锁,两者性能且不谈,因为随着JVM对代码的不断优化,两者性能上的差异会越来越小。扯远了,接着前面的继续说,flushControl.obtainAndLock()在获取锁的时候内部实际是通过perThreadPool.getAndLock来获取锁的,perThreadPool并不是什么线程池,准确来说它是一个锁池,池子里维护了N把锁,每个锁与一个线程ID,跟着我继续看源码,你就明白了。
perThreadPool是如何获取lock的呢?继续看它的getAndLock方法:
getAndLock需要传入一个线程,而flushControl.obtainAndLock()在获取锁的时候内部是这样实现的:
到此,你应该明白了,Lucene内部只是维护了多把锁而已,并没有真的去New Thread,Thread是通过把当前调用线程当作参数传入的,然后分配锁的时候,每个线程只分配一把锁,而每把锁在写索引的时候都会使用ReentrantLock.lock来限制并发写操作,其实每次对于同一个索引目录仍然只能有一个indexWriter在写索引,那Lucene内部维护多把锁有什么意义呢?一个索引目录只能有一把锁,那如果有多个索引目录,每个索引目录发一把锁,N个索引目录同时进行索引写操作就有意义了。把索引数据全部放一个索引目录本身就不现实,再说一个文件夹下能存放的文件最大数量也不是无穷大的,当一个文件夹下的文件数量达到某个数量级会你读写性能都会急剧下降的,所以把索引文件分摊到多个索引目录是明智之举,所以,当你需要索引的数据量很庞大的时候,要想提高索引创建的速度,除了要充分利用RAMDirectory减少与磁盘IO次数外,可以尝试把索引数据分多索引目录存储,个人建议,如果说的不对,请尽情的喷我。下面我贴一个我昨晚写的多线程创建索引的demo,抛个砖引个玉哈!看代码:
package com.yida.framework.lucene5.index;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.CountDownLatch;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.FSDirectory;
import com.yida.framework.lucene5.util.LuceneUtils;
/**
* 索引创建线程
* @author Lanxiaowei
*
*/
public class IndexCreator implements Runnable {
/**需要读取的文件存放目录*/
private String docPath;
/**索引文件存放目录*/
private String luceneDir;
private int threadCount;
private final CountDownLatch countDownLatch1;
private final CountDownLatch countDownLatch2;
public IndexCreator(String docPath, String luceneDir,int threadCount,CountDownLatch countDownLatch1,CountDownLatch countDownLatch2) {
super();
this.docPath = docPath;
this.luceneDir = luceneDir;
this.threadCount = threadCount;
this.countDownLatch1 = countDownLatch1;
this.countDownLatch2 = countDownLatch2;
}
public void run() {
IndexWriter writer = null;
try {
countDownLatch1.await();
Analyzer analyzer = LuceneUtils.analyzer;
FSDirectory directory = LuceneUtils.openFSDirectory(luceneDir);
IndexWriterConfig config = new IndexWriterConfig(analyzer);
config.setOpenMode(OpenMode.CREATE_OR_APPEND);
writer = LuceneUtils.getIndexWriter(directory, config);
try {
indexDocs(writer, Paths.get(docPath));
} catch (IOException e) {
e.printStackTrace();
}
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
LuceneUtils.closeIndexWriter(writer);
countDownLatch2.countDown();
}
}
/**
*
* @param writer
* 索引写入器
* @param path
* 文件路径
* @throws IOException
*/
public static void indexDocs(final IndexWriter writer, Path path)
throws IOException {
// 如果是目录,查找目录下的文件
if (Files.isDirectory(path, new LinkOption[0]))