Solr请求过程执行概述

  categories:搜索资料  tags:,   author:

Solr的请求(包括索引数据更新和查询)都是通过 SolrCore类的

execute(SolrRequestHandler handler, SolrQueryRequest req, SolrQueryResponse rsp) 方法来执行的。

其中第一个参数表示执行这一过程的处理器,这些处理器都在handler包下,例如XmlUpdateRequestHandler。

第二个参数表示请求其中包含请求的参数(通常是查询时我们 通过表单传入的参数)以及请求的内容流(例如更新索引时发送的xml格式的数据)。

第三个参数代表响应结果,handler处理请求后会将结果保存在第三个参数中。

SolrQueryRequest 接口的谱系图

 

其中有斜线标示的方法已经废弃

 

SolrQueryResponse接口的谱系图

 

 

索引更新过程

xml格式的更新数据被保存在请求的streams : Iterable<ContentStream>参数中。其中的ContentStream接口谱系图如下:

 

 

注意这里的GiantContentStream是我自己加入的类。 这个类继承自ContentStreamBase并且改写了构造函数与InputStream getStream()方法。这个方法正是更新时被处理器读取数据时调用的关键。看一看这个类的大纲也就明白了这个类的本质。

 

我们看到这样的代码:
final NamedList<Object> responseHeader = new SimpleOrderedMap<Object>();
rsp.add(“responseHeader”, responseHeader);
这段代码的作用是什么呢?它是创建一个请求头的载体,然后将其加入到响应类中,这样我们就可以在响应中获得我们请求头,这个请求头中包含我们的请求参数。
关于NameList这个类,说明文件说明中说它是一个name/value对的有序表,具有如下特性:

  • name可以重复
  • name/value 对的有序
  • 可以通过index访问
  • name和value都可以为空

看看它的谱系图:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

再看这样一段代码:
NamedList toLog = rsp.getToLog();
//toLog.add(“core”, getName());
toLog.add(“webapp”, req.getContext().get(“webapp”));
toLog.add(“path”, req.getContext().get(“path”));
toLog.add(“params”, “{” + req.getParamString() + “}”);
toLog是什么呢?它是SolrQueryResponse类的一个属性,定义如下:

protected NamedList toLog = new SimpleOrderedMap();
上面我们已经看到了SimpleOrderedMap类是NamedList的子类,这里通过属性的名字不难 想象它是记录需要写入日志中的内容的 载体。的确我们在打印出来的日志中很容易找到如下内容:
信息: [database] webapp=null path=null params={} status=0 QTime=1438

执行更新关键的代码是这里:

handler.handleRequest(req,rsp);
setResponseHeaderValues(handler,req,rsp);

第一句执行更新过程,第二句只是将req中的参数部分以及经过处理后的rsp中的toLog中的内容写如到rsp中的请求头 (responseHeader )中来。

我们来看最关键的第一句: handler.handleRequest(req,rsp);

 

关于 handler.handleRequest(req,rsp)。更新常用的handler是XmlUpdateRequestHandler,所以我们 进入该类的handleRequest方法中查看究竟。我们发现XmlUpdateRequestHandler中并没有该方法,我们猜想该方法可以在父 类中,果然。于是我们就研究父类的handleRequest方法。

 

SolrPluginUtils.setDefaults(req,defaults,appends,invariants);
该方法内部的代码如下:
SolrParams p = req.getParams();
if (defaults != null) {
p = new DefaultSolrParams(p,defaults);
}
if (appends != null) {
p = new AppendedSolrParams(p,appends);
}
if (invariants != null) {
p = new DefaultSolrParams(invariants,p);
}
req.setParams(p);
AppendedSolrParams是DefaultSolrParams的子类,可以看出来,这段代码的作用是设置req使其具有实质性的参数内容。

代码:rsp.setHttpCaching(httpCaching);
作用:设置是否进行缓存功能。

