Solr单节点索引和NRT源码分析

基于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的场景中

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Solr,源码分析,NRT)