基于solr4.4
solr索引过程分析
分布式的索引起始就是在每个sharding中建立索引,在sharding中的索引过程和单节点的情况是一致的。
先说一下solr的单节点的索引过程,
在Http请求的SolrDispatcherFilter filter处理中,获取SolrRequestHandler
handler = core.getRequestHandler( path );
这里的handler是UpdateRequestHandler
<requestHandler name="/update" class="solr.UpdateRequestHandler">
SolrDispatcherFilter.execute( HttpServletRequest req, SolrRequestHandler handler, SolrQueryRequest sreq, SolrQueryResponse rsp)
---SolrCore.execute( handler, req, rsp );
---SolrRequestHandler.handleRequest
在handleRequest实现中,调用handleRequestBody进行处理,处理过程如下:
//生成的processor链有两个,RunUpdateProcessor&LogUpdateProcessor,其中一个在processor包括另外一个processor作为next变量
UpdateRequestProcessorChain processorChain =
req.getCore().getUpdateProcessingChain(params.get(UpdateParams.UPDATE_CHAIN))
UpdateRequestProcessor processor = processorChain.createProcessor(req, rsp);
//用documentLoader对数据参数做加工处理,生成UpdateCommand作为processor的输入参数
documentLoader.load(req, rsp, stream, processor);
----RunUpdateProcessor.processAdd(AddUpdateCommand cmd)
-----DirectUpdateHandler2.addDoc
-----DirectUpdateHandler2.commit
结合lucene的索引的底层的原理,重点对commit这个方法进行分析,
commit,
void commit(CommitUpdateCommand cmd)
这个方法会在几种场合下调用,softCommit,hard commit,以及显式进行commit api的调用
比如可以在solrConfig.xml配置中声明超过多长时间,就自动触发commit或者softCommit。
commit操作把索引sync到磁盘上,在autoCommit操作时,可以选择是否重新打开IndexSearcher,还是用old的IndexSearcher;
softCommit是solr近实时搜索的一种方案,只是使得可以搜索到当前的内存中索引的改变,而不用把索引sync到文件中。
这样可以避免commit操作时的消耗,但是只有真正做commit操作,索引才真正持久化到硬盘上。
因基于当前的索引目录,打开新的Searcher在性能和效率方面都不高,在这里重点关注一下在commit方法中打开IndexSearcher的操作,如何考虑这方面的,
在commit中会调用SolrCore.getSearcher(true, false, waitSearcher, true)来打开新的Searcher来使得可以搜索到自commit之前对索引的修改,
关注一下SolrCore的这个方法,
RefCounted<SolrIndexSearcher> getSearcher(boolean forceNew, boolean returnSearcher, final Future[] waitSearcher, boolean updateHandlerReopens)
//此处设置forceNew=true,目的是新的seacher基于最新的索引变更生效
----openNewSearcher(boolean updateHandlerReopens, boolean realtime)
if (writer != null) {
//基于当前最新的IndexWriter进行构建
newReader = DirectoryReader.openIfChanged(currentReader,writer.get(), true);
} else {
// verbose("start reopen without writer, reader=", currentReader);
//基于当前最新的reader进行构建
newReader = DirectoryReader.openIfChanged(currentReader);
// verbose("reopen result", newReader);
}
基于当前最新的IndexWriter进行构建的实现如下:
DirectoryReader中的
DirectoryReader openIfChanged(DirectoryReader oldReader, IndexWriter writer, boolean applyAllDeletes)
-----(StandardDirectoryReader)oldReader.doOpenIfChanged(writer, applyAllDeletes);
if (writer == this.writer && applyAllDeletes == this.applyAllDeletes) {
//如果IndexCommit不为空,那就基于该IndexCommit为基准,每次提交操作都会产生相应的IndexCommit,代表提交操作生成新的Segments_N文件,
//IndexCommit由调用IndexDeletionPolicy策略进行删除
return doOpenFromWriter(null);
} else {
return writer.getReader(applyAllDeletes);
}
在doOpenFromWriter方法实现中包括了write.getReader的分支
private DirectoryReader doOpenFromWriter(IndexCommit commit) throws IOException {
if (commit != null) {
return doOpenFromCommit(commit);
----SegmentInfos.run(commit)
如果commit不为空,那么就把IndexCommit中segmentInfos和当前的Reader做比对
若commit为空,就拿索引文件目录下的合适的Segment_N构造segmentInfos和和当前的Reader做比对
(这种情况和基于reader是一致的,DirectoryReader.openIfChanged(currentReader);),
未做变更的部分段的reader可以直接拿来用,提高效率。参见doOpenIfChanged逻辑
DirectoryReader doOpenIfChanged(SegmentInfos infos) throws IOException {
return StandardDirectoryReader.open(directory, infos, getSequentialSubReaders(), termInfosIndexDivisor);
}
}
//验证当前writer是否和旧的reader中的segmentInfos相关的信息是否一致,包括segmentInfos版本信息,writer中的内存区域的docWriter和deletes部分是否存在数据
//不一致说明,writer有修改过当前的索引
if (writer.nrtIsCurrent(segmentInfos)) {
return null;
}
//根据writer获取reader
DirectoryReader reader = writer.getReader(applyAllDeletes);
//IndexWriter的内部对象docWriter中的DocumentsWriterPerThread(DWPT),每个DWPT将每个段写入自有的段中,提高并发索引的效率。
----anySegmentFlushed = docWriter.flushAllThreads();
//在IndexWriter中,保存了readerpool,当IndexWriter向索引文件提交删除的时候,仍然是从readerpool中得到相应的IndexReader,
//并用IndexReader来进行删除的
---- maybeApplyDeletes(applyAllDeletes);
//根据writer.readerPool依次获取每个segment中的reader,作为新的StandardDirectoryReaderf返回
----StandardDirectoryReader.open(this, segmentInfos, applyAllDeletes);
return reader;
}
通过对以上openSearcher源码的分析,可见打开一个reader基本上有三种方式,
IndexReader.Open(Dir,readOnly);
IndexReader.reopen(readOnly);
IndexWriter.getReader();
按照顺序,效率依次变高;通过IndexWriter.getReader的方式,也被用在了NRT的场景中