代码:handleRequestBody( req, rsp );
这段代码是处理体,由于我们获得的是XmlUpdateRequestHandler的实例,所以我们又要跳到 XmlUpdateRequestHandler类的handleRequestBody方法中了。handleRequestBody所做事情如下:

  • SolrParams params = req.getParams();获得请求参数内容。
  • UpdateRequestProcessorChain processingChain =
    req.getCore().getUpdateProcessingChain( params.get( UpdateParams.UPDATE_PROCESSOR ) );获得更新请求处理链。
  • UpdateRequestProcessor processor = processingChain.createProcessor(req, rsp);获得更新请求处理器。
  • Iterable<ContentStream> streams = req.getContentStreams();获得更新请求的内容流。
  • 如果streams为空,进行提交的尝试处理,处理失败抛出错误。如果不为空,如下处理:

for( ContentStream stream : req.getContentStreams() ) {
Reader reader = stream.getReader();
try {
XMLStreamReader parser = inputFactory.createXMLStreamReader(reader);
this.processUpdate( processor, parser );
}
finally {
IOUtils.closeQuietly(reader);
}
}
这段处理的本质是循环获取每部分内容流,然后获得内容流的Reader实例。接下构建XMLStreamReader 实例并调用this.processUpdate( processor, parser );来完成更新。最后尝试进行提交(仅仅是可能,因为我们可能在更新的同时在请求参数中加入commit参数,并设其值为true)。

  • processor.finish();

我们一步一步来观察这个详细的过程。
首先是获得处理链,版本的solrconfig.xml配置文件,所以没有添加处理链的配置参数。所以我们在 eq.getCore().getUpdateProcessingChain方法中传入的参数是null,但是尽管如此依然获得了一个包含两个 UpdateRequestProcessorFactory的处理链。虽然我们知道Map类型中可以包含键为null的内容,但是这个内容是怎么获得的呢?
实际上SolrCore的构造函数中调用了SolrCore类的loadUpdateProcessorChains方法,这就是加载处理链的地方。在这 段代码中我们看到这两个UpdateRequestProcessorFactory的原型:
new RunUpdateProcessorFactory(),
new LogUpdateProcessorFactory()。
也就是说在没有进行处理链配置的时候,就使用它们作为默认值。
我们从RunUpdateProcessorFactory来看更新处理器工厂的本质。它是RunUpdateProcessor的工厂,工厂的本质是产 生实例,所以它最重要的方法是getInstance(SolrQueryRequest, SolrQueryResponse, UpdateRequestProcessor),这里的最后一个参数是下一个更新请求处理器,这就是“链”的由来。那么 RunUpdateProcessor是什么呢?请看下面。

 

 

 

 

 

 

 

 

RunUpdateProcessor实质是处理添加,提交,删除的地方,每个更新方法的参数是UpdateCommand的子类。也就是最终我们的更新 请求都会通过这里来进行处理。而LogUpdateProcessorFactory又有所不同,它是关于日志处理器 (LogUpdateProcessor)的一个工厂类。

然后,我们利用获得的处理链来构建处理器:
UpdateRequestProcessor processor = processingChain.createProcessor(req, rsp);
在createProcessor方法的内部,我们看到每个处理器工厂都被轮流调用了,也就是最终返回的processor 是和一连串的处理器相关的。

最后,我们来看如何利用获得的处理器以及多个内容流streams来处理更新请求的。
for( ContentStream stream : req.getContentStreams() ) {
Reader reader = stream.getReader();
try {
XMLStreamReader parser = inputFactory.createXMLStreamReader(reader);
this.processUpdate( processor, parser );
}
finally {
IOUtils.closeQuietly(reader);
}
}
我们在一个for循环中对每个ContentStream 作如下处理。先获得其Reader的实例,然后利用xml分析工具构建对Reader的解析字符流。
XMLStreamReader parser = inputFactory.createXMLStreamReader(reader);
使用this.processUpdate( processor, parser );来处理更新。

 

XmlUpdateRequestHandler的processUpdate( processor, parser )做了什么事情呢?请跟随我来看一看。
主要到while (true)这样一个循环,在循环内部是对parser :XMLStreamReader的循环读取与处理。这个循环中有一个switch语句,:
int event = parser.next();
switch (event){
case XMLStreamConstants.END_DOCUMENT:
parser.close();
return;
case XMLStreamConstants.START_ELEMENT:
String currTag = parser.getLocalName();//获取当前标签名

if (currTag.equals(ADD)) {
…………
//这里主要是设置addCmd的一些属性
}
else if (“doc”.equals(currTag)) {
……
processor.processAdd(addCmd);
……
}
else if ( COMMIT.equals(currTag) || OPTIMIZE.equals(currTag)) {
……
processor.processCommit( cmd );
……
}
else if (DELETE.equals(currTag)) {
……
processDelete( processor, parser);
……
}
break;
}

 

在这个过程中,我们看到xml解析时,发现当前标签,然后判断其内 容。如果内容为add,那么使用
addCmd = new AddUpdateCommand()来创建新的addCmd实例,并设置其overwriteCommitted等属性。如果标签内容为doc,使用如 下代码先清空addCmd的内容,然后从xml数据中读取一 个新的Doc并将其放如addCmd中。
addCmd.clear();
addCmd.solrDoc = readDoc( parser );
然后调用processor.processAdd(addCmd)方法来进行处理,至于commit与delete也是类似的方法处理。

 

下面我们来看processor.processAdd(addCmd)做了些什么,清楚它的实质以后,关于提交与删除的处理就可以用同样的方法来进行处 理了。
通过简单的信息输入发现,这里有三个类的processAdd方法被调用了。首先调用的是RunUpdateProcessor的processAdd方法,在这个方法中调用 了父类(UpdateRequestProcessor)的processAdd方法,调用父类的方法的时候我们注意到方法中有if (next != null) next.processAdd(cmd)代码,正是这个代码的调用使得LogUpdateProcessor中的processAdd得以调用,这正是 处理得以实现的根本。也就是说之前构造时候的链式仅仅时使其具有链式的能力,而调用父类 (UpdateRequestProcessor)的processAdd方法使得链式真正发挥了作用。

 

下面我们沿着RunUpdateProcessor、UpdateRequestProcessor、LogUpdateProcessor这条主线一步 一步来看。
在RunUpdateProcessor中的processAdd方法中。通过如下的代码获得了cmd的doc属性,这就是说将原来的 SolrInputDocument引入了schema信息建立了lucene下 的docement。
cmd.doc = DocumentBuilder.toDocument(cmd.getSolrInputDocument(), req.getSchema());
然后通过下面的代码来实现 lucene下的文档对象的更新。
updateHandler.addDoc(cmd);
这里的updateHandler是什么东西呢?输出updateHandler.getClass().getName()信息很容易发现该类是 org.apache.solr.update.DirectUpdateHandler2。这是更新默认的处理器,也可以在配置文件中进行设定。
我们进入该类看个究竟吧。
简单浏览代码概要发现所有的更新处理最终都落实到这里,常常惹人头痛的“两把锁”–提交锁与访问锁也都在这里。暂且先不管这么多吧,我们先来看看我们关心的addDoc方法。

 

 

我们进入了DirectUpdateHandler2中的addDoc方法,现在我们来看看里面发生了什么惊天动地的事情吧。
前面几行代码是增加一些记录添加的文档有多少等的一 些信息以及如果当前ID域不存在的情况下允许重复的处理,暂且别管它。我们从读索引访问加锁开始。
iwAccess.lock();
获得锁以后我们在一个try语句中有如下的代码:
synchronized (this) {
// adding document — prep writer
closeSearcher();
openWriter();
tracker.addedDocument();
}
这段代码做了什么呢?一个一个的分析。

closeSearcher()
关闭搜索器,这样使得没有搜索器会从索引文件读取内容。我试图证明 这个观点,但是还没有找到直接的证据,暂时保留意见吧。

openWriter();
这个过程先后经过了下面的方法:

DirectUpdateHandler2的 openWriter()
UpdateHandler的createMainIndexWriter(“DirectUpdateHandler2″, false)方法。第二个参数表示是否removeAllExisting。
UpdateHandler的SolrIndexWriter(name,core.getIndexDir(), removeAllExisting, schema, core.getSolrConfig().mainIndexConfig);参数分别为名字,索引目录路径,是否移去所有已经存在的内容,代表schema.xml内容的实例,代表索引的相关配置属性的实例。
最后就是调用lucene的 IndexWriter的构建方法了。
这一切的目的就是使得当前DirectUpdateHandler2的实例具备一个可用的IndexWriter的实例,注意这里是lucene下的 IndexWriter了。

tracker.addedDocument()
tracker是CommitTracker的实例,作为DirectUpdateHandler2的属性,我把它叫做提交跟踪器。代码说明中这样介绍: 这是一个跟踪自动提交的帮助类。

为了保持思路的完整性暂且别管这个类,后面再来理它。先来看看后面的代码。
后面其实是先获得cmd中的overwriteCommitted以及overwriteCommitted的值来决定如何添加文档到索引中。一种是允许重复ID的添加,另外一种是不允许重复的添加。代码如下:
if (cmd.overwriteCommitted || cmd.overwritePending) {
if (cmd.indexedId == null) {
cmd.indexedId = getIndexedId(cmd.doc);
}
//id唯一的添加方式
writer.updateDocument(idTerm.createTerm(cmd.indexedId), cmd.getLuceneDocument(schema));
} else {
// allow duplicates
writer.addDocument(cmd.getLuceneDocument(schema));
}

 

 

了解一下SOLR的跟踪器。先来看看构造函数。
public CommitTracker() {
docsSinceCommit = 0;
pending = null;

docsUpperBound = core.getSolrConfig().getInt(“updateHandler/autoCommit/maxDocs”, -1);
timeUpperBound = core.getSolrConfig().getInt(“updateHandler/autoCommit/maxTime”, -1);

SolrCore.log.info(“AutoCommit: ” + this);
}
docsSinceCommit记录自上一次提交以来已经具有的doc的数目,pending 是ScheduledFuture的对象,显然这个类里面用到了多线程。docsUpperBound和 timeUpperBound是从配置文件获得的提交条件,doc的上界以及时间的上界。
再来看看addedDocument方法吧,这个方法没有参数。首先利用下面的两行代码来对已经添加的doc数目进行递增,同时设置当前时间给 lastAddedTime ,也就是获得最近添加doc时的系统时间。
docsSinceCommit++;
lastAddedTime = System.currentTimeMillis();
下面是一个条件为docsUpperBound > 0 && (docsSinceCommit > docsUpperBound)的if语句,这个语句的意思是,在doc数目上界大于0且自上次提交以来的doc数目已经超过了允许的上界,那么做以下事情:
如果上次提交延迟,中断上次提交延迟的任务。为pending分配一个新的任务(这里就是CommitTracker本身)。这段代码的本质就是在doc 已经超过允许的值的情况下进行提交。
接下来就是分配一个超时触发的提交任务,也就是在设定的时间到了以后触发提交过程。

既然schedule方法中的Runnable参数是this,也就是提交跟踪器,那么我们就有必要来看一看它的run方法了。这个方法包括提交的所有过程。

CommitUpdateCommand command = new CommitUpdateCommand( false );
command.waitFlush = true;
command.waitSearcher = true;
//no need for command.maxOptimizeSegments = 1; since it is not optimizing
commit( command );
autoCommitCount++;

这里首先创建一个提交更新命令的对象,并设置构造函数的参数为false,意思是optimize(优化)为false。r然后设置其两个属性 waitFlush 和waitSearcher 为true,然后调用commit方法处理刚才创建的提交更新命令的对象。最后将自动更新计数增1。不管自动提交过程成功或者失败,都会在finally 语句块中将pending设为null,以取消pending对线程的监听。

最后的几句代码是判断是否又有新的内容添加进来(通过lastAddedTime > started否判断),如果有又重复这样一个过程:

1.如果已经添加进来的doc的数目大于允许的doc的上界,那么启动一个自动提交线程。

2.如果超时时间大于0,那么也启动一个自动提交线程,这里的时间延迟是时间上界。

如此说来,这个run中最重要的实质部分就是commit(command)方法,我们必须知道这个方法做了什么。按照顺序来解释代码。

if (cmd.optimize) {
optimizeCommands.incrementAndGet();
} else {
commitCommands.incrementAndGet();
}

根据是否优化(optimize)来设置是优化计数器递增还是一般提交计数器递增。

————————————————————

Future[] waitSearcher = null;
if (cmd.waitSearcher) {
waitSearcher = new Future[1];
}

构造一个Future的空数组,然后根据是否设置waitSearcher(决定是否在获得新的Searcher后继续进行还是立即先下执行)来决定是否为这个Future数组进行实例分配。如果对其进行分配,后面的代码会在某个位置利用waitSearcher来等待其关注的代码执行完毕后继续向前执 行。

iwCommit.lock();
try {
log.info(“start “+cmd);

if (cmd.optimize) {
closeSearcher();
openWriter();
writer.optimize(cmd.maxOptimizeSegments);
}

closeSearcher();
closeWriter();

callPostCommitCallbacks();
if (cmd.optimize) {
callPostOptimizeCallbacks();
}
// open a new searcher in the sync block to avoid opening it
// after a deleteByQuery changed the index, or in between deletes
// and adds of another commit being done.
core.getSearcher(true,false,waitSearcher);

// reset commit tracking
tracker.didCommit();

log.info(“end_commit_flush”);

error=false;
}
finally {
iwCommit.unlock();
//TEST
log.info(“提交锁解锁2″);
addCommands.set(0);
deleteByIdCommands.set(0);
deleteByQueryCommands.set(0);
numErrors.set(error ? 1 : 0);
}
这里使用锁来进行同步它之后执行的代码。如果需要优化(optimize),那么关闭关联的搜索器,打开写索引器,然后调用 writer.optimize(cmd.maxOptimizeSegments)方法。该方法的作用是使得索引断的数目少于 maxOptimizeSegments。

closeSearcher();
closeWriter(); li

为了防止上面的if没有成立(即优化没有进行)的时候没有进行搜索器和写索引器的关闭,那么我们这里再进行一次以确保确实被执行了。

callPostCommitCallbacks();
if (cmd.optimize) {
callPostOptimizeCallbacks();
}

对于这段代码,目前不知道有什么作用。先不理它了。

core.getSearcher(true,false,waitSearcher);
这里显然是重新获得搜索器,这里正是提交过程是精髓所在,所谓提交就是使得刚才的更新生效,也就是搜索器的重新建立。因为我们的查询结果都是从搜索器获得 的。这里将waitSearcher作为参数传递给了getSearcher方法,也为后面的等待searcher获取过程完毕才继续执行埋下了伏笔。

tracker.didCommit();只是完成reset工作。finally里面的块也是完成一些变量的重设工作。

最后这段代码:

if (waitSearcher!=null && waitSearcher[0] != null) {
try {
waitSearcher[0].get();
} catch (InterruptedException e) {
SolrException.log(log,e);
} catch (ExecutionException e) {
SolrException.log(log,e);
}
}

这正是上面埋下的伏笔,这里通过查看waitSearcher来获知搜索器建立的线程是否已经完成了,如果没有完成,等待,如果完成了那么继续向下运行。
我们知道了所谓提交就是使得更新处理 对查询生效。也就是搜索器的重新建立,这里我们就来看看这个搜索器重新获得的过程是怎么实现的。 也就是DirectUpdateHandler2类commit方法中的这一小句代码所做的事情: core.getSearcher(true,false,waitSearcher)。
我们来看看SolrCore类中的getSearcher(boolean forceNew, boolean returnSearcher, final Future[] waitSearcher)方法的参数说明。
forceNew:是否强制建立新的,因为可以利用原有的来建立,故由此 一说。 (提交当然是ture)
returnSearcher:是否需要返回一个 Searcher。(提交false即可,因为没有必要返回新的)
waitSearcher:这里将填 充一个Future,这个Future要直到获得一个搜索器以后才返回。利用它可以获得waitSearcher功能。

 

下面来看看代码。一开始就来了一个synchronized语句块。这个语句快说明如下:
// it may take some time to open an index…. we may need to make
// sure that two threads aren’t trying to open one at the same time
// if it isn’t necessary.
翻译一下先: 打开一个索引会花一 点时间…如果没有必要,我们需要确保没有两个线程同时打开一个索引。
synchronized (searcherLock) {
// 看是否有一个搜索器可以直接返回。前提:不需要强制返回新的搜索器。
if (_searcher!=null && !forceNew) {…
}

// 看是否有正在建立中的搜索器,如果有,我们等待它建立
if (onDeckSearchers>0 && !forceNew && _searcher==null) {
try {
searcherLock.wait();
} catch (InterruptedException e) {
log.info(SolrException.toStr(e));
}
}

//经过了上面的等待,我们再次查看是否有可用的搜索器可以返回了
if (_searcher!=null && !forceNew) {…
}

到此位置,既然没有可以直接返回的,或者需要强制建立新的,我们只好开始建立过程了。
首先设置一个信号,通知其它线程,一个新的搜索器正在建立过程中。先开始onDeckSearchers合法性检查。
onDeckSearchers++;
if (onDeckSearchers < 1) {
// 检查onDeckSearchers 是否小于1,因为上面进行了加加操作,所以不 应小于1,但是
//只是出于编程习惯的检查,实际上并不会发生。
log.severe(logid+”ERROR!!! onDeckSearchers is ” + onDeckSearchers);
onDeckSearchers=1; // reset
} else if (onDeckSearchers > maxWarmingSearchers) {
//如果超过了最大允许的“预热”搜索器的数目,抛出错误
} else if (onDeckSearchers > 1) {//仅仅输出警告信息,因为搜索器的建立也不是随便允许多个的
log.info(logid+”PERFORMANCE WARNING: Overlapping onDeckSearchers=” + onDeckSearchers);
}
}

如果把搜索器的建立比作一个工程,那么上面的过程是对这个工程的可行性,环境污染性,资源消耗程度等方面的评估。下面就正是开工咯。
首先建立一个新的搜索器,如果这个搜索器建立的过程失败了,我们在一个同步 块中进行处理(onDeckSearchers递减)。失败后我们没有什么可以继续的了,抛出错误并返回。如果成功了那么还要继续进行一系列的处理。下面 代码中的 tmp = new SolrIndexSearcher….就是获得一个搜索器的过程。它有可能失败。即使成功,我们仍然需要进行进一步的处理,别忘记了我们的返回类型 是RefCounted<SolrIndexSearcher> 而不是SolrIndexSearcher类型,而且新建立的搜索器是”裸体“的,没有任何内容的。
SolrIndexSearcher tmp;
try {
tmp = new SolrIndexSearcher(this, schema, “main”, IndexReader.open(FSDirectory.getDirectory(getIndexDir()), true), true, true);
} catch (Throwable th) {
synchronized(searcherLock) {
onDeckSearchers–;
// notify another waiter to continue… it may succeed
// and wake any others.
searcherLock.notify();
}
// need to close the searcher here??? we shouldn’t have to.
throw new RuntimeException(th);
}
这里简单写出SolrIndexSearcher的构造函数。根据参数名很容易明白其含义。

SolrIndexSearcher(SolrCore core, IndexSchema schema, String name, IndexReader r, boolean closeReader, boolean enableCache);

我们假设新的处理器的建立成功了,也就是tmp确实获得了一个搜索器,那么我们就需要进一步的处理,注意成功获得tmp的情况下 onDeckSearchers没有递减,且 searcherLock.notify()也还没有执行。我们可以猜想,下面的处理中肯定会出现这两个语句。
处理包括哪些呢?
final SolrIndexSearcher newSearcher=tmp;//将获得的搜索器引用赋予newSearcher,从此newSearcher就代表了这个新建立 的搜索器。

newSearchHolder=newHolder(newSearcher);//将新获得的搜索器包装成newSearchHolder,使其具备引用计数功能

根据returnSearcher是否为ture,决定newSearchHolder.incref()是 否执行,也即其引用计数是否增加。

下面这三个语句是用来记录是否需要搜索器计数的递减和是否已经完成注册。如果在后面的检查中发现 decrementOnDeckCount[0]=true,我们还需要执行递减操作。

final boolean[] decrementOnDeckCount=new boolean[1];
decrementOnDeckCount[0]=true;
boolean alreadyRegistered = false;

接下来A.如果_searcher为null且useColdSearcher为ture(意思是原有的_searcher为空,并且配置文件中指定使用 “冷的搜索器”)那么我们将newSearchHolder 进行注册(注册时已经执行了onDeckSearchers–与 searcherLock.notifyAll()),同时 decrementOnDeckCount[0]=false; boolean alreadyRegistered = true;(意思是后面通过对这两个变量的检查就可以确定已经注册完毕且建立计数也已经递减了)。B.如果 _searcher不为空,那么currSearcherHolder=_searcher; currSearcherHolder.incref();(也就是这里currSearcherHolder获 得了原有的_searcher)。

接下来的这段代码currSearcher = currSearcherHolder==null ? null : currSearcherHolder.get();她的本质是使得currSearcher保存了原有的_searcher内容。

到这里的时候,我们可以肯定,如果原有_searcher不为空,那么 currSearcher中是有内容的,而如果原来 _searcher为空,这里的currSearcher也就为空,下面的处理就是:

A.如果currSearcher不为空(请注意这时候它来自_searcher),我们将它预热newSearcher。

B.如果currSearcher为空,也就是我们不具备直接预热的来源,如果这时候具有一个第一次(也就是之前没有任何搜索器)建立搜索器的监听器(firstSearcherListeners),那么我们就可以让这 个监听器来处理newSearcher。

C如果currSearcher不为空,也就是原有的_searcher具有内容,如果这时候具有具有一个新搜索器建立时的监听器(newSearcherListeners),那么我们就用这个监听器来 根据currSearcher来对newSearcher进行处理。

最后看是不是已经注册了,因为前面的处理中仅仅在获得了newSearcher且_searcher为空的时候才有可能进行注册,也就是假如 _searcher不为空的情况下,newSearcher很可能是没有注册的,这样的话,我们这里就需要进行一个注册。

最后剩下的就是决定是否需要返回搜索器了,同时决定是否需要将与搜索器建立过程相关的future与传递进来的future参数进行关联。

对于像监听器(newSearcherListeners与firstSearcherListeners)与搜索器注册这样的概念我们还没有理解。

主要是想弄清楚Solr1.5中的搜索器注册以及建立新搜索器监听器和建立第一次搜索器监听器的概念,同时弄清楚预热是个什么概念。

 

 

注册搜索器

SolrCore::registerSearcher(RefCounted<SolrIndexSearcher> newSearcherHolder)
方法目的:用newSearcherHolder来替代_searcher(SolrCore的属性),同时该方法内部的下面代码完成了对 newSearcherHolder所持有的搜索器的注册。
SolrIndexSearcher newSearcher = newSearcherHolder.get();
newSearcher.register(); //

newSearcher.register()做了什么事情呢?先看代码。
>>>>
core.getInfoRegistry().put(“searcher”, this);
core.getInfoRegistry().put(name, this);
for (SolrCache cache : cacheList) {
cache.setState(SolrCache.State.LIVE);
core.getInfoRegistry().put(cache.name(), cache);
}
registerTime=System.currentTimeMillis();
1.将搜索器的信息加入核的信息记录器并与searcher关联
2.将搜索器的信息加入核的信息记录器并与搜索器的名字关联
3.将与这个搜索器关联的每个缓存的名字与实例的管理加入核的信息记录器中
4.设置当前搜索器的注册时间

  • firstSearcherListeners

在SolrCore中有一个事件监听器(SolrEventListener)的列表firstSearcherListeners。
SolrEventListener是一个接口,尽管它有init(NamedList args),postCommit(),newSearcher(SolrIndexSearcher newSearcher, SolrIndexSearcher currentSearcher)三个方法,现在我们只关心其newSearcher方法。SolrEventListener类谱系图如下:

 

 

 

 

 

这两个监听器都是在solrconfig.xml中设置的。但是如果没有设置怎么办呢,应该有一个默认值。但是发现其实如果配置文件中没有设置的话,那么 这个监听过程是不存在的。也就是什么都不住。

  • newSearcherListeners

与firstSearcherListeners类似,不赘述。

  • 预热

SolrIndexSearcher::warm(SolrIndexSearcher old)
这个预热过程就是将old搜索器中的内容拷贝到本方法所属的搜索器中来。一般每个搜索器具有三个缓存 filterCache,queryResultCache,documentCache,我把他们分别叫做过滤缓存,查询结果缓存,文档缓存。



快乐成长 每天进步一点